Now I decided to publish this script here.
Dozen years ago I realized JSCmd.wsf, then extended it and renamed to wscmd.bat (the bat+js hybrid). The curent project called wsx.bat is their successor. Their main goal is to enable REPL and support some useful features like require(...) and console.log(...) existing in NodeJS (the famous and very popular JS engine), Rhino (Java-based implementation) and Chakra (another MS JS engine). Of course, you can say that I reinvented the wheels and you will be right. Nevetheless, due to absence of ability to run something simple as a one line program natively inspired me to continue activity in this direction.
FEATURES
As I said above the tool allows to run it interactively, write short one line programs without necessity to create temporary files and do something functional and flexible within one command. Internally it runs JS, which parses JS and/or VBS and evals. You can find it as a alternative to jrepl.bat, the tool popular here.
To the moment of announcing the script can to the following:
-- run external scripts
-- inject other external scripts with require(...) and variables with /let:..., /set:..., /get:...
-- run in the interactive mode
-- run one line programs
In the interactive mode it evaluates immediately commands reading them from STDIN. To enable multiple lines enter the double colon ::, enter commands and complete multiline mode with the second double colon. Everything entered between them will be evaluated as one set of commands.
TODO LIST
In the future I would like to implement the following features:
-- file globbing
-- argument file as a list of arguments
-- REPL for VBS
-- "compiling" to standalone JS/VBS/WSF/BAT files
-- most probably something more
EXAMPLES
Let me show some examples of the tool usage:
Count the number of lines (similar to wc -l, the unix tool)::
Code: Select all
rem as JS code
wsx /n /endfile:"echo(FLN, FILE)" /end:"echo(LN)" ...
rem as VBS code
wsx /n /endfile:vbs:"echo FLN, FILE" /end:vbs:"echo LN" ...
Code: Select all
rem as JS code
wsx /p /e:"LINE = LN + ':' + LINE" ...
rem as VBS code
wsx /let:delim=":" /p /e:vbs:"LINE = LN & delim & LINE" ...
Code: Select all
rem as JS code
wsx /let:limit=10 /p /e:"LN > limit && quit()"
rem as VBS code
wsx /use:vbs /let:limit=10 /p /e:"if LN > limit then exit : end if"
Code: Select all
wsx /n /e:"LINE.match(/^#/) && echo(LINE)" ...
wsx /p /e:"LINE.match(/^#/) || next()" ...
Right now the project is hosted on github at https://github.com/ildar-shaimordanov/jsxt. There are original scripts there stored in the separate files.
To build the fully standalone tool from the sources you need to run the following command:
Code: Select all
cscript tools\compile-wsf2bat.js < wsx.wsf > wsx.bat
Code: Select all
cmdize wsx.wsf
Code: Select all
@echo off
cscript //nologo wsx.wsf %*
STANDALONE VERSION
Code: Select all
<?xml :
: version="1.0" encoding="utf-8" ?><!--
@echo off
cscript //nologo "%~f0?.wsf" %*
goto :EOF
: -->
<package>
<job id="wsx">
<?job error="false" debug="false" ?>
<script language="javascript"><![CDATA[
var NAME = 'WSX';
var VERSION = '1.0.1 Alpha';
]]></script>
<runtime>
<description><![CDATA[
WSX: Version 1.0.1 Alpha
Copyright (C) 2009-2015, 2019, 2020 Ildar Shaimordanov
Run an external script file in the same way as it can be done traditionally via "cscript" or "wscript" with additional benefits making its usage closer to NodeJS, Perl, Python etc.
Run itself in the interactive mode. Type in the prompt any JS or VBS commands and execute them immediately. In this mode each entered line is evaluated immediately. To enable many lines executed as one you need to surround them with the double colons "::". The first double colon turns on the multiline mode, the second one turns it off. After that everything entered will be executed.
Run one line program from CLI and apply it on inputstream and other files. One line programs allow to estimate some code on the fly, without creating a temporary file. Writing one line programs you focus on the most important parts of the program implementation. Some implementation stuff -- like objects initialization, I/O operations etc -- are hidden on your eyes, however executed yet implicitly.
If the tool is launched with the one line program, everything after is assumed as a file name. Each argument is opened as a file and processed line by line until the end of file. Otherwise, if no any one line program is entered, the first item of the argument list is the script file and the rest of arguments are arguments for the script file. They could be everything and the script can use them accordingly its functionality.
For more convenience there are few predefined global definitions:
Common objects:
FSO - The object "Scripting.FileSystemObject"
STDIN - The reference to "WScript.StdIn"
STDOUT - The reference to "WScript.StdOut"
STDERR - The reference to "WScript.StdErr"
Common helper functions:
usage(), help() - Display this help
echo(), print(), alert() - Print expressions
quit(), exit() - Quit this shell
cmd(), shell() - Run a command or DOS-session
sleep(n) - Sleep n milliseconds
clip() - Read from or write to clipboard
gc() - Run the JScript garbage collector
ERROR - The variable keeping the last error
USE - The instance of "Importer" class to import VBS easier
ARGV - The CLI arguments
Used in the loop mode:
STREAM - The reference to the stream of the current file
FILE - The name of the current file
FILEFMT - The format to open files ("ascii", "unicode" or system "default")
LINE - The current line
FLN - The line number in the current file
LN - The total line number
These special functions can be used on the loop mode only to cover the issue when we can't use "continue" and "break".
next() - The "continue" operator
last() - The "break" operator
Used in REPL:
The interactive mode provides the following useful properties for referencing to the history of the commands and REPL mode:
REPL.number - the current line number
REPL.history - the list of all commands entered in the current session
REPL.quiet - the current session mode
The CLI options supplying the program parts for execution could be infixed with the engine identifier ("js" or "vbs") supposed to be used for processing these options. See examples below.
The name explanation:
Following the old good tradition to explain acronyms recursively "WSX" means "WSX Simulates eXecutable".
]]></description>
<example><![CDATA[
Examples:
- Run interactively:
wsx
- Count the number of lines (similar to "wc -l", the unix tool):
wsx /n /endfile:"echo(FLN, FILE)" /end:"echo(LN)"
wsx /n /endfile:vbs:"echo FLN, FILE" /end:vbs:"echo LN"
wsx /use:vbs /n /endfile:"echo FLN, FILE" /end:"echo LN"
- Numerate lines of each input file (VScript example shows how to bypass the trouble with quotes within quotes):
wsx /p /e:"LINE = LN + ':' + LINE"
wsx /let:delim=":" /p /e:vbs:"LINE = LN & delim & LINE"
- Print first 10 lines (similar to "head", the unix tool):
wsx /let:limit=10 /p /e:"LN > limit && quit()"
wsx /use:vbs /let:limit=10 /p /e:"if LN > limit then exit : end if"
- Print last 10 lines (similar to "tail", the unix tool):
wsx /let:limit=10 /n /beginfile:"lines=[]" /e:"lines.push(LINE); lines.length > limit && lines.shift()" /endfile:"echo(lines.join('\n'))"
]]></example>
<named
name="help"
helpstring="Print this help and exit ("/h" shortcut)"
type="simple"
required="false"
/>
<named
name="version"
helpstring="Print version information and exit"
type="simple"
required="false"
/>
<named
name="dry-run"
helpstring="Show in pseudocode what is going to be executed"
type="simple"
required="false"
/>
<!--
<named
name="compile"
helpstring="Compile and store to another file without execution"
type="simple"
required="false"
/>
-->
<named
name="quiet"
helpstring="Be quiet in the interactive mode ("/q" shortcut)"
type="simple"
required="false"
/>
<named
name="use"
helpstring="Use the engine ("js" or "vbs")"
type="string"
required="false"
/>
<named
name="m"
helpstring="Load the module (similar to "require(...)" in NodeJS)"
type="string"
required="false"
/>
<named
name="let"
helpstring="Assign the value: "name=value""
type="string"
required="false"
/>
<named
name="set"
helpstring="Create the object: "name=CreateObject(object)""
type="string"
required="false"
/>
<named
name="get"
helpstring="Get the object: "name=GetObject(object)""
type="string"
required="false"
/>
<named
name="e"
helpstring="One line program (multiple "/e"'s supported)"
type="string"
required="false"
/>
<named
name="n"
helpstring="Apply a program in a loop "while read LINE { ... }""
type="simple"
required="false"
/>
<named
name="p"
helpstring="Apply a program in a loop "while read LINE { ... print }""
type="simple"
required="false"
/>
<named
name="begin"
helpstring="The code for executing before the loop"
type="string"
required="false"
/>
<named
name="end"
helpstring="The code for executing after the loop"
type="string"
required="false"
/>
<named
name="beginfile"
helpstring="The code for executing before each file"
type="string"
required="false"
/>
<named
name="endfile"
helpstring="The code for executing after each file"
type="string"
required="false"
/>
<unnamed
name="scriptfile"
helpstring="The script file"
required="false"
/>
<!--
<named
name="@"
helpstring="Read arguments from the specified file"
type="string"
required="false"
/>
-->
<named
name="f"
helpstring="Open a file as "ascii", "unicode" or using system "default""
type="string"
required="false"
/>
<unnamed
name="arguments"
helpstring="Other arguments to be passed to the program"
required="false"
/>
</runtime>
<!-- <script language="javascript" src="./wsx/Helpers.js"></script> -->
<script language="javascript"><![CDATA[
//
// Set of useful and convenient definitions
// This script is the part of the wsx
//
// Copyright (c) 2019, 2020 by Ildar Shaimordanov
//
var FSO = new ActiveXObject('Scripting.FileSystemObject');
var STDIN = WScript.StdIn;
var STDOUT = WScript.StdOut;
var STDERR = WScript.StdErr;
var usage = help = (function() {
var helpMsg = [
'Commands Descriptions'
, '======== ============'
, 'usage(), help() Display this help'
, 'echo(), print(), alert() Print expressions'
, 'quit(), exit() Quit this shell'
, 'cmd(), shell() Run a command or DOS-session'
, 'sleep(n) Sleep n milliseconds'
, 'clip() Read from or write to clipboard'
, 'gc() Run the JScript garbage collector'
].join('\n');
return function() {
WScript.Echo(helpMsg);
};
})();
var echo = print = alert = (function() {
var slice = Array.prototype.slice;
return function() {
WScript.Echo(slice.call(arguments));
};
})();
var quit = exit = function(exitCode) {
WScript.Quit(exitCode);
};
var cmd = shell = function(command) {
var shell = new ActiveXObject('WScript.Shell');
shell.Run(command || '%COMSPEC%');
};
var sleep = function(time) {
return WScript.Sleep(time);
};
var clip = function(text) {
if ( typeof text == 'undefined' ) {
return new ActiveXObject('htmlfile').parentWindow.clipboardData.getData('Text');
}
// Validate a value is integer in the range 1..100
// Otherwise, defaults to 20
var clamp = function(x) {
x = Number(x);
if ( isNaN(x) || x < 1 || x > 100 ) {
x = 20;
}
return x;
};
var WAIT1 = clamp(clip.WAIT_READY);
var WAIT2 = clamp(clip.WAIT_LOADED);
// Borrowed from https://stackoverflow.com/a/16216602/3627676
var msie = new ActiveXObject('InternetExplorer.Application');
msie.silent = true;
msie.Visible = false;
msie.Navigate('about:blank');
// Wait until MSIE ready
while ( msie.ReadyState != 4 ) {
WScript.Sleep(WAIT1);
}
// Wait until document loaded
while ( msie.document.readyState != 'complete' ) {
WScript.Sleep(WAIT2);
}
msie.document.body.innerHTML = '<textarea id="area" wrap="off" />';
var area = msie.document.getElementById('area');
area.value = text;
area.select();
area = null;
// 12 - "Edit" menu, "Copy" command
// 0 - the default behavior
msie.ExecWB(12, 0);
msie.Quit();
msie = null;
};
var gc = CollectGarbage;
if ( typeof exports != "undefined" ) {
exports.FSO = FSO;
exports.STDIN = STDIN;
exports.STDOUT = STDOUT;
exports.STDERR = STDERR;
exports.usage = usage;
exports.help = help;
exports.echo = echo;
exports.alert = alert;
exports.print = print;
exports.quit = quit;
exports.exit = exit;
exports.cmd = cmd;
exports.shell = shell;
exports.sleep = sleep;
exports.clip = clip;
exports.gc = gc;
}
]]></script>
<!-- <script language="javascript" src="./core/console.js"></script> -->
<script language="javascript"><![CDATA[
//
// console.js
// Imitation of the NodeJS console in the Windows Scripting Host
//
// Copyright (c) 2012, 2013, 2019, 2020 by Ildar Shaimordanov
//
/*
The module adds the useful NodeJS console features to WSH
"console.log(), console.debug(), ..." can be used the same way as used in NodeJS.
Log messages with custom icon depending on the function used.
console.log(object[, object, ...])
console.debug(object[, object, ...])
console.info(object[, object, ...])
console.warn(object[, object, ...])
console.error(object[, object, ...])
Test if the expression. If it is false, the info will be logged in the console
console.assert(expression[, object, ...])
Starts and stops a timer and writes the time elapsed
console.time(name)
console.timeEnd(name)
Customizing the console
console.fn
Checks that the object is a formatting string
console.fn.isFormat(object)
The simplest formatting function immitating C-like format
console.fn.format(pattern, objects)
Details for the complex object
console.fn.inspect(object)
The low-level printing function
console.fn.print(msgType, msg)
The deep of nestion for complex structures (default is 5)
console.fn.deep
The initial value of indentation (4 whitespaces, by default).
A numeric value defines indentaion size, the number of space chars.
console.fn.space
Numeric values controls the visibility of functions. Defaults to 0.
(0 - do not show function, 1 - show [Function] string, 2 - show a details)
console.fn.func
The visibility properties from the prototype of the oject. Defaults to 0.
(0 - do not show properties from prototype, 1 - show then)
console.fn.proto
The string to glue the set of arguments when output them
console.fn.separator = ' '
The following functions are not implemented:
console.clear
console.count
console.dir
console.dirxml
console.group
console.groupCollapsed
console.groupEnd
console.profile
console.profileEnd
console.table
console.trace
*/
var console = console || (function() {
var entities = {
'&': '&',
'"': '"',
'<': '<',
'>': '>'
};
var escaped = /[\x00-\x1F\"\\]/g;
var special = {
'"': '\\"',
'\r': '\\r',
'\n': '\\n',
'\t': '\\t',
'\b': '\\b',
'\f': '\\f',
'\\': '\\\\'
};
var space;
var indent = '';
var deep;
var proto;
var func;
function _quote(value) {
var result = value.replace(escaped, function($0) {
return special[$0] || $0;
});
return '"' + result + '"';
};
// The main method for printing objects
var inspect = function(object) {
switch (typeof object) {
case 'string':
return _quote(object);
case 'boolean':
case 'number':
case 'undefined':
case 'null':
return String(object);
case 'function':
if ( func == 1 ) {
return '[Function]';
}
if ( func > 1 ) {
return object.toString();
}
return '';
case 'object':
if ( object === null ) {
return String(object);
}
// Assume win32 COM objects
if ( object instanceof ActiveXObject ) {
return '[ActiveXObject]';
}
var t = Object.prototype.toString.call(object);
// Assume the RegExp object
if ( t == '[object RegExp]' ) {
return String(object);
}
// Assume the Date object
if ( t == '[object Date]' ) {
return object.toUTCString();
}
// Stop the deeper nestings
if ( ! deep ) {
return '[...]';
}
var saveDeep = deep;
deep--;
var saveIndent = indent;
indent += space;
var result = [];
for (var k in object) {
if ( ! object.hasOwnProperty(k) && ! proto ) {
continue;
}
var v;
if ( object[k] === object ) {
v = '[Recursive]';
} else {
v = inspect(object[k]);
if ( v === '' ) {
// Sure that any property will return non-empty string
// Only functions can return an empty string when func == 0
continue;
}
}
result.push(k + ': ' + v);
}
var pred;
var post;
if ( t == '[object Array]' ) {
pred = 'Array(' + object.length + ') [';
post = ']';
} else {
pred = 'Object {';
post = '}';
}
result = result.length == 0
? '\n' + saveIndent
: '\n' + indent + result.join('\n' + indent) + '\n' + saveIndent;
indent = saveIndent;
deep = saveDeep;
return pred + result + post;
default:
return '[Unknown]';
}
};
// This regular expression is used to recongnize a formatting string
var reFormat = /%%|%(\d+)?([idfxso])/g;
// Checks that the object is a formatting string
var isFormat = function(object) {
return Object.prototype.toString.call(object) == '[object String]' && object.match(reFormat);
};
var formatters = {};
formatters.i = function(v) { return Number(v).toFixed(0); };
formatters.d =
formatters.f = function(v) { return Number(v).toString(10); };
formatters.x = function(v) { return Number(v).toString(16); },
formatters.o = inspect;
formatters.s = function(v) { return String(v); };
// The formatting function immitating C-like "printf"
var format = function(pattern, objects) {
var i = 0;
return pattern.replace(reFormat, function(format, width, id) {
if ( format == '%%' ) {
return '%';
}
i++;
var r = formatters[id](objects[i]);
return r;
});
};
// The low-level printing function
var print = function(msgType, msg) {
WScript.Echo(msg);
};
// The core function
var printMsg = function(msgType, objects) {
// Get the actual configuration of the console
var fn = console.fn || {};
var sep = fn.separator || ' ';
space = fn.space;
deep = Number(fn.deep) > 0 ? fn.deep : 5;
proto = fn.proto || 0;
func = fn.func || 0;
var t = Object.prototype.toString.call(space);
if ( t == '[object Number]' && space >= 0 ) {
space = new Array(space + 1).join(' ');
} else if ( t != '[object String]' ) {
space = ' ';
}
if ( typeof fn.isFormat == 'function' ) {
isFormat = fn.isFormat;
}
if ( typeof fn.format == 'function' ) {
format = fn.format;
}
if ( typeof fn.inspect == 'function' ) {
inspect = fn.inspect;
}
if ( typeof fn.print == 'function' ) {
print = fn.print;
}
var result;
if ( isFormat(objects[0]) ) {
//result = format(objects[0], Array.prototype.slice.call(objects, 1));
result = format(objects[0], objects);
} else {
result = [];
for (var i = 0; i < objects.length; i++) {
result.push(inspect(objects[i]));
}
result = result.join(sep);
}
print(msgType, result);
};
var log = function() {
printMsg(0, arguments);
};
var debug = log;
var info = function() {
printMsg(64, arguments);
};
var warn = function() {
printMsg(48, arguments);
};
var error = function() {
printMsg(16, arguments);
};
// If the expression is false, the rest of arguments will be logged in
// the console
var assert = function(expr) {
if ( expr ) {
return;
}
error.apply(console, arguments.length < 2 ? ['Assertion error'] : Array.prototype.slice.call(arguments, 1));
};
// Processing of timers start/stop
var timeNames = {};
var timeStart = function(name) {
if ( ! name ) {
return;
}
timeNames[name] = new Date();
log(name + ': Timer started');
};
var timeEnd = function(name) {
if ( ! name || ! timeNames.hasOwnProperty(name) ) {
return;
}
var t = new Date() - timeNames[name];
delete timeNames[name];
log(name + ': ' + t + 'ms');
};
return {
// Implemented methods
log: log,
debug: debug,
info: info,
warn: warn,
error: error,
assert: assert,
time: timeStart,
timeEnd: timeEnd,
// Not implemented methods
clear: function() {},
dir: function() {},
dirxml: function() {},
group: function() {},
groupCollapsed: function() {},
groupEnd: function() {},
profile: function() {},
profileEnd: function() {},
table: function() {},
trace: function() {},
// Customizing the console
fn: {
space: 4,
deep: 5,
proto: 0,
func: 0,
separator: ' '
}
};
})();
if ( typeof module != "undefined" ) {
module.exports = console;
}
]]></script>
<!-- <script language="javascript" src="./core/require.js"></script> -->
<script language="javascript"><![CDATA[
//
// require.js
// Imitation of the NodeJS function require in the Windows Scripting Host
//
// Copyright (c) 2019, 2020 by Ildar Shaimordanov
//
/*
The module adds to WSH the "require" function, the useful NodeJS feature, and some additional extensions.
Load a module by name of filename
require(id[, options])
Resolve a location of the module
require.resolve(id[, options])
The list of of paths used to resolve the module location
require.paths
Cache for the imported modules
require.cache
*/
var require = require || (function() {
var fso = WScript.CreateObject("Scripting.FileSystemObject");
function loadFile(file, format) {
var stream = fso.OpenTextFile(file, 1, false, format || 0);
var text = stream.ReadAll();
stream.Close();
return text;
};
/**
* Load a module by name of filename
*
* @param <String> module name or path
* @param <Object> options
* @return <Object> exported module content
*
* Available options:
* - paths <Array> Paths to resolve the module location
* - format <Integer> format of the opened file (-2 - system default, -1 - Unicode file, 0 - ASCII file)
*/
function require(id, options) {
if ( ! id ) {
throw new TypeError("Missing path");
}
if ( typeof id != "string" ) {
throw new TypeError("Path must be a string");
}
options = options || {};
var file = require.resolve(id, options);
require.cache = require.cache || {};
if ( ! require.cache[file] || ! require.cache[file].loaded ) {
var text = loadFile(file, options.format);
var code
= "(function(module) {\n"
+ "var exports = module.exports;\n"
+ text + ";\n"
+ "return module.exports || {};\n"
+ "})({ exports: {} })";
var evaled = eval(code);
require.cache[file] = {
exports: evaled,
loaded: true
};
}
return require.cache[file].exports;
};
function absolutePath(file) {
if ( fso.FileExists(file) ) {
return fso.GetAbsolutePathName(file);
}
}
/**
* Resolve a location of the module
*
* @param <String> module name or path
* @param <Object> options
* @return <String> resolved location of the module
*
* Available options:
* - paths <Array> Paths to resolve the module location
*/
require.resolve = function(id, options) {
options = options || {};
var file = /[\\\/]|\.js$/i.test(id)
// module looks like a path
? absolutePath(/\.[^.\\\/]+$/.test(id) ? id : id + ".js")
// attempt to find a librarian module
: (function() {
var paths = [].concat(options.paths || [], require.paths);
for (var i = 0; i < paths.length; i++) {
var file = absolutePath(paths[i] + "\\" + id + ".js");
if ( file ) {
return file;
}
}
})();
if ( ! file ) {
throw new Error("Cannot find module '" + id + "'");
}
return file;
};
var myDir = fso.GetParentFolderName(WScript.ScriptFullName);
var me = WScript.ScriptName.replace(/(\.[^.]+\?)?\.[^.]+$/, '');
var shell = WScript.CreateObject ("WScript.Shell");
var cwd = shell.CurrentDirectory;
require.paths = [
myDir + "\\js"
, myDir + "\\" + me
, myDir + "\\" + me + "\\js"
, myDir + "\\lib"
, cwd
];
if ( fso.GetBaseName(myDir) == "bin" ) {
require.paths.push(myDir + "\\..\\lib");
}
return require;
})();
]]></script>
<!-- <script language="vbscript" src="./core/importer.vbs"></script> -->
<script language="vbscript"><![CDATA[
'
' importer.js
' Import modules by name or filename similar to the NodeJS "require"
'
' Copyright (c) 2019, 2020 by Ildar Shaimordanov
'
' @see
' https://blog.ctglobalservices.com/scripting-development/jgs/include-other-files-in-vbscript/
' https://helloacm.com/include-external-files-in-vbscriptjscript-wsh/
' https://www.planetcobalt.net/sdb/importvbs.shtml
' https://stackoverflow.com/a/316169/3627676
' https://stackoverflow.com/a/43957897/3627676
' https://riptutorial.com/vbscript/topic/8345/include-files
' Create and return an instance of the Importer class
' Can be useful to import VBScript modules from JScript
'
' @return <Importer>
Function CreateImporter
Set CreateImporter = New Importer
End Function
Class Importer
' For caching already loaded modules
Private cache
' File system object, used internally
Private fso
' The list of of paths used to resolve the module location
Public paths
' format of the opened file
' (-2 - system default, -1 - Unicode file, 0 - ASCII file)
Public format
' Initialize importer
Private Sub Class_Initialize
Set cache = CreateObject("Scripting.Dictionary")
Set fso = CreateObject("Scripting.FileSystemObject")
Dim re
Set re = New RegExp
re.Pattern = "(\.[^.]+\?)?\.[^.]+$"
Dim mydir, myself
mydir = fso.GetParentFolderName(WScript.ScriptFullName)
myself = re.Replace(WScript.ScriptName, "")
Dim shell, cwd
Set shell = WScript.CreateObject("WScript.Shell")
cwd = shell.CurrentDirectory
paths = Array( _
mydir & "\vbs" _
, mydir & "\" & myself _
, mydir & "\" & myself & "\vbs" _
, mydir & "\lib" _
, cwd _
)
If fso.GetBaseName(mydir) = "bin" Then
ReDim Preserve paths(UBound(paths) + 1)
paths(UBound(paths)) = mydir & "\..\lib"
End If
End Sub
' Destroy importer
Private Sub Class_Terminate
Set paths = Nothing
Set fso = Nothing
Set cache = Nothing
End Sub
Private Sub PathIncrement(insert, apath)
Dim gain
If IsArray(apath) Then
gain = UBound(apath) + 1
Else
gain = 1
End If
Dim count
count = UBound(paths)
Redim Preserve paths(count + gain)
Dim i
If insert = 1 Then
For i = count To 0 Step -1
paths(i + gain) = paths(i)
Next
count = 0
End If
If IsArray(apath) Then
For i = 0 To gain - 1
paths(count + i) = apath(i)
Next
Else
paths(count) = apath
End If
End Sub
' Add a path or array of paths to the begin of the list of paths
'
' @param <String|Array> Path or array of paths
Public Sub PathInsert(apath)
PathIncrement 1, apath
End Sub
' Add a path or array of paths to the end of the list of paths
'
' @param <String|Array> Path or array of paths
Public Sub PathAppend(apath)
PathIncrement 0, apath
End Sub
' Load a text
'
' @param <String> a text to be executed
Public Sub Execute(text)
On Error GoTo 0
ExecuteGlobal text
On Error Resume Next
End Sub
' Load a module by name of filename
'
' @param <String> module name or path
Public Sub Import(id)
On Error GoTo 0
Dim Name
Dim ErrStr
Select Case VarType(id)
Case vbString
Name = id
Case vbEmpty
ErrStr = "Missing path (Empty)"
Case vbNull
ErrStr = "Missing path (Null)"
Case Else
ErrStr = "Path must be a string"
End Select
If ErrStr <> "" Then
Err.Description = ErrStr
Err.Raise vbObjectError
End If
Dim file
file = Resolve(id)
If Not cache.Exists(file) Then
Dim text
text = ReadFile(file)
ExecuteGlobal text
cache.Add file, 1
End If
On Error Resume Next
End Sub
' Read a file
Private Function ReadFile(file)
Dim stream
Set stream = fso.OpenTextFile(file, 1, False, format)
ReadFile = stream.ReadAll
stream.Close
Set stream = Nothing
End Function
' Resolve a location of the module
'
' @param <String> module name or path
' @return <String> resolved location of the module
Public Function Resolve(id)
Dim file
Dim re
Set re = New RegExp
re.Pattern = "[\\\/]|\.vbs$"
re.IgnoreCase = True
If re.Test(id) Then
' module looks like a path
re.Pattern = "\.[^.\\\/]+$"
If re.Test(id) Then
file = id
Else
file = id & ".vbs"
End If
Resolve = AbsolutePath(file)
Else
' attempt to load a librarian module
Dim path
For Each path In paths
file = path & "\" & id & ".vbs"
Resolve = AbsolutePath(file)
If Resolve <> "" Then
Exit For
End If
Next
End If
If Resolve = "" Then
Err.Description = "Cannot find module '" & id & "'"
Err.Raise vbObjectError
End If
End Function
' Return the absolute path if file exists, otherwise - empty string
Private Function AbsolutePath(file)
AbsolutePath = ""
If fso.FileExists(file) Then
AbsolutePath = fso.GetAbsolutePathName(file)
End If
End Function
End Class
]]></script>
<!-- <script language="javascript" src="./wsx/Program.js"></script> -->
<script language="javascript"><![CDATA[
//
// Program
// This script is the part of the wsx
//
// Copyright (c) 2019, 2020 by Ildar Shaimordanov
//
var Program = {
dryRun: false,
quiet: false,
inLoop: 0,
engine: "js",
modules: [],
vars: [],
main: [],
begin: [],
end: [],
beginfile: [],
endfile: [],
setEngine: function(engine) {
this.engine = engine;
},
getEngine: function(engine) {
return (engine || this.engine).toLowerCase();
},
setMode: function(mode) {
var loopTypes = { i: 0, n: 1, p: 2 };
this.inLoop = loopTypes[mode];
},
setQuiet: function() {
this.quiet = true;
},
addScript: function(engine, name) {
name = name.replace(/\\/g, '\\\\');
var result = '';
if ( this.getEngine(engine) == "vbs" ) {
result = 'USE.Import("' + name + '")';
} else {
result = 'require("' + name + '")';
}
return result;
},
addModule: function(engine, name) {
this.modules.push(this.addScript(engine, name));
},
vbsVar: function(name, value, setter) {
var result = 'Dim ' + name;
if ( value ) {
switch( setter.toLowerCase() ) {
case 'let': result += ' : ' + name + ' = \\"' + value + '\\"'; break;
case 'set': result += ' : Set ' + name + ' = CreateObject(\\"' + value + '\\")'; break;
case 'get': result += ' : Set ' + name + ' = GetObject(\\"' + value + '\\")'; break;
}
}
return 'USE.Execute("' + result + '")';
},
jsVar: function(name, value, setter) {
//var result = 'var ' + name;
var result = ''
if ( value ) {
result = name;
switch( setter.toLowerCase() ) {
case 'let': result += ' = "' + value + '"'; break;
case 'set': result += ' = new ActiveXObject("' + value + '")'; break;
case 'get': result += ' = GetObject("' + value + '")'; break;
}
}
return result;
},
addVar: function(engine, name, value, setter) {
var result;
if ( this.getEngine(engine) == "vbs" ) {
result = this.vbsVar(name, value, setter);
} else {
result = this.jsVar(name, value, setter);
}
this.vars.push(result);
},
addCode: function(engine, code, region) {
var result = '';
if ( this.getEngine(engine) == "vbs" ) {
result = 'USE.Execute("' + code + '")';
} else {
result = code;
}
this[region || 'main'].push(result);
},
detectScriptFile: function(args) {
if ( this.main.length ) {
return;
}
if ( this.inLoop ) {
return;
}
if ( args.length ) {
var scriptFile = args.shift();
var engine = /\.vbs$/.test(scriptFile) ? 'vbs' : 'js';
this.main.push(this.addScript(engine, scriptFile));
}
}
};
]]></script>
<!-- <script language="javascript" src="./wsx/Runner.js"></script> -->
<script language="javascript"><![CDATA[
//
// Code processor: Runner
// This script is the part of the wsx
//
// Copyright (c) 2019, 2020 by Ildar Shaimordanov
//
var Runner = function(Program, argv) {
if ( Program.dryRun ) {
Runner.dump(Program);
return;
}
var modules = Program.modules.join(';\n');
var vars = Program.vars.join(';\n');
var begin = Program.begin.join(';\n');
var beginfile = Program.beginfile.join(';\n');
var main = Program.main.join(';\n');
var endfile = Program.endfile.join(';\n');
var end = Program.end.join(';\n');
/*
The following variables are declared without the keyword "var". So
they become global and available for all codes in JScript and VBScript.
*/
// Helper to simplify VBS importing
USE = CreateImporter();
// Keep a last exception
ERROR = null;
// Reference to CLI arguments
ARGV = argv;
/*
Load provided modules
Set user-defined variables
*/
eval(modules);
eval(vars);
if ( Program.main.length == 0 && Program.inLoop == false ) {
/*
Run REPL
*/
REPL.quiet = Program.quiet;
REPL();
return;
}
if ( ! Program.inLoop ) {
/*
Load the main script and do nothing more.
*/
eval(main);
return;
}
// The currently open stream
STREAM = null;
// The current filename, file format and file number
FILE = '';
FILEFMT = 0;
// The current line read from the stream
LINE = '';
// The line number in the current file
FLN = 0;
// The total line number
LN = 0;
// Emulate the "continue" operator
next = function() {
throw new EvalError('next');
};
// Emulate the "break" operator
last = function() {
throw new EvalError('last');
};
/*
Execute the code before starting to process any file.
This is good place to initialize.
*/
eval(begin);
if ( ! ARGV.length ) {
ARGV.push('con');
}
while ( ARGV.length ) {
FILE = ARGV.shift();
var m = FILE.match(/^\/f:(ascii|unicode|default)$/i);
if ( m ) {
var fileFormats = { ascii: 0, unicode: -1, 'default': -2 };
FILEFMT = fileFormats[ m[1] ];
continue;
}
FLN = 0;
/*
Execute the code before starting to process the file.
We can do here something while the file is not opened.
*/
eval(beginfile);
try {
STREAM = FILE.toLowerCase() == 'con'
? STDIN
: FSO.OpenTextFile(FILE, 1, false, FILEFMT);
} catch (ERROR) {
WScript.Echo(ERROR.message + ': ' + FILE);
continue;
}
/*
Prevent failure of reading out of STDIN stream
The real exception number is 800a005b (-2146828197)
"Object variable or With block variable not set"
*/
//// NEED MORE INVESTIGATION
//try {
// stream.AtEndOfStream;
//} catch (ERROR) {
// WScript.StdErr.WriteLine('Out of stream: ' + file);
// continue;
//}
while ( ! STREAM.AtEndOfStream ) {
FLN++;
LN++;
LINE = STREAM.ReadLine();
/*
Execute the main code per each input line.
*/
try {
eval(main);
} catch (ERROR) {
if ( ERROR instanceof EvalError && ERROR.message == 'next' ) {
continue;
}
if ( ERROR instanceof EvalError && ERROR.message == 'last' ) {
break;
}
throw ERROR;
}
if ( Program.inLoop == 2 ) {
WScript.Echo(LINE);
}
}
if ( STREAM != STDIN ) {
STREAM.Close();
}
/*
Execute the code when the file is already closed. We can do
some finalization (i.e.: print the number of lines in the file).
*/
eval(endfile);
}
/*
Execute the code when everything is completed.
We can finalize the processing (i.e.: print the total number of lines).
*/
eval(end);
};
Runner.dump = function(Program) {
var s = [];
function dumpCode(code) {
if ( code.length ) {
s.push(code.join(';\n'));
}
}
dumpCode(Program.modules);
dumpCode(Program.vars);
if ( Program.inLoop ) {
dumpCode(Program.begin);
s.push('::foreach FILE do');
dumpCode(Program.beginfile);
s.push('::while read LINE do');
}
if ( Program.main.length == 0 && Program.inLoop == false ) {
s = s.concat([
'::while read',
'::eval',
'::print',
'::loop while'
]);
}
dumpCode(Program.main);
if ( Program.inLoop == 2 ) {
s.push('::print LINE');
}
if ( Program.inLoop ) {
s.push('::loop while');
dumpCode(Program.endfile);
s.push('::loop foreach');
dumpCode(Program.end);
}
WScript.Echo(s.join('\n'));
};
]]></script>
<!-- <script language="javascript" src="./wsx/REPL.js"></script> -->
<script language="javascript"><![CDATA[
//
// Code processor: REPL
// This script is the part of the wsx
//
// Copyright (c) 2019, 2020 by Ildar Shaimordanov
//
var REPL = function() {
if ( ! WScript.FullName.match(/cscript.exe$/i) ) {
WScript.Echo('REPL works with cscript only');
WScript.Quit();
}
while ( true ) {
try {
(function(storage, result) {
/*
A user can modify the codes of these methods so
to prevent the script malfunctioning we have
to keep their original codes and restore them later
*/
eval = storage.eval;
REPL = storage.REPL;
if ( result === void 0 ) {
return;
}
if ( result && typeof result == 'object'
&& console && typeof console == 'object'
&& typeof console.log == 'function' ) {
console.log(result);
} else {
WScript.StdOut.WriteLine('' + result);
}
})({
eval: eval,
REPL: REPL
},
eval((function(PS1, PS2) {
if ( REPL.quiet ) {
PS1 = '';
PS2 = '';
} else {
var me = WScript.ScriptName;
PS1 = me + ' js > ';
PS2 = me + ' js :: ';
}
/*
The REPL.history can be changed by the user as he
can. We should prevent a concatenation with the one
of the empty values such as undefined, null, etc.
*/
if ( ! REPL.history || ! ( REPL.history instanceof [].constructor ) ) {
REPL.history = [];
}
/*
The REPL.number can be changed by the user as he can.
We should prevent an incrementing of non-numeric values.
*/
if ( ! REPL.number || typeof REPL.number != 'number' ) {
REPL.number = 0;
}
/*
The line consisting of two colons only switches the
multiline mode. The first entry of double colons
means turn on the multiline mode. The next entry
turns off. In the multiline mode it's possible to
type a code of few lines without inpterpreting.
*/
var multiline = false;
/*
Storages for:
-- one input line
-- one or more input lines as array
(more than one are entered in multiline mode)
-- the resulting string of all entered lines
(leading and trailing whitespaces are trimmed)
*/
var input = [];
var inputs = [];
var result = '';
WScript.StdOut.Write(PS1);
while ( true ) {
try {
REPL.number++;
input = [];
while ( ! WScript.StdIn.AtEndOfLine ) {
input[input.length] = WScript.StdIn.Read(1);
}
WScript.StdIn.ReadLine();
} catch (ERROR) {
input = [ 'WScript.Quit()' ];
}
if ( input.length == 2 && input[0] + input[1] == '::' ) {
input = [];
multiline = ! multiline;
}
if ( inputs.length ) {
inputs[inputs.length] = '\n';
}
for (var i = 0; i < input.length; i++) {
inputs[inputs.length] = input[i];
}
if ( ! multiline ) {
break;
}
WScript.StdOut.Write(PS2);
} // while ( true )
// Trim left
var k = 0;
while ( inputs[k] <= ' ' ) {
k++;
}
// Trim right
var m = inputs.length - 1;
while ( inputs[m] <= ' ' ) {
m--;
}
var result = '';
for (var i = k; i <= m; i++) {
result += inputs[i];
}
if ( result == '' ) {
return '';
}
REPL.history[REPL.history.length] = result;
return result;
})()));
} catch (ERROR) {
WScript.StdErr.WriteLine(WScript.ScriptName
+ ': "<stdin>", line ' + REPL.number
+ ': ' + ERROR.name
+ ': ' + ERROR.message);
}
} // while ( true )
};
]]></script>
<!-- <script language="javascript" src="./wsx/CommandLine.js"></script> -->
<script language="javascript"><![CDATA[
//
// Command Line processor
// This script is the part of the wsx
//
// Copyright (c) 2019, 2020 by Ildar Shaimordanov
//
(function(Program, Runner, REPL) {
var argv = [];
function ShowVersion() {
var me = WScript.ScriptName.replace(/(\.[^.]+\?)?\.[^.]+$/, '');
var name = typeof NAME == 'string' ? NAME : me;
var version = typeof VERSION == 'string' ? VERSION : '0.0.1';
WScript.Echo(name + ' (' + me + '): Version ' + version
+ '\n' + WScript.Name
+ ': Version ' + WScript.Version
+ ', Build ' + WScript.BuildVersion);
}
// Walk through all named and unnamed arguments because
// we have to handle each of them even if they duplicate
for (var i = 0; i < WScript.Arguments.length; i++) {
var arg = WScript.Arguments.Item(i);
var m;
m = arg.match(/^\/h(?:elp)?$/i);
if ( m ) {
WScript.Arguments.ShowUsage();
WScript.Quit();
}
m = arg.match(/^\/version$/i);
if ( m ) {
ShowVersion();
WScript.Quit();
}
m = arg.match(/^\/dry-run$/i);
if ( m ) {
Program.dryRun = true;
continue;
}
m = arg.match(/^\/q(?:uiet)?$/i);
if ( m ) {
Program.setQuiet();
continue;
}
m = arg.match(/^\/use:(js|vbs)$/i);
if ( m ) {
Program.setEngine(m[1]);
continue;
}
m = arg.match(/^\/m(?::(js|vbs))?:(.+)$/i);
if ( m ) {
Program.addModule(m[1], m[2]);
continue;
}
m = arg.match(/^\/(let|set|get)(?::(js|vbs))?:(\w+)=(.*)$/i);
if ( m ) {
Program.addVar(m[2], m[3], m[4], m[1]);
continue;
}
m = arg.match(/^\/(?:e|((?:begin|end)(?:file)?))(?::(js|vbs))?(?::(.*))?$/i);
if ( m ) {
Program.addCode(m[2], m[3], m[1]);
continue;
}
m = arg.match(/^\/([np])$/i);
if ( m ) {
Program.setMode(m[1]);
continue;
}
/*
This looks like ugly, but it works and reliable enough to
stop looping over the rest of the CLI options. From this
point we allow end users to specify their own options even,
if their names intersect with names of our options.
*/
break;
}
for ( ; i < WScript.Arguments.length; i++) {
var arg = WScript.Arguments.Item(i);
argv.push(arg);
}
Program.detectScriptFile(argv);
Runner(Program, argv);
})(Program, Runner, REPL);
]]></script>
</job>
</package>