JScript — механизм каскадного импорта скриптов (библиотек)

в 12:07, , рубрики: javascript, jscript, автоматизация, администрирование windows, Песочница, метки: , , ,

Всем доброго времени суток, уважаемые читатели.
Те, кто сталкивался с написанием скриптов автоматизации в Windows на языках JScript и VBScript наверняка знают о том, что очевидного способа подключения других скриптов в исполняемый — «задачка та еще», возможности же подключать их каскадом, т.е. подключать скрипты, которые в свою очередь сами подключают другие скрипты, не предусмотрено вовсе.
Я же расскажу о том, как я преодолел это обстоятельство в отношении скриптов написанных на JScript, при этом побаловал себя «плюшками» в виде возможности делать это по абсолютному и относительному пути (в том числе и из подключаемых скриптов), «сингулярностью загрузки скрипта» т.е. механизмом гарантирующим, что один и тот же скрипт подгрузится лишь единожды.
Написать решил по причине того, что некоторые коллеги посчитали мое решение довольно интересным, начали им пользоваться, возможно оно покажется приемлемым и Вам.
Заинтересовавшихся прошу под кат.

Позволю себе небольшое предисловие, а для тех кто и так понял о чем собственно речь, или не любит разглагольствование — милости прошу к абзацу «Продумываем идею»

«Велосипедостроитель» скажите Вы

И действительно, то о чем я намереваюсь рассказать, это не что иное, как очередной «велосипед», «Тогда зачем?» — спросите Вы.
Во-первых, велосипед велосипедом, а такого функционала «из коробки» всё-же нет.
Маловато аргументов?
Прежде чем придти к решению «Легче написать самому!(и по столу так Хрясь!)» я естественно потратил не один час на попытку освоения «изкорбочного» арсенала, поинтересовался у системных администраторов, как с такой проблемой справляются они. Оказалось, что они ни как не справляются — нет значит нет. Повторяющийся функционал копируется из скрипта в скрипт. Боле не менее сложные скрипты, этак строк в 150-200, в которых около половины — объявления повсеместно использующихся сущностей (COM/OLE/ActiveX объекты), и обыденная обработка ошибок, аля «открытие несуществующего файла». Из скрипта в скрипт, я проверял, даже попросил у товарища аудиенции с его системными администраторами, там все собственно говоря то же самое ( у «них» превалирует все-же VBScript, где эта проблема так же не решается из коробки, или PowerShell)

О чем собственно разговор?

Про автоматизацию в среде Microsoft Windows через написание скриптов на специфической реализации стандарта ECMA-262 — языке JScript (который такой же как JavaScript, но другой).
И в частности, про возможность из таких скриптов создавать библиотеки, которые в последствии можно будет легко подключить, как впрочем и любой другой скрипт написанный на JScript.

Почему JScript, а не PowerShell, CMD/BAT, VBScript и т.д. и т.п. ?

Перечисленные в заголовке абзаца альтернативы требуют изучения новой технологии (с узкой областью применимости). JScript же снимает с наших плеч важный пункт изучения такой технологии — знание языка программирования. Позволю себе утверждать, что JavaScript, в том или ином виде, очень часто является языком с которым программист ранее сталкивался. Я был не исключением.

А как же WSF ?!

Да, такая технологии есть в нашем «изкоробочном» арсенале, но:
Во-первых, при подключении через тег script в файлах формата WSF, имеется возможность подключить скрипт только на один уровень, т.е. в подключаемом скрипте что либо еще подключить уже не получится. Подключать друг друга, судя по всему, не имеется возможности (по крайней мере, очевидного пути точно нет).
Во-вторых, файл формата WSF требует описания в соответствии с самобытным подмножеством XML, что требует освоения еще одной технологии применимость которой специфична до безобразия. (хотя основная причина — см.«во-первых»)

Продумываем идею

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

Очевидные грабли

Как вы наверное уже догадались, кроме как через богомерзкий eval() ничего у нас не выйдет.
И более того, нас интересует не просто eval(), а eval() как метод глобального объекта. В JScript не существует иной возможности выполнить код через eval() так, чтобы объявленные в нем сущности принадлежали к глобальному объекту, кроме как вызов его из основного стека. В противном случае например при вызове из функции все что мы вызовем, умрет вместе со [scope] объекта функции, да и будет доступно только в ней.
Помимо команд подключения нам потребуется финальная команда инициализации.

Ну вот собственно и код

Дабы не плодить глобальных объектов, заключим весь механизм в единственный объект jsImport.
Механиз состоит из 2-х частей:
jsImport.include(TargetScript); где TargetScript это абсолютный или относительный путь к подключаемому скрипту.
*через вызов данного метода производится непосредственная загрузка исходных кодов скрипта (или каскада скриптов),
jsImport.initialization
*данное свойство необходимо передать в eval() глобального объекта, для инициализации загруженных через jsImport.include() скриптов.
Проще всего понять о чем, и для чего на конкретном примере, выполнив mainScript.js из каталога MainDir
Листинг (без комментариев):

var jsImport = new Object(); 
jsImport.initialization = "";
jsImport.include = function (TargetScript, ParentDir){
  jsImport.fso = jsImport.fso || new ActiveXObject("Scripting.FileSystemObject");
  var includedLibs = jsImport.includedLibs = jsImport.includedLibs || new Array();   
  var ParentDir = ParentDir || WScript.ScriptFullName.replace(/[^\]+$/, ''); 
  PathValidation(ParentDir);    
    
    for (i in includedLibs){
      if(i == TargetScript){jsImport.include.AlreadyLoaded = true};
    };    
    
    
    if(typeof jsImport.include.AlreadyLoaded == "undefined" || jsImport.include.AlreadyLoaded !== true){
     if(jsImport.fso.FileExists(TargetScript)){    
               
        var SourseBuffer = jsImport.fso.OpenTextFile(TargetScript).ReadAll(); 
        
        var includeArray = includeParser(); 
        
        jsImport.initialization = jsImport.initialization + SourseBuffer;  
        
          if(includeArray.length != 0){          
            for(i in includeArray){
                jsImport.include(includeArray[i].replace(/\\/, "\"), TargetScript.replace(/[^\]+$/,""));
             };
          };  
                  
        jsImport.includedLibs.push(TargetScript); AlreadyLoaded = false;
    } else {
      
      WScript.Echo("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(ParentDir){    
      if(TargetScript.charAt(1) !== ":"){
        var dot = TargetScript.charAt(2);
        if(dot == "."){
          var OutsidePath = ParentDir, counter= 1;               
            while(dot == "."){
              counter++;
              dot = TargetScript.charAt(counter);
              OutsidePath = StepOutside(OutsidePath);
            };     
          TargetScript = OutsidePath + TargetScript.substring(counter);
          dot = "undefined"; counter = "undefined"; OutsidePath = "undefined";
        } else {
          if(TargetScript.substring(0,2) == ".."){TargetScript = TargetScript.substring(2)};
          if(TargetScript.substring(0,2) !== "\\"){TargetScript = "\\" + TargetScript};
          TargetScript = ParentDir + TargetScript.substring(1);
        };
      };
      
      function StepOutside(outside){
        outside = outside.replace(/\\/, "") 
        return outside.substring(0, outside.lastIndexOf("\"));
      };  
    };        
  };
};

 
jsImport.include('\EmbeddedDir\EmbeddedScript.js')
jsImport.include("SecondIncludeTest.js")
eval(jsImport.initialization)
 
SecondIncludeFunction()
OutsideFunction()
EmbeddedFunction()
WScript.Echo(Embedded.Constant)
WScript.Echo(Embedded3)

Подключив этот механизм к исполняемому скрипту, можно вдоволь «наподключать» себе всего, что хочется в виде отдельной библиотеки, которой возможно потребуется что либо еще для своей работы и т.д.
Осозновая, что с точки зрения JavaScript-кода, кое-что можно сделать лаконичнее и оптимальнее, надеюсь коллективный хабраразум подскажет и направит. Аминь!
PS: Лишь бы кому ни будь пригодилось.

Автор: RUVATA

  1. RUVATA:

    подготовлена новая редакция, с рядом исправлений, изменений, новым примеров и “какой, ни какой” справкой
    http://habrahabr.ru/post/147850/

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


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