Делаю Package Manager для VBA

в 22:23, , рубрики: package manager, vba, visual basic for applications, Программирование, Читальный зал

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

лицо человека, который узнает что кто-то пишет код на 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)

Ну это база.

Делаю Package Manager для VBA^,^< " title="подпись к изображению я не придумал, поэтому вот вам котик >^,^< " width="600" height="600" data-src="https://habrastorage.org/getpro/habr/upload_files/95e/a04/615/95ea04615662247a550c746a283591fe.png"/>

подпись к изображению я не придумал, поэтому вот вам котик >^,^<

А зачем козе баян?

Нет, а действительно, зачем? Разве макросы настолько большие, что туда постоянно нужно что-то импортировать?

Да. Причем на моей практике таких макросов много. И так как VBAшники работают в современных IDE, а сам VBA – современный язык программирования, для которого как крупные компании, вроде google, так и энтузиасты пишут кучу разных пакетов/библиотек/фреймворков, ими нужно как-то управлять.

...

Ладно, на самом деле всё чуточку плачевней.

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

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

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

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

Делаю Package Manager для VBA - 6

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

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

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

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

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

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

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

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

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

Правильно – с названия. Я потратил на то, чтобы придумать название менеджера пакета для 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 модуль.

Делаю Package Manager для VBA 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"/>

%PACK% -> CRegExp -> 1.0.0 -> src

Зачем src? На самом деле не зачем, просто раньше я выносил package отдельно от основного кода.
Возможно надо фиксить, но мне пока лень и вдруг в этом есть смысл?

Устали? Ща побыстрому пробежимся по последним пунктам.

🔴Четвертая задача – у нас новые пакетики

И вот вы уже почти уснули, а я начинаю предпоследнюю главу своего умопомрачительного повествования.

Обновлять уже установленные пакеты (update)

Тут, как обычно, все очень просто.

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

🔴Пятая задача – выкинь ты этот пакет

Удалять установленные пакеты из проекта (uninstall)

А вот тут есть что сказать по сложностям. Одно слово – зависимости.
Нужно пройтись по всем установленным пакетам и их зависимостям. Еще раз, по всем пакетам и по всем их зависимостям, гуглим Dependency hell.

И че? И где?

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

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

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

Автор:
ArtCapCorn

Источник

* - обязательные к заполнению поля


https://ajax.googleapis.com/ajax/libs/jquery/3.4.1/jquery.min.js