Всем доброго времени суток, уважаемые читатели.
Те, кто сталкивался с написанием скриптов автоматизации в Windows на языках JScript и VBScript наверняка знают о том, что очевидного способа подключения других скриптов в исполняемый — «задачка та еще», возможности же подключать их каскадом, т.е. подключать скрипты, которые в свою очередь сами подключают другие скрипты — не предусмотрено вовсе.
Я же расскажу о том, как я преодолел это обстоятельство путем разработки механизма каскадного импорта JScriptInclude Gear.
С момента первой публикации, произошло много полезных и концептуальных изменений, код частично упрощен и переработан, предложены новые решения и функциональность.
— Подготовлен новый пример использования. Теперь он не только демонстрирует непосредственно возможность импорта, но и представляет частичную реализацию предлагаемой модели стандартных модулей.
Внимание! Перед запуском примера обязательно ознакомитесь с разделом Example файла справки JScriptInclude.chm находящегося в архиве с примером.
— На скорую руку, и все же, подготовлен файл справки JScriptInclude.chm. В данной справке, более детально, описывается работа с механизмом, функционал, синтаксис, не очевидные моменты. В предоставляемом примере, перед непосредственным запуском, требуется совершение нескольких предварительных действий, касающихся указания действительных абсолютных путей, все необходимые инструкции изложены в разделе Example данного файла справки.
PS: Как уже упоминалось, справка довольно «сыровата», дефицит свободного времени, тем не менее она вполне информативна, в случае если моим решением заинтересуются пользователи, обязательно будет переработана.
Заинтересовавшихся прошу под кат…
Позволю себе процитировать пункт Введение, изложенного в прилагаемом справочном пособии, его содержание достаточно понятно излагает идею и возможности реализации.
И так — Введение
JScriptInclude Gear — это механизм каскадного импорта скриптов/библиотек предназначенный для использования в скриптах автоматизации написанных на языке JScript интерпретируемых технологией WSH(Windows Script Host) в окружении семейства операционных систем Windows.
Назначение JScriptIncludeGear
– Восполнить отсутствие в технологии возможности подключать скрипты/библиотеки как таковые. Технология предлагает частично разрешить этот вопрос при помощи оборачивания кода в XML формат специфического файла WSF(WindowsScriptFile), но его использование не позволяет обеспечить «каскад подключений». Из-за этого обстоятельства, невозможно сформировать структуру библиотек имеющих свои зависимости. В данном решении, Вам предлагается разрешить эту проблему через обеспечение каскадного импорта.
Каскадный импорт, это возможность заслуживающая особого внимания — ее принцип позаимствован автором из технологии Node.JS. В такой модели множество файлов JavaScript-кода составляют гибкую и лаконичную структуру модулей и их зависимостей друг от друга.
В Node.JS такой механизм предоставляется ядром технологии и ей же обеспечивается контроль, в случае с JScriptInclude Gear механизм естественно является имитацией, и реализован средствами языка JScript.
Возможности JScriptInclude Gear:
1) Подключение произвольного скрипта – предоставляется возможность простой конструкцией
jsImport.include(targetScript)
подключить другой скрипт к исполняемому, исполнить его код, или получить доступ к объявленным в нем сущностям. Это позволяет многократно использовать имеющиеся наработки и готовые решения в своей работе.
2) Каскадный импорт – предоставляется возможность подключать скрипты/библиотеки, которые, в свою очередь, так же имеют возможность указать на необходимость подключения других скриптов/библиотек. Такое указание реализуется через содержание такой же инструкции подключения
jsImport.include(targetScript)
, т.е. такой же как той, при помощи которой они сами были подключены.
В общем виде подключение происходит по принципу нисходящего рекурсивного вызова процедуры импорта. В данном механизме применимо использование абсолютных и относительных путей при определении зависимости. Стоит акцентировать внимание на том, что относительные пути, при объявлении зависимости, рассматриваться исходя из местоположения скрипта в котором эта зависимость объявлена, т.е. в подключаемом скрипте — относительно этого самого подключаемого скрипта, а не относительно скрипта инициатора (скрипта в котором объявлен механизм, и производится первый импорт в каскаде).
Предусмотрена возможность формирования конфигурации стандартных библиотек/модулей с заранее известным местоположением, такие модули можно подключать просто по имени файла исходного кода или псевдониму, на любом уровне каскадного импорта.
3) Сингулярность подключения – Данный механизм, в процессе импорта, отслеживает уже подключенные файлы, и не допускает их повторной инициализации, даже если ссылка на один и тот же скрипт/библиотеку присутствует во множестве зависимостей по пути каскадного ветвления.
4) Обеспечение модели стандартных модулей – Данный механизм предусматривает ситуацию, в которой определенные скрипты/библиотеки стандартизированы или универсальны и предполагаются присутствующими в системе.
Такой подход реализуется путем определения таких скритов/библиотек специальной инструкцией
jsImport.defineBaseModule(moduleName, modulePath)
, или компиляции таких инструкций в отдельном файле конфигурации и последующей его загрузки в виде
jsImport.getConfig(configFilePath)
.
Предусмотрена возможность ссылаться на абстрактную библиотеку, предполагая, что она должна быть описана в конфигурации, просто по имени файла ее исходного кода или по её псевдониму. Механизм самостоятельно определит такое поведение и обеспечит подключение необходимой библиотеки из стандартного местоположения.
5) Кэширование импортированного кода, с целью отладки или повторного использования – В процессе разработки данного механизма, неоднократно вставал вопрос касающийся отладки импортируемого кода. В связи с тем, что инициализируется он через богомерзкий eval(), то получить корректное сообщение об ошибке в загруженном коде не представляется возможным — ссылка на строку кода вызвавшую исключение является неинформативной относительно Вашего скрипта. Для разрешения подобной проблемы предусмотрено кэширование импортируемого кода, т.е. его материализация через запись в отдельный файл скрипта cashFile.js (в случае если Вы заранее определили специальное свойство
jsImport.WriteCash = true
).
В последствии через его запуск возможно получить корректное сообщение об ошибке.
PS: Файл кэша возможно использовать как скомпилированное решение, т.е. в последствии использовать его вместо новой процедуры импорта, в таком случае не ясна цель использования JSscriptInclude Gear, хотя автору видится, что такое решение возможно пригодится, хотя бы по средствам упрощения сборки кода из множества файлов.
Заключительное слово
В первую очередь автор заинтересован в полезности и применимости данного решения другими пользователями. Прошу Вас принять участие в обсуждении, огласить замечания и предложения.
Основной вопрос по прежнему: «Нужно ли такое решение ?»
(на данный момент достоверно известно лишь о 10-15 пользователях этого решения, при этом крайне мало каких либо отзывов, что не позволяет автору определиться с необходимостью дальнейшего развития в сторону универсальности и документированности).
Непосредственно код механизма
Данный код необходимо разместить в начале Вашего скрипта.
var jsImport = new Object();
jsImport.initialization = "";
jsImport.WriteCash = false;
jsImport.modulesByDefault = new Object();
jsImport.defineBaseModule= function(moduleName, modulePath){
jsImport.modulesByDefault[moduleName] = new Object();
jsImport.modulesByDefault[moduleName]["path"] = modulePath;
}
jsImport.getConfig = function(ConfFile){
jsImport.fso = jsImport.fso || new ActiveXObject("Scripting.FileSystemObject");
try{
var SourseConf = jsImport.fso.OpenTextFile(ConfFile).ReadAll();
eval(SourseConf);
}catch(err){
WScript.Echo("Error(jsImprot.getConfig): Unable to load the configuration file")
}
}
jsImport.include = function (TargetScript, ParentDir){
jsImport.fso = jsImport.fso || new ActiveXObject("Scripting.FileSystemObject");
if(jsImport.WriteCash == true){jsImport.cash = jsImport.cash || jsImport.fso.OpenTextFile("cashFile.js", 2, true);};
var includedLibs = jsImport.includedLibs = jsImport.includedLibs || new Array();
var ParentDir = ParentDir || jsImport.spoofedParentDir || WScript.ScriptFullName.replace(/[^\]+$/, '');
var TargetScriptName = TargetScript.substring(TargetScript.lastIndexOf("\")+1,TargetScript.length - 3);
TargetScript = PathValidation(TargetScript, ParentDir);
jsImport.include.AlreadyLoaded = false;
for (i in includedLibs){
if(includedLibs[i] == TargetScriptName){jsImport.include.AlreadyLoaded = true};
};
if(typeof jsImport.include.AlreadyLoaded == "undefined" || jsImport.include.AlreadyLoaded !== true){
try{
var SourseBuffer = jsImport.fso.OpenTextFile(TargetScript).ReadAll();
var includeArray = includeParser();
jsImport.initialization = jsImport.initialization + SourseBuffer;
jsImport.cash.Write(SourseBuffer);
if(includeArray.length != 0){
for(i in includeArray){
jsImport.include(includeArray[i].replace(/\+/g, "\"), TargetScript.replace(/[^\]+$/,""));
};
};
jsImport.includedLibs.push(TargetScriptName); AlreadyLoaded = false;
}catch(err){
WScript.Echo("Error(jsImprot.include): Target " + TargetScript + " not found");
};
function includeParser(){
var pattern = /jsImport.include(["'](.*?)["'])/g;
var result;
var includeArray = [];
while ((result = pattern.exec(SourseBuffer)) != null) {
includeArray.push(result[1]);
SourseBuffer = SourseBuffer.replace(result[0],"");
pattern.lastIndex = 0;
};
return includeArray;
};
function PathValidation(TargetScript, ParentDir){
if(!(TargetScript.charAt(1) == ":" || TargetScript.charAt(1) == "\")){
var dot = TargetScript.charAt(1);
if(dot == "."){
var OutsidePath = ParentDir, counter= 0;
while(dot == "."){
counter++;
dot = TargetScript.charAt(counter);
OutsidePath = StepOutside(OutsidePath);
};
TargetScript = (OutsidePath + TargetScript.substring(counter)).replace(/\+/g, "\") ;
dot = "undefined"; counter = "undefined"; OutsidePath = "undefined";
} else {
if(TargetScript.substring(0,1) == "."){TargetScript = TargetScript.substring(1)};
if(TargetScript.substring(0,2) !== "\\"){TargetScript = "\\" + TargetScript};
TargetScript = (ParentDir + TargetScript.substring(1)).replace(/\+/g, "\");
};
};
if( ! jsImport.fso.FileExists(TargetScript)){
if(TargetScript.substring(TargetScript.length - 3, TargetScript.length) == ".js"){TargetScript = TargetScript.substring(0, TargetScript.length - 3)}
var TagertModule = TargetScript.substring(TargetScript.lastIndexOf("\")+1,TargetScript.length)
for(module in jsImport.modulesByDefault){
if (module == TagertModule){
TargetScript = jsImport.modulesByDefault[module].path;
break;
};
};
};
return TargetScript;
function StepOutside(outside){
return outside.substring(0, outside.lastIndexOf("\"));
};
};
};
};
Синтаксис и особенности использования подробно раскрыты в справочном руководстве.
Краткое описание основного функционала
Данный код позволяет Вам использовать инструкцию
jsImport.include("somescript")
производящей загрузку исходного кода подключаемого скрипта, а так же исходного кода по всем объявленным зависимостям.
В связи с особенностями реализации, вызванного наличием единственного в JScript способа исполнить код в окружении глобального объекта, загруженный код необходимо явно инициализировать через eval() в верхнем стеке, в контексте глобального объекта.
(подробнее в справочном пособии, пункт «Особенности реализации»)
eval(jsImport.initialization)
Еще раз ссылочка на пример.
Внимание! Перед запуском примера обязательно ознакомитесь с разделом Example файла справки JScriptInclude.chm находящегося в архиве с примером.
Автор: RUVATA