Делаю Package Manager для VBA

от автора

Я знаю, какое у вас сейчас лицо:

лицо человека, который узнает что кто-то пишет код на VBA

лицо человека, который узнает что кто-то пишет код на VBA

Но на самом деле идея не нова и изначально я даже думал не изобретать велосипед, ведь есть по описанию неплохой vba-blocks, с открытым исходным кодом. Бери не хочу.
Увы, так вышло, что я не умею писать скрипты в powershell, а у меня при установке какой-то из этих скриптов не отрабатывает и падает.

Если, кстати, кому-то интересно, попробуйте поправить, расскажите хоть как оно?

Ну а я беру палки и колеса, и начинаю лепить творить все же лепить.

примерно на этой стадии находится большинство моих велосипедов...

примерно на этой стадии находится большинство моих велосипедов…

Прежде чем мы начнем

1. Я не программист, я музыкант. Но сейчас моя профессия – разработчик VBA. Я не знаю как правильно писать код. Но учусь, кажется. И когда я делюсь своими мыслями – я тоже учусь. Если вы хотите помочь мне научиться, я буду только рад ?

2. Далее пойдет многобуквенный текст о том, как автор делает для себя инструмент для упрощения работы с VBA. Кода нет. Вернее у меня то он есть, но его много и какие куски сюда добавлять я не знаю (да и надо ли оно).

3. Здесь вы не увидите очень крутую штуку, которую точно-приточно надо быстрее запихивать в мой проект, оспаде, как же я раньше жил без этого.
Это, по сути, личный инструмент автора, про который он (кто он? я, получается) решил поведать всему миру хабру.

Так что если вы сюда за святым граалем — Thank you Mario! But our princess is in another castle!

А всем остальным приятного время провождения.

Что такое PackageManager

Не буду нудить скучными вырезками из википедии и постараюсь своими словами объяснить.
Package Manager – это npm, и pip, и nuget, и composer etc.

Ну вы поняли.

Нет? Тогда вот вам википедия под катом:

Информация из Википедии, свободной энциклопедии

Система управления пакетами (также иногда «менеджер пакетов» или «пакетный менеджер») — набор программного обеспечения, позволяющего управлять процессом установки, удаления, настройки и обновления различных компонентов программного обеспечения. Системы управления пакетами активно используются в различных дистрибутивах операционной системы Linux и других UNIX-подобных операционных системах.

Программное обеспечение представляется в виде особых пакетов, содержащих, помимо дистрибутива программного обеспечения, набор определённых метаданных, которые могут включать в себя полное имя пакета, номер версии, описание пакета, имя разработчика, контрольную сумму, отношения с другими пакетами. Метаданные сохраняются в системной базе данных пакетов.

Исходя из того, что делает менеджер пакетов, можно вывести список того, что же нам предстоит разработать.
Ну, вроде все просто:

Нужно создать консольное приложение, которое будет парсить….
Так, стоп

Всё, да? Приплыли. Консольное. А как же в excel/word/access открывать консоль-то? Отдельно чтоли? Это же неудобно!
А Immediate Window только и умеет, что выводить информацию через Debug.Print.

Да, но не совсем. Immediate (я буду называть ее вбансоль, чтоб понятней было) умеет выполнять процедуры и функции:

это не реклама, я сам себе не платил (ссылка в профиле, кстати)

это не реклама, я сам себе не платил (ссылка в профиле, кстати)

Ну, то есть, есть у нас какая-то функция, мы ее в вбансоли вызываем, она нам выдает какой-то результат. Уже можно парсить аргументы, получается.
Попробуем еще раз пройти по списку?

Нужно создать консольное вбансольное приложение, которое будет парсить аргументы и исходя из переданной команды выполнять некие действия:
1. Инициализировать проект (init)
2. Находить и устанавливать пакеты (install)
3. Публиковать пакеты в некое хранилище (publish)
4. Обновлять уже установленные пакеты (update)
5. Удалять установленные пакеты из проекта (uninstall)

Ну это база.

подпись к изображению я не придумал, поэтому вот вам котик >^,^< » title=»подпись к изображению я не придумал, поэтому вот вам котик >^,^< » width=»600″ height=»600″ data-src=»https://habrastorage.org/getpro/habr/upload_files/95e/a04/615/95ea04615662247a550c746a283591fe.png»/></p>
<div><figcaption>подпись к изображению я не придумал, поэтому вот вам котик >^,^< </figcaption></div>
</figure>
<h3>А зачем козе баян?</h3>
<p>Нет, а действительно, зачем? Разве макросы настолько большие, что туда постоянно нужно что-то импортировать?</p>
<p>Да. Причем на моей практике таких макросов много. И так как VBAшники работают в современных IDE, а сам VBA – современный язык программирования, для которого как крупные компании, вроде google, так и энтузиасты пишут кучу разных пакетов/библиотек/фреймворков, ими нужно как-то управлять.</p>
<blockquote>
<p>…</p>
</blockquote>
<p>Ладно, на самом деле вс<em>ё</em> чуточку плачевней.</p>
<figure class=но все же приемлемо

но все же приемлемо

Откровенно говоря, я действительно в каждый проект импортирую кучу кода, который специально пишу для переиспользования. И да, все это пишется только для себя, как и любые VBA инструменты (надстройки не берем в расчет, наверное, как удобно; вы можете взять в расчет, а я не хочу).

Самый частый гость в моих проектах – класс модуль CRegExp, который помогает взаимодействовать с регулярками (напишите в комменты, если интересно глянуть как он выглядит, закину на GitHub |=| там по сути обертка для VBScript.RegExp, так что наверное ничего интересного |=| я передумал, короче, не пишите в комменты).
И подобных гостей много. И импортировать все это каждый раз руками… я что, не программист чтоли?

До недавнего времени, для импорта пакетов я пользовался… Ни за что не догадаетесь… изобретенным для себя велосипедом в виде надстройки, которую подключал к проекту. В ней есть несколько функций, вызывая которые в вбансоли, можно было импортировать код, заранее сохраненный в какую-нибудь папочку, в подключенный проект и спокойно им пользоваться.

Очень удобно, поверьте.

И вот сейчас кодовая база моих пакетов начала разрастаться и ими стало неудобно управлять.

Отсутствует нормальное версионирование, например. Неудобно прописывать зависимости для того или иного пакета (а так как код переиспользуемый, некоторые пакеты ссылаются на другие пакеты).

И вот тут я понял, что настало время нового велосипеда…

А что это за кружочки?

? – реализовано
? – в процессе реализации, но уже что-то работает
? – пока даже класс не создал

?Нулевая задача — а как звать то?

Итак, с чего начать новый проект?

Правильно – с названия. Я потратил на то, чтобы придумать название менеджера пакета для VBA почти полчаса, но теперь оцените:

ipm – Immediate Package Manage

А? Каково?
Думали vpm (vba package manager) увидеть, а вот и нет.

Искушенный читатель сразу заметил отсылку к другому менеджеру – npm. И да, для меня он стал идейным вдохновителем и лекалом всего проекта, поэтому велосипед будет изобретаться по его образу и подобию (со своей реализацией, конечно).

?Первая задача — модуль Package

Вспоминаем, что там у нас идет первым делом:

Инициализировать проект (init)

Вот с init и начнем.

При вызове этой команды, вбансоль должна запросить:

  1. Имя пакета

  2. Версию

  3. Автора

  4. Описание

и записать все эти данные. В npm они хранятся в файле package.json.

И что, опять стоп? Как вбансоль диалог то делать будет?

Отставить панику, я все придумал.

Материал из лучшего телеграм канала о VBA по версии его админа – Дневник VBAшника

PackageManager: диалог в Immediate Window


Так вот, думал, как бы сделать аналог консольного диалога (в npm, например, после вызова команды init происходит диалог с пользователем, после чего из полученных данных формируется package.json). Immediate Window не совсем для таких вещей сделали. Но выдумать, таки, получилось.

Суть простая:
Пишем основную процедуру, в которой будет вызван диалог.
Заканчиваем выводом сообщения с названием следующей функции и нижним подчеркиванием, в которую ожидаем аргумент от пользователя.

Примерно так все выглядит:

Sub Start()   Debug.Print «Продолжить диалог?(y/n)»   Debug.Print «Continue _» End Sub  Sub Continue(ByVal Choice As String)   If Choice = «y» then     Debug.Print «Продолжаем, следующая функция!»   ElseIf Choice = «n» then     Debug.Print «Ок.»   Else     Debug.Print «Неизвестная команда!»   End If End Sub

В итоге в Immediate Window получаем такой диалог:

Start   ' жмем Enter => Продолжить диалог?(y/n) Continue _ ' курсор будет находиться на этой строке, нужно будет ввести ответ в кавычках, например «n» и нажать Enter  => Ок.

Причем ответ можно писать не в полных кавычках («y»), а только с первой («y). Работает одинаково.

Вот такая имитация cli диалога. Не думаю, что кому-то пригодится, но кажется что штука забавная?

Далее, файл package.json.
Мы, как прогрессивные программисты, в VBIDE создавать файлы не можем, поэтому для этих целей будем использовать стандартный модуль и комментарии.

Выглядеть все должно примерно так:

такой прекрасный Code Explorer делает Rubberduck

такой прекрасный Code Explorer делает Rubberduck

Заметили последнюю строку? Ее на вкусное оставлю, затрону позже.

Тут, в принципе, все просто. Спарсили команду, создали (или нашли) модуль Package, записали полученную информацию.

Но.

В npm каждый импортируемый пакет идет со своим package.json. То есть таких файлов в проект импортируется ровно столько, сколько пакетов и их зависимостей будет импортировано.

А у нас что?

а у нас в проекте конфликт имен

а у нас в проекте конфликт имен

На этом все. Подписывайтесь на канал, ставьте лайки.

Первое решение, которое пришло мне в голову – лепить к названию модуля имя пакета. И думаете я придумал еще парочку и выбрал лучшее? Глупости какие.
В случае с примером будет CRegExpPackage.
Это имя для экспорта.

А для init, чтобы отделить основной пакет от импортных (шутка про импортозамещение), будем называть модуль ThisProjectPackage.
Даже нативно получается.

?Вторая задача – пакетик нужен?

Идем дальше по списку:

Находить и устанавливать пакеты (install)

Ох, как тут все неоднозначно.

Во-первых, где хранить пакеты?
Ну в текущей итерации, ForMyselfMode, вполне достаточно и локального хранилища.
Для того, чтобы первично задать путь к этому хранилищу, я решил сделать доп команду config в которой нужно будет прописать значение опции --rootpath – путь к папке со всеми пакетами.

структура папки с пакетами

структура папки с пакетами

Команда парсит путь и записывает его в переменную окружения PACK для текущего пользователя .

Во-вторых, а как быть с версиями?
И вот над этим я сейчас скрипящими и кипящими мозгами думаю.

Вот вам синтетический пример:

Есть пакет Четверочка, на который логотип наносит компания РисуемНаПакетах версии 1.0.0.
А еще есть пакет Магнезия, на который логотип наносит та же компания РисуемНаПакетах, но более поздней версии – 2.0.0.
Мне нужны в проекте оба этих пакета, но они за собой потянут компанию РисуемНаПакетах, и вот тут произойдет конфликт версий (придется брать версию 1.0.0 и 2.0.0).

В npm это решается очень просто – создается новая папка node_modules внутри папки загружаемого пакета и уже в нее импортируется нужная версия.

Внимание вопрос.

правильный ответ – никак и Rubberduck ситуацию не исправляет

правильный ответ – никак и Rubberduck ситуацию не исправляет

Если у вас есть мысли, как сделать совмещение версий в одном проекте, напишите коммент.
Вот тут правда, честно. Вообще не представляю.

И напоследок, в-третьих, при импорте пакета, информация о нем должна записаться в файлмодуль ThisProjectPackage, а именно в раздел '@dependencies, ну чтобы понятно было, какие пакеты используются в проекте. Более того, туда же нужно записать версию пакета, ну чтобы понятно было, какая версия пакета используется в проекте.

Выглядеть это должно примерно так:

заметили галочку возле версии? это еще одна головная боль :)

заметили галочку возле версии? это еще одна головная боль 🙂
Интересный факт (нет)

На текущей итерации у меня небольшой баг в этой команде – в зависимости основного проекта попадают пакеты и их зависимости (а должны только сами пакеты, т.к. их зависимости прописаны в их модуле Package), но это поправимо.

Не знаю зачем вам эта информация, просто решил рассказать. Вы же зачем-то аж сюда дочитали?

?Третья задача – пакет с пакетами

Публиковать пакеты в некое хранилище (publish)

Какие тут могут быть проблемы?

Jarvis, подержи мое пиво, ща все объясню

Jarvis, подержи мое пиво, ща все объясню

Берем модуль, сохраняем в папку. Готово.

Ну в целом то да, но как зависимости то не сохранять в папку с основным пакетом?
У нас же задача в чем? Настроить удобное управление пакетами, то есть их импорт, экспорт, изменение и т.д.

Вот чтобы изменение было легко производить, нужно отделить мух от котлетзависимости от основного кода.

А что, а как?
А Rubberduck! нам на что?

Раз уж я очень крепко прикипел к этому аддону, решил задействовать его в своей надстройке.
Для тех кто не в курсе:

Ну уж про Rubberduck вы должны были слышать! Так ведь?

слышали же?

слышали же?

В общем для своего Code Eplorer’а этот аддон добавляет в каждый модуль комментарии.
Один из таких комментариев – '@Folder.
Как следует из названия, он отображает информацию о том в какой папке лежит модуль (ну как папке… ну вы поняли).

Вот эту инфу мы и будем парсить.
Всего лишь нужно экспортируемый/публикуемый код поместить в папку src в корневом каталоге. ThisProjectPackage выносим в корень:

внимание на Code Explorer

внимание на Code Explorer

Всё, мухи отдельно, котлеты отдельно.

Еще одно обязательное условие, перед экспортом переименовать ThisProjectPackage в [NameOfProject]Package, а потом, и это не менее важно, вернуть название обратно.

Ну и, естественно, в нашем хранилище должна создаваться папка с названием пакета, в которой должна создаваться папка с номером версии, в которой должна создаваться папка src, в которую мы экспортируем весь проект + package модуль.

%PACK% -> CRegExp -> 1.0.0 -> src» title=»%PACK% -> CRegExp -> 1.0.0 -> src» width=»600″ height=»600″ data-src=»https://habrastorage.org/getpro/habr/upload_files/0eb/589/57c/0eb58957cc772ce0417aae6eefd3d846.png»/></p>
<div><figcaption>%PACK% -> CRegExp -> 1.0.0 -> src</figcaption></div>
</figure>
<p>Зачем src? На самом деле не зачем, просто раньше я выносил package отдельно от основного кода.<br />Возможно надо фиксить, но мне пока лень и вдруг в этом есть смысл?</p>
<p>Устали? Ща побыстрому пробежимся по последним пунктам.</p>
<h3>?Четвертая задача – у нас новые пакетики</h3>
<p>И вот вы уже почти уснули, а я начинаю предпоследнюю главу своего умопомрачительного повествования.</p>
<blockquote>
<p>Обновлять уже установленные пакеты (<code>update</code>)</p>
</blockquote>
<p>Тут, как обычно, все очень просто.</p>
<p>Парсим package модуль, ищем там нужный нам пакет, проверяем до какой версии можно обновить, обновляем.<br />Самая короткая задача, наверное. Поправьте меня, может я ошибаюсь?</p>
<h2>?Пятая задача – выкинь ты этот пакет</h2>
<blockquote>
<p>Удалять установленные пакеты из проекта (<code>uninstall</code>)</p>
</blockquote>
<p>А вот тут есть что сказать по сложностям. Одно слово – зависимости.<br />Нужно пройтись по всем установленным пакетам и их зависимостям. Еще раз, по <strong><u>всем</u></strong> пакетам и по <strong><u>всем</u></strong> их зависимостям, гуглим <a href=Dependency hell.

И че? И где?

Как вы поняли, ipm пока в разработке. Буквально вчерашняя ночь прошла в отладке трех команд из-за переименования модуля package. Но результат того стоит.
Сам проект я выложу в open source по завершению и отредактирую статью.

Если вам не понравилась статья, ни за что не подписывайтесь на мой телеграм. Не надо, правда. Ссылка еще в профиле есть.

А вы изобретаете велосипеды в VBA?


ссылка на оригинал статьи https://habr.com/ru/articles/726384/


Комментарии

Добавить комментарий

Ваш адрес email не будет опубликован. Обязательные поля помечены *