nanoCAD – аналог s::startup / acaddoc.lsp
Автор: Кулик Алексей aka kpblc | Дата: 15 Июль 2024 · Прокомментировать
На форумах и в телеграм-чатах время от времени проскальзывает вопрос "а как в нанике сделать автозагружаемый код в каждый документ? Ну типа s::startup в каде?" Попробую разобраться.
Это не развитие имеющейся статьи, а отдельная разработка. Хотя из предыдущей статейки чего-нибудь тоже тисну, скорее всего.
Памятуя, что лисп в нанике грузится в контекст приложения, а не документа, становится очевидным, что загрузка кода в _.appload и там в StartUp поможет только в первом открытом документе, дальше кода загружаться не будут. Единственный вариант, который я вижу в текущих реалиях - это использовать в лиспе реактор на создание документа, в NET - примерно те же Фаберже, вид сбоку: подписка на событие DocumentCreated.
Спойлер: репозитория не будет, его ценность примерно равна нулю по моим ощущениям.
Но! Прежде чем что-то делать, слегка подумаю. Приложений может быть овердофига: lsp, nrx, dll. .package затрагивать не хочу - больно много гемора там по парсингу xml (особенно в лиспе). Так что весь перечень загружаемых модулей после создания нового документа (или открытия существующего, не суть) засуну в отдельный файл. Файл с перечнем (предположительно) будет болтаться в %AppData%/Nanosoft/nanoCAD XX.Y/Config, имя файла - Startup.ini, кодировка Windows-1251. Потом попробую то же самое на NET нарисовать, если сильно лениво не будет
Структуру пока что я вижу такой:
1 2 3 4 5 6 7 8 9 10 11 | ; Общие модули, загружаемые при любом активном профиле / конфигурации [Common] c:\1\2\3.lsp c:\4\5.dll c:\6\7.nrx ; Загружаются в профиль / конфигурацию <<Default>> [<<Default>>] c:\1\a.lsp ; Аналогично, под SPDS [SPDS] c:\a\b.dll |
При этом считаю, что регистр неважен, и Common / common / COMMON одно и то же.
Спойлер: прописывать еще и автозагрузку в nanoCAD lsp/dll слишком длинно, так что априори считаю, что прописать файл в napp.ini не есть проблема.
Для начала – отдельная функция получения полного имени файла с перечнем загружаемых приложений:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | (vl-load-com) ;| Получает имя файла с перечислением загружаемых модулей @Returns Имя файла с перечислением загружаемых модулей |; (defun fun_get-startup-filename (/ folder) (strcat (vl-string-right-trim "\\" (strcat (vl-registry-read (strcat "HKEY_CURRENT_USER\\" (vl-string-trim "\\" (vlax-product-key)) ) "UserDataDir" ) "\\Config" ) ) "\\Startup.ini" ) ) |
Дальше можно попытаться прописать тупое чтение файла, но попытаюсь не забыть про группировку по профилям
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
;| Получает список загружаемых модулей. Пример вызова: (fun_get-startup-applications (fun_get-startup-filename)) @Param filename Имя файла с описанием/перечислением модулей @Returns Группированный список с приложениями по профилям (в т.ч. и общими) |; (defun fun_get-startup-applications (filename / handle str group_name res) (if (findfile filename) (progn (setq handle (open filename "r")) (while (setq str (read-line handle)) (setq str (vl-string-trim " " str)) (cond ((wcmatch str "`[*`]") (setq group_name (vl-string-trim "[]" str) res (cons (list group_name) res) ) ) ((and (not (wcmatch str ";*")) group_name ) (setq res (subst (cons group_name (append (cdr (assoc group_name res)) (list str) ) ) (assoc group_name res) res ) ) ) ) ) (close handle) res ) ) ) |
Так, какое-то количество кода нарисовалось, пока проверяться. Создаю тот самый файл Startup.ini, загружаю lsp, смотрю чего получилось:
1 2 | (fun_get-startup-applications (fun_get-startup-filename)) (("SPDS" "c:\\a\\b.dll") ("<<Default>>" "c:\\1\\a.lsp") ("Common" "c:\\1\\2\\3.lsp" "c:\\4\\5.dll" "c:\\6\\7.nrx")) |
Так, вроде бы не сильно врет, пора прописывать загрузку, не забывая про возможные ошибки загрузки:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 | ;| Загружает в текущий документ приложения указанной группы @Param app-list Список всех приложений. Фактически - результат выполнения fun_get-startup-applications @Param group-name Имя группы, для которой надо выполнять загрузку приложения |; (defun fun_load-modules (app-list group-name / sysvar err) (setq sysvar (vl-remove nil (mapcar (function (lambda (x / temp) (if (setq temp (getvar (car x))) (progn (setvar (car x) (cdr x)) (cons (car x) temp) ) ) ) ) '(("cmdecho" . 0) ("menuecho" . 0) ("nomutt" . 1) ) ) ) ) (foreach app (cdr (assoc (strcase group_name)) (mapcar (function (lambda (x) (cons (strcase x) (cdr x)) ) ) app-list ) ) (if (findfile app) (vl-catch-all-apply (function (lambda () (cond ((= (strcase (vl-filename-extension app)) ".LSP") (load app) ) ((= (strcase (vl-filename-extension app)) ".NRX") (arxload app) ) ((= (strcase (vl-filename-extension app)) ".DLL") (vl-cmdf "_.netload" app) ) ) ) ) ) ) ) (foreach item sysvar (setvar (car item) (cdr item)) ) ) |
Учитывая по меньшей мере странную реализацию vl-catch-all-apply в NC, предпочту пока не сообщать об ошибках. Себе дороже – по крайней мере на текущий момент.
Чисто для проверки в c:\1\2\3.lsp создаю элементарный код типа:
1 | (alert "start common!") |
И внутри nanoCAD загружаю основной код с вызовом (fun_load-modules-for-group-name (fun_load-modules-for-group-name (fun_get-startup-filename)) “Common”)
При таких раскладах прописать загрузку становится совсем уж элементарным:
1 2 3 4 5 6 7 8 9 10 11 12 | (setq config_name (strcase (if (= (getvar "cconfiguration") "") (getvar "cprofile") (getvar "cconfiguration") ) ) ini_file (fun_get-startup-filename) full_app_list (fun_get-startup-applications ini_file) ) (foreach group (list "common" config_name) (fun_load-modules full_app_list group) ) |
Просто засунуть все это надо в реактор создания документа. Все это было на убитом форуме наника, но теперь доверия к этому ресурсу примерно ноль-ноль и хрен вдоль. Очень хотелось привести текст сразу и весь, но выяснилось, что некоторые версии (а может, и сборки – хрен их там поймешь!) могут при прямом вызове только что определенной lisp-функции тупо не срабатывать. Поэтому в результирующем коде есть два вариант – один “нормальный”, с самовызовом, второй – “костыльный”. В моих условиях сработал именно “костыльный”.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 | ((lambda () (vl-load-com) (if *kpblc-vlr-docman* (progn (vlr-remove *kpblc-vlr-docman*) (setq *kpblc-vlr-docman* nil) ) ) (if (not *kpblc-vlr-docman*) (setq *kpblc-vlr-docman* (vlr-docmanager-reactor "kpblc-docman-reactor" '((:vlr-documentcreated . _kpblc-document-created)) ) ) ) (defun _kpblc-document-created (reactor cmd / fun_get-startup-filename fun_get-startup-applications fun_load-modules config_name ini_file full_app_list) ;| Получает имя файла с перечислением загружаемых модулей @Returns Имя файла с перечислением загружаемых модулей |; (defun fun_get-startup-filename (/ folder) (strcat "\\\\vmware-host\\Shared Folders\\Share\\2024--07-10" "\\Startup.ini" ) ) ;| Получает список загружаемых модулей. Пример вызова: (fun_get-startup-applications (fun_get-startup-filename)) @Param filename Имя файла с описанием/перечислением модулей @Returns Группированный список с приложениями по профилям (в т.ч. и общими) |; (defun fun_get-startup-applications (filename / handle str file_content group_name res) (if (findfile filename) (progn (setq handle (open filename "r")) (while (setq str (read-line handle)) (setq str (vl-string-trim " " str)) (cond ((wcmatch str "<code>[*</code>]") (setq group_name (vl-string-trim "[]" str) res (cons (list group_name) res) ) ) ((and (not (wcmatch str ";*")) group_name ) (setq res (subst (cons group_name (append (cdr (assoc group_name res)) (list str) ) ) (assoc group_name res) res ) ) ) ) ) (close handle) res ) ) ) ;| Загружает в текущий документ приложения указанной группы @Param app-list Список всех приложений. Фактически - результат выполнения fun_get-startup-applications @Param group-name Имя группы, для которой надо выполнять загрузку приложения |; (defun fun_load-modules (app-list group-name / sysvar err) (setq sysvar (vl-remove nil (mapcar (function (lambda (x / temp) (if (setq temp (getvar (car x))) (progn (setvar (car x) (cdr x)) (cons (car x) temp) ) ) ) ) '(("cmdecho" . 0) ("menuecho" . 0) ("nomutt" . 1) ) ) ) ) (foreach app (cdr (assoc (strcase group-name) (mapcar (function (lambda (x) (cons (strcase (car x)) (cdr x)) ) ) app-list ) ) ) (if (findfile app) (vl-catch-all-apply (function (lambda () (cond ((= (strcase (vl-filename-extension app)) ".LSP") (load app) ) ((= (strcase (vl-filename-extension app)) ".NRX") (arxload app) ) ((= (strcase (vl-filename-extension app)) ".DLL") (vl-cmdf "_.netload" app) ) ) ) ) ) (princ (strcat "\nНе найдено приложение" app)) ) ) (foreach item sysvar (setvar (car item) (cdr item)) ) ) (setq config_name (strcase (if (member (getvar "cconfiguration") (list nil "")) (getvar "cprofile") (getvar "cconfiguration") ) ) ini_file (fun_get-startup-filename) full_app_list (fun_get-startup-applications ini_file) ) (foreach group (list "common" config_name) (fun_load-modules full_app_list group) ) ) ; (_kpblc-document-created nil nil) (command "(_kpblc-document-created nil nil) ") (princ) ) ) |
Чего получилось – болтается тута.
Я понимаю, что любой профи после этого текста из меня сделает игрушку для битья, ну да ладно
Все как всегда – создаю NET-проект, библиотека классов, под NET5 (поскольку NC23 и более поздний). Имя проекта – NCadStartApp.
Собственно чтение перечня приложений выкину в отдельную сборку, написанную на NetStandard 2.0 для возможности корректного использования в проекта на NET Framework. Имя – NCadStartApp.Base. Вот захотелось мне так
Библиотека будет только читать ini-файл, возвращая перечень групп с приложениями
И теперь прописываю читалку ini-файла. Вменяемого и простого в применении NuGet-пакета я не нашел по-быстрому, так что изобретаю велосипед. Усложнять не буду – тупо буду возвращать словарь:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 | using System.Collections.Generic; using System.IO; using System.Text; using System.Text.RegularExpressions; namespace NCadStartApp.Base { public class StartAppReader { public Dictionary<string, List<string>> GetGroupedApps(string IniFileName) { Dictionary<string, List<string>> groupedAppNames = new Dictionary<string, List<string>>(); string key = string.Empty; using (StreamReader reader = new StreamReader(IniFileName, Encoding.GetEncoding("windows-1251"))) { string line; while ((line = reader.ReadLine()) != null) { if (line.StartsWith(";")) { continue; } if (Regex.IsMatch(line, @"\[(.*)\]")) { key = line.Trim(new[] { ' ', '[', ']' }); if (!groupedAppNames.ContainsKey(key)) { groupedAppNames.Add(key, new List<string>()); } } if (!string.IsNullOrEmpty(key)) { groupedAppNames[key].Add(line.Trim()); } } } return groupedAppNames; } } } |
Ну и собственно стартер всего этого богатства:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 | using HostMgd.ApplicationServices; using HostMgd.EditorInput; using Microsoft.Win32; using NCadStartApp.Base; using Teigha.DatabaseServices; using Teigha.Runtime; namespace NCadStartApp { public class AppExtension : IExtensionApplication { public void Initialize() { Application.DocumentManager.DocumentCreated += OnDocumentCreated; OnDocumentCreated(null, null); } private void OnDocumentCreated(object sender, DocumentCollectionEventArgs e) { string regKeyName = HostApplicationServices.Current.UserRegistryProductRootKey; RegistryKey key = Registry.CurrentUser.OpenSubKey(regKeyName); string defaultIniFile = Path.Combine(key.GetValue("UserDataDir").ToString(), "Config\\Startup.ini"); string profileName = Application.GetSystemVariable("cconfiguration").ToString(); if (string.IsNullOrWhiteSpace(profileName)) { profileName = Application.GetSystemVariable("cprofile").ToString(); } Document doc = Application.DocumentManager.MdiActiveDocument; Editor ed = doc.Editor; if (!File.Exists(defaultIniFile)) { ed.WriteMessage("\nФайл с перечнем загружаемых приложений не найден"); return; } StartAppReader reader = new StartAppReader(); foreach (KeyValuePair<string, List<string>> keyValuePair in reader.GetGroupedApps(defaultIniFile) .Where(o => o.Key.Equals("common", StringComparison.InvariantCultureIgnoreCase) || o.Key.Equals(profileName, StringComparison.InvariantCultureIgnoreCase) ) ) { foreach (string appFilePath in keyValuePair.Value) { if (!File.Exists(appFilePath)) { ed.WriteMessage($"\nПриложение {appFilePath} не найдено"); continue; } string extension = Path.GetExtension(appFilePath).ToUpper(); string path = appFilePath.Replace(@"", @"\"); if (extension == ".LSP") { doc.SendStringToExecute($"(load "{path}") ", true, true, false); } else if (extension == ".NRX") { doc.SendStringToExecute($"(arxload "{path}") ", true, true, false); } else if (extension == ".DLL") { doc.SendStringToExecute($"_.netload "{path}" ", true, true, false); } } } } public void Terminate() { } } } |
Написано кривенько и косенько, апробировано только на лиспах. Так что репозитория не будет