JScriptInclude Gear — механизм каcкадного импорта скриптов/библиотек

в 21:19, , рубрики: javascript, jscript, windows, wsh, системное администрирование, метки: , , ,

Всем доброго времени суток, уважаемые читатели.
Те, кто сталкивался с написанием скриптов автоматизации в 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

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