cejs
Version:
A JavaScript module framework that is simple to use.
1,200 lines (1,073 loc) • 35.5 kB
JavaScript
/**
* @name CeL function for Node.js
* @fileoverview 本檔案包含了 Node.js 專用的 functions。
*
* use 'application.storage' instead
*
* @since
*/
;
// 'use asm';
// --------------------------------------------------------------------------------------------
// 不採用 if 陳述式,可以避免 Eclipse JSDoc 與 format 多縮排一層。
typeof CeL === 'function' && CeL.run({
// TODO: 使用此名稱,在 include 時可能沖到原先的 CeL.platform!!
// module name
name : 'application.platform.nodejs',
// to_JS_value()
require : 'data.code.',
// 設定不匯出的子函式。
// no_extend : '*',
// 為了方便格式化程式碼,因此將 module 函式主體另外抽出。
code : module_code
});
function module_code(library_namespace) {
// http://nodejs.org/api/process.html
if (!library_namespace.platform.nodejs) {
library_namespace.warn('Error loading ' + this.name
+ ': Not in Node.js?');
// library_namespace.set_debug();
library_namespace.debug('typeof process: ' + typeof process);
if (typeof process !== 'undefined')
library_namespace.debug('process.title: ' + process.title);
library_namespace.debug('typeof global: ' + typeof global);
library_namespace.debug('typeof require: ' + typeof require);
return;
}
/**
* null module constructor
*
* @class Node.js 的 functions
*/
var _// JSDT:_module_
= function() {
// null module constructor
};
/**
* for JSDT: 有 prototype 才會將之當作 Class
*/
_// JSDT:_module_
.prototype = {};
/** {String}path separator. e.g., '/', '\' */
var path_separator = library_namespace.env.path_separator,
// placeholder for library_namespace.storage.append_path_separator()
append_path_separator = function(directory_path, file_name) {
if (library_namespace.append_path_separator)
return (append_path_separator = library_namespace.append_path_separator)
.apply(null, arguments);
file_name = file_name || file_name === 0 ? String(file_name).replace(
/^(\.{0,2}[\\\/])+/, '') : '';
return directory_path + path_separator + file_name;
};
// set/get current working directory. 設定/取得目前工作目錄。
function working_directory(change_to_directory) {
if (change_to_directory)
process.chdir(change_to_directory);
return append_path_separator(process.cwd());
}
_.working_directory = working_directory;
/** const node.js file system module */
var node_fs = require('fs'), child_process = require('child_process');
// TODO: 規範化
function fs_status(file_path, with_error) {
var file_status;
try {
return node_fs.lstatSync(file_path);
} catch (e) {
// console.error(e);
if (with_error) {
return e;
}
}
}
_.fs_status = fs_status;
_.file_exists = function file_exists(file_path) {
var fso_status = fs_status(file_path);
// console.trace([ file_path, fso_status ]);
return fso_status
&& (fso_status.isFile() || fso_status.isSymbolicLink());
};
_.chmod = function chmodSync(path, mode) {
try {
node_fs.chmodSync(path, mode);
} catch (e) {
return e;
}
};
function directory_exists(directory_path) {
var fso_status = fs_status(directory_path);
return fso_status && fso_status.isDirectory();
}
_.directory_exists = directory_exists;
function copy_attributes(source, target) {
var file_status;
try {
// TODO: fs.stat cannot read a filename includes U+FBC6 "?"
file_status = node_fs.statSync(source);
node_fs.utimesSync(target, file_status.atime, file_status.mtime);
} catch (e) {
return e;
}
}
_.copy_attributes = copy_attributes;
function fs_copy(source, target, callback, overwrite) {
if (!callback)
callback = function(error) {
if (false) {
library_namespace.log(source + '\n→\n' + target);
}
if (error) {
library_namespace.error(error);
}
};
if (node_fs.existsSync(target))
if (overwrite) {
node_fs.unlinkSync(path);
} else {
callback();
}
// 2016/8/18 20:5:5 可採用 fs.ReadStream.prototype.bytesRead
var source_stream = node_fs.createReadStream(source),
//
target_stream = node_fs.createWriteStream(target);
source_stream.on("error", callback);
target_stream.on("error", callback);
target_stream.on("close", function() {
copy_attributes(source, target);
callback();
});
source_stream.pipe(target_stream);
}
_.fs_copy = fs_copy;
// returns undefined if successful
function fs_copySync(source, target, overwrite) {
// destination
if (node_fs.existsSync(target))
if (overwrite) {
node_fs.unlinkSync(target);
} else {
return new Error('Target file exists: [' + target + ']!');
}
// TODO: use fs.createReadStream(), fs.createWriteStream(, {mode})
// https://github.com/coderaiser/fs-copy-file/blob/master/lib/fs-copy-file.js
var buffer_length = 1 * 1024 * 1024,
//
buffer = Buffer.allocUnsafe(buffer_length),
//
source_descriptor = node_fs.openSync(source, 'r'),
//
target_descriptor = node_fs.openSync(target, 'w'),
//
bytesRead, position = 0;
while (0 < (bytesRead
//
= node_fs.readSync(source_descriptor, buffer, 0, buffer_length,
position))) {
node_fs.writeSync(target_descriptor, buffer, 0, bytesRead);
position += bytesRead;
}
node_fs.closeSync(source_descriptor);
node_fs.closeSync(target_descriptor);
copy_attributes(source, target);
}
_.fs_copySync = node_fs.copyFileSync ? function(source, target, overwrite) {
try {
node_fs.copyFileSync(source, target, overwrite ? 0
: node_fs.constants.COPYFILE_EXCL);
} catch (e) {
// TODO: handle exception
}
} : fs_copySync;
// e.g., for node_fs_constants.R_OK
var node_fs_constants = node_fs.constants || node_fs;
/**
* create directory / directories
*
* @param {String|Array}directories
* directory name
*
* @param {Object}[options]
* 附加參數/設定選擇性/特殊功能與選項。 e.g., { recursive : true }
*
* @returns error count
*/
function create_directory(directories, options) {
// var node_fs = require('fs');
var error = 0;
if (typeof directories === 'string') {
directories = [ directories ];
}
directories.forEach(function(directory_name) {
// directory_name = String(directory_name).replace(/[\\\/]+$/, '');
if (!directory_name) {
return;
}
try {
node_fs.accessSync(directory_name, node_fs_constants.F_OK
| node_fs_constants.R_OK | node_fs_constants.W_OK
| node_fs_constants.X_OK);
library_namespace.debug('Already existed: [' + directory_name
+ ']', 1, 'create_directory');
return;
} catch (e) {
}
library_namespace.debug('Create [' + directory_name + ']...', 1,
'create_directory');
try {
if (library_namespace.platform.is_Windows()
&& directory_name.endsWith('.')) {
library_namespace.warn({
// gettext_config:{"id":"a-directory-name-ending-with-a-.-will-result-in-no-way-to-delete-or-copy-$1"}
T : [ '以點 "." 作為結尾的目錄名稱,將無法刪除或複製:%1',
JSON.stringify(directory_name) ]
});
}
if (false && isNaN(mode)) {
// https://github.com/nodejs/node/pull/32499
// deprecate process.umask() with no arguments
mode = parseInt('700', 8)
| (parseInt('777', 8) ^ process.umask());
}
node_fs.mkdirSync(directory_name, options);
} catch (e) {
if (e.code !== 'EEXIST')
;
if (!options || !options.no_throw)
throw e;
library_namespace.warn([ 'create_directory: ', {
// gettext_config:{"id":"create-directory-$1-failed-$2"}
T : [ '創建目錄 [%1] 失敗:%2', directory_name, String(e) ]
} ]);
error++;
}
});
return error;
}
_.fs_mkdir = create_directory;
/**
* remove path list.
*
* @param {Array}list
* path list.
* @param {Array}parent
* parent directory
*
* @returns error
*
* @inner
*/
function remove_fso_list(list, recursive, parent) {
if (parent) {
parent = append_path_separator(parent);
}
var error;
list.some(function(fso_name) {
// recursive, iterative method
return error = remove_fso(parent ? parent + fso_name : fso_name,
recursive);
});
return error;
}
/**
* remove file / directory recursively.<br />
* 若有需要先刪除之子檔案列表,需要把母directory置於Array最末尾。
*
* 注意:改變API時需要順便修訂CeL.application.storage中的remove_file。
*
* TODO: 有時操作來不及,會出現錯誤,需要 flush disk cache。<br />
* TODO: 處理強制刪除force,例如無視唯讀屬性。<br />
*
* @param {String|Array}path
* file / directory name
*
* @see https://github.com/isaacs/rimraf/blob/master/rimraf.js
*/
function remove_fso(path, recursive) {
if (Array.isArray(path)) {
return remove_fso_list(path, recursive);
}
// fs.rmSync(path[, options]) Added in: v14.14.0
try {
if (recursive) {
library_namespace.debug({
// gettext_config:{"id":"recursively-removing-subdirectories-of-$1"}
T : [ 'Recursively removing subdirectories of %1', path ]
}, 2, 'remove_fso');
}
// https://github.com/nodejs/node/issues/56049
// console.trace(path, recursive);
node_fs.rmSync(path, {
recursive : !!recursive
});
return;
} catch (e) {
if (e.code !== 'ERR_FS_EISDIR') {
return e;
}
// Will try node_fs.rmdirSync(path); later
}
// `path` should be empty directory, or should set recursive flag.
library_namespace.debug({
// gettext_config:{"id":"removing-directory-$1"}
T : [ 'Removing directory: %1', path ]
}, 1, 'remove_fso');
// delete directory itself.
try {
node_fs.rmdirSync(path);
} catch (e) {
return e;
}
}
function old_remove_fso(path, recursive) {
if (Array.isArray(path)) {
return remove_fso_list(path, recursive);
}
try {
/**
* The lstat() system call is like stat() except in the case where
* the named file is a symbolic link, in which case lstat() returns
* information about the link, while stat() returns information
* about the file the link references.
*/
var fso_status = node_fs.lstatSync(path);
// https://nodejs.org/api/fs.html#fs_class_fs_stats
if (!fso_status.isDirectory()) {
library_namespace.debug({
// gettext_config:{"id":"removing-file-$1"}
T : [ 'Removing file: %1', path ]
}, 1, 'remove_fso');
// delete file, link, ...
node_fs.unlinkSync(path);
return;
}
// 設定 recursive/force 時才會遞迴操作。
if (recursive) {
library_namespace.debug({
// gettext_config:{"id":"recursively-removing-subdirectories-of-$1"}
T : [ 'Recursively removing subdirectories of %1', path ]
}, 2, 'remove_fso');
// https://github.com/nodejs/node/pull/29168
// fs: add recursive option to fs.rmdir(), fs.rmdirSync(), and
// fs.promises.rmdir().
// https://github.com/nodejs/node/pull/35171
// fs: remove experimental language from rmdir recursive
if (library_namespace.platform('node', '12.10')) {
// using native function
node_fs.rmdirSync(path, {
// emfileWait : 10 * 1000,
// maxBusyTries : 10,
recursive : true
});
return;
}
var error
// recursive, iterative method
= remove_fso_list(node_fs.readdirSync(path), recursive, path);
if (error) {
return error;
}
}
library_namespace.debug({
// gettext_config:{"id":"removing-directory-$1"}
T : [ 'Removing directory: %1', path ]
}, 1, 'remove_fso');
// delete directory itself.
node_fs.rmdirSync(path);
} catch (e) {
// https://nodejs.org/api/errors.html
if (e.code === 'EPERM') {
// TODO: .chmodSync(path, 666) @ Windows??
;
}
if (e.code === 'EBUSY') {
// TODO
;
}
if (e.code === 'ENOTEMPTY') {
// TODO: 可能有其他原因不能刪除子物件?
;
}
if (e.code !== 'ENOENT') {
return e;
}
}
}
// _.fs_delete, _.fs_rmdir
_.fs_remove =
// fs.rmSync(path[, options]) Added in: v14.14.0
typeof node_fs.rmSync === 'function' ? remove_fso : old_remove_fso;
var KEY_auto_detect_encoding = 'auto',
// https://github.com/nodejs/node/blob/master/lib/buffer.js
// https://en.wikipedia.org/wiki/Byte_order_mark#Byte_order_marks_by_encoding
// TODO: more detecting, @see guess_encoding()
// "binary", "iso2022", "iso88591", "usascii", "utf7"
BOM_to_encoding = {
// e.g., Excel 將活頁簿儲存成 "Unicode 文字"時的正常編碼為 UTF-16LE
fffe : 'utf16le',
// byte order mark (BOM) of UTF-8: 0xEF,0xBB,0xBF
efbbbf : 'utf8'
}, BOM_list = Object.keys(BOM_to_encoding),
//
max_BOM_length = BOM_list.reduce(function(length, BOM) {
// assert: (BOM.length / 2 | 0) === BOM.length / 2
return Math.max(length, Math.ceil(BOM.length / 2));
}, 0);
// https://nodejs.org/docs/latest/api/buffer.html#buffers-and-character-encodings
_.file_utf16le_mapping = {
'UTF-16' : 'utf16le'
};
/**
* fs.readFileSync() without throw.
*
* @param {String}file_path
* file path.
* @param {Object}[options]
* 附加參數/設定選擇性/特殊功能與選項
*
* @returns {String}檔案內容
* @returns {Undefined}error occurred
*/
function fs_readFileSync(file_path, options) {
// auto detect encoding
var auto_detect_encoding;
if (options === KEY_auto_detect_encoding) {
options = null;
auto_detect_encoding = true;
} else if (options && options.encoding === KEY_auto_detect_encoding) {
// delete options.encoding
options.encoding = null;
auto_detect_encoding = true;
}
try {
// TODO:
// https://github.com/sonicdoe/detect-character-encoding
var buffer = node_fs.readFileSync(file_path, options);
if (auto_detect_encoding) {
var BOM = buffer.slice(0, max_BOM_length).toString('hex');
if (BOM_list.every(function(BOM_key) {
if (!BOM.startsWith(BOM_key))
return true;
// 去掉 BOM
// assert: (BOM.length / 2 | 0) === BOM.length / 2
buffer = buffer.slice(BOM_key.length / 2)
if (BOM_key === 'feff') {
// byte order mark (BOM) of UTF-16BE Unicode Big-endian
BOM_key = 'fffe';
buffer.swap(16);
}
buffer = buffer.toString(BOM_to_encoding[BOM_key]);
})) {
buffer = buffer.toString();
}
}
return buffer;
} catch (e) {
if (library_namespace.is_debug()) {
library_namespace.error(e);
}
// return e;
}
}
_.fs_read = fs_readFileSync;
/**
* 取得 .json 檔案內容,並回傳 {Object} 或錯誤時回傳 undefined。
*
* @param {String}file_path
* file path.
* @param {Object}[options]
* 附加參數/設定選擇性/特殊功能與選項
*
* @returns {Object}JavaScript object
* @returns {Undefined}錯誤時回傳
*/
function get_JSON_file(file_path, options) {
if (!/\.[^.\\\.]+$/i.test(file_path)
// auto add filename extension
&& (!options || !options.no_add_extension)) {
file_path += '.json';
}
var json = fs_readFileSync(file_path, options);
try {
if (json && (json = json.toString())) {
return JSON.parse(json);
}
} catch (e) {
// SyntaxError: Invalid JSON
if (library_namespace.is_debug())
library_namespace.error(e);
}
}
_.get_JSON = get_JSON_file;
/**
* fs.writeFileSync() without throw.
*
* @example <code>
CeL.fs_write(path, data, 'utf8');
* </code>
*
* @param {String}file_path
* file path.
* @param data
* data to write.
* @param {Object}[options]
* 附加參數/設定選擇性/特殊功能與選項
*
* @returns error
*/
function fs_writeFileSync(file_path, data, options) {
// console.trace(file_path);
if (options) {
var encoding;
if (typeof options === 'string') {
encoding = options;
} else {
encoding = options.encoding;
}
if (options.BOM) {
if (Buffer.isBuffer(data)) {
data = Buffer.concat([
Buffer(encoding === 'UTF-16BE' ? [ 0xfe, 0xff ] : [
0xff, 0xfe ]), data ]);
} else {
data = '\ufffe' + data;
}
}
if (!Buffer.isBuffer(data) && encoding === 'UTF-16BE') {
// encoding = 'UTF-16';
data = Buffer.from(data, _.file_utf16le_mapping[encoding])
// conversion between UTF-16 little-endian and UTF-16 big-endian
.swap16();
}
if (!Buffer.isBuffer(data) && (encoding in _.file_utf16le_mapping)) {
options = typeof options === 'string' ? Object.create(null)
: Object.clone(options);
options.encoding = encoding = _.file_utf16le_mapping[encoding];
}
}
// console.trace(options);
try {
node_fs.writeFileSync(file_path, data, options);
} catch (e) {
if (library_namespace.is_debug()) {
library_namespace.error([ 'fs_writeFileSync: ', {
// gettext_config:{"id":"cannot-save-data-to-file-$1"}
T : [ 'Cannot save data to file [%1]!', file_path ]
} ]);
console.error(e);
}
return e;
}
}
_.fs_write = fs_writeFileSync;
/**
* move file, fs.renameSync() without throw.
*
* WARNING: If the target path already exists, it will be overriden.
* https://linux.die.net/man/2/rename
*
* @param {String}move_from_path
* old file path.
* @param {String}move_to_path
* new file path.
*
* @returns error
*/
function fs_renameSync(move_from_path, move_to_path, base_path) {
if (base_path) {
base_path = append_path_separator(base_path);
move_from_path = base_path + move_from_path;
move_to_path = base_path + move_to_path;
}
try {
node_fs.renameSync(move_from_path, move_to_path);
} catch (e) {
library_namespace.error([ 'fs_renameSync: ', {
// gettext_config:{"id":"move-$1-to-$2-failed-$3"}
T : [ 'Move %1 to %2 failed: %3',
//
move_from_path, move_to_path, e.toString() ]
} ]);
return e;
}
}
_.fs_move = fs_renameSync;
// --------------------------------------------
if (false) {
require('./_for include/node.loader.js');
CeL.run('application.platform.nodejs');
CeL.traverse_file_system('.', function(path, fso_status, is_directory) {
console.log(path);
}, /\.js$/);
}
var KEY_root_process = typeof Symbol === 'function' ? Symbol('KEY_root_process')
: '\0KEY_root_process';
// https://github.com/coolaj86/node-walk
// https://github.com/oleics/node-filewalker/blob/master/lib/filewalker.js
function traverse_file_system(path, handler, options, depth) {
// 前置處理。
if (!options || !options[KEY_root_process]) {
if (library_namespace.is_RegExp(options)) {
options = {
filter : options
};
} else {
options = library_namespace.new_options(options);
}
options[KEY_root_process] = true;
}
if (!(depth >= 0)) {
depth = isNaN(options.depth) ? Infinity : options.depth | 0;
}
// console.trace(depth);
if (!(depth-- >= 1)) {
// depth === 1: 僅到本層為止。
return;
}
var list;
try {
list = node_fs.readdirSync(path);
} catch (e) {
library_namespace.debug({
// gettext_config:{"id":"no-file-or-directory-exists-$1"}
T : [ '不存在檔案或目錄:%1', path ]
});
return;
}
// console.log(list);
// treat path as directory
path = append_path_separator(path);
var filter = library_namespace.is_RegExp(options.filter)
&& options.filter;
// console.log([ depth, filter ]);
var callback = options.callback;
if (callback)
delete options.callback;
if (!(options.all_file_count > 0)) {
// Warning: options.all_file_count will auto-added
// after read new directory.
options.all_file_count = 0;
}
if (!(options.all_directory_count > 0)) {
// Warning: options.all_directory_count will auto-added
// after read new directory.
options.all_directory_count = 0;
}
function process_next_fso(promise, fso_name) {
var full_path = path + fso_name;
// https://nodejs.org/api/fs.html#fs_class_fs_stats
// maybe throw
var fso_status;
try {
fso_status = node_fs.lstatSync(full_path);
} catch (error) {
if (error.code === 'ENOENT') {
if (!options.ignore_error) {
// e.g., file name including ""
library_namespace
.error('traverse_file_system: Cannot access '
+ full_path);
console.error(error);
}
} else {
return promise.then(function() {
return Promise.reject(error);
});
}
return promise;
}
// else: e.g., is file
var is_directory = fso_status.isDirectory();
// 設定額外的屬性以供利用。
fso_status.name = fso_name;
fso_status.directory = path;
// console.log(full_path);
// Depth-first search (DFS)
if (is_directory) {
if (!filter || filter.test(fso_name)) {
options.all_directory_count++;
promise = promise.then(handler.bind(null, full_path,
fso_status, is_directory, options));
}
return promise.then(traverse_file_system.bind(null, full_path,
handler, options, depth));
}
if (!filter || filter.test(fso_name)) {
options.all_file_count++;
return promise.then(handler.bind(null, full_path, fso_status,
is_directory, options));
}
return promise;
}
return list.reduce(process_next_fso, Promise.resolve())
//
['catch'](function(error) {
// https://nodejs.org/api/errors.html
if (error.code === 'EPERM') {
// TODO: .chmodSync(path, 666) @ Windows??
// return;
}
if (error.code === 'EBUSY') {
// TODO
// return;
}
if (error.code !== 'ENOENT') {
// return;
}
throw error;
}).then(function(error) {
if (callback)
callback(error);
library_namespace.debug({
// gettext_config:{"id":"processing-completed-$1"}
T : [ '處理完畢:%1', path ]
}, 2, 'traverse_file_system');
});
}
_.traverse_file_system = traverse_file_system;
// --------------------------------------------
/**
* 取得目錄或檔案中最新的目錄或檔案。
*
* @param {String|Array}path
* 目錄或檔案路徑
* @param {Object}[options]
* 附加參數/設定選擇性/特殊功能與選項
*
* @returns {Undefined|Array} newest_fso_data = [newest_modify_time, path,
* file_status ]
*/
function get_newest_fso(path, options) {
if (typeof options === 'string') {
options = {
fso_type : options
};
} else {
options = library_namespace.setup_options(options);
}
function modify_time_of_fso_status(fso_status) {
return fso_status.mtimeMs || fso_status.mtime - 0;
}
if (Array.isArray(path)) {
return Promise.allSettled(path.map(function(_path) {
return get_newest_fso(_path, options);
})).then(function(results) {
var newest_modify_time, newest_fso_data;
results.forEach(function(result) {
result = result.status === "fulfilled" && result.value;
if (result && result[0] > 0
//
&& !(newest_modify_time >= result[0])) {
newest_modify_time = result[0];
newest_fso_data = result;
}
});
return newest_fso_data;
});
}
var fso_type = options.fso_type;
if (library_namespace.file_exists(path)) {
if (fso_type === 'directory')
return;
var file_status = library_namespace.fso_status(path);
var mtimeMs = modify_time_of_fso_status(file_status);
// console.trace([ mtimeMs, path, file_status ]);
return [ mtimeMs, path, file_status ];
}
var newest_modify_time, newest_fso_data;
return Promise.resolve(library_namespace.traverse_file_system(path,
//
function(path, fso_status, is_directory) {
if (is_directory ? fso_type === 'file' : fso_type === 'directory')
return;
var mtimeMs = modify_time_of_fso_status(fso_status);
if (mtimeMs > 0 && !(newest_modify_time >= mtimeMs)) {
newest_modify_time = mtimeMs;
newest_fso_data = [ newest_modify_time, path, fso_status ];
}
}, options)).then(function() {
return newest_fso_data;
});
}
_.get_newest_fso = get_newest_fso;
// --------------------------------------------
/**
* command line arguments 指令列參數
*
* https://github.com/nodejs/node/blob/master/doc/changelogs/CHANGELOG_V10.md#10.12.0
* The options parser now normalizes "_" to "-"; --no_warnings has the same
* effect as --no-warnings
*
* test code: <code>
* '-h|--help|-f=|--file=|-f=File|--file=File|File|file=File'.split('|').forEach(function(arg) { console.log(arg.match(/^(-{0,2})([^=]+?)(=(.*))?$/)); });
* </code>
*/
if (process.argv && process.argv.length > 2) {
// CeL.env.argv: see module.js
process.argv.slice(2).forEach(function(arg) {
var matched = arg.match(/^(-{0,2})([^=]+?)(=(.*))?$/);
if (!matched) {
// e.g., "=..."
this[''] = arg.slice(1);
return;
}
if (matched[2].startsWith('-')) {
library_namespace.warn([ 'platform.nodejs: ', {
// gettext_config:{"id":"invalid-command-line-argument-$1"}
T : [ 'Invalid command-line argument: [%1]', arg ]
} ]);
this[arg] = true;
return;
}
if (!matched[3]) {
// e.g., "script.js force": force=true
this[matched[2]] = true;
return;
}
// 在命令列設定這些值時,將會被轉換為所指定的值。
// 若是有必要將之當作字串值,必須特地加上引號。
this[matched[2]] = library_namespace.to_JS_value(matched[4]);
}, library_namespace.env.arg_hash
// ↑ use ((CeL.env.arg_hash)) to get command line arguments
= Object.create(null));
}
// --------------------------------------------
// WshShell.ExpandEnvironmentStrings()
function ExpandEnvironmentStrings(string) {
return string.replace(/%([a-z_\d]+)%/ig, function(all, variable) {
var value = process.env[variable];
// console.log(value);
return value === undefined ? all : value;
});
}
/**
* search $PATH, 搜尋可執行檔案的完整路徑。
*
* @example <code>
// cache the path of p7z executable file
var p7zip_path = CeL.executable_file_path('7z') || '%ProgramFiles%\\7-Zip\\7z.exe';
</code>
*
* @param {String}file_name
* 要搜尋的執行檔名。
* @param {Array}[search_path_list]
* 搜尋這些目錄路徑。
*
* @returns {String}可執行檔案的完整路徑。
*
* @see GitHub.updater.node.js
*/
function executable_file_path(file_name, search_path_list) {
if (!file_name)
return;
if (!Array.isArray(search_path_list)) {
search_path_list = String(search_path_list
// Unix: process.env.PATH, Windows: process.env.Path
|| process.env.PATH
// /usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:.
|| '');
// Unix: ':', Windows: ';'
search_path_list = search_path_list.split(search_path_list
// e.g., "C:\"
.includes(':\\') ? ';' : ':');
if (library_namespace.platform('windows')) {
// windows 會自動搜尋當前目錄 ".\\" 下的執行檔。
search_path_list.push('');
}
}
if (Array.isArray(file_name)) {
file_name.some(function(_file_name) {
return file_name = executable_file_path(_file_name,
search_path_list);
})
return file_name;
}
// is absolute path
var is_absolute_path;
// assert: {String}file_name
if (library_namespace.platform('windows')) {
// 直接給予包括 %environment variable% 的路徑名稱,在 Windows 下不用
// WshShell.ExpandEnvironmentStrings() 解析,亦可正常 **執行**。
// 但是採用 fs_status() 無法正常作動。
if (false && /%[a-z_]+%/i.test(file_name)) {
// TODO: using process.env.ProgramFiles
if (library_namespace.is_debug())
library_namespace
.warn('executable_file_path: Cannot handle '
+ file_name);
return file_name;
}
// e.g., for "%ProgramFiles%\\7-Zip\\7z.exe"
file_name = ExpandEnvironmentStrings(file_name);
if (/^[a-z]:\\/i.test(file_name)) {
is_absolute_path = true;
}
} else if (file_name.startsWith('/')) {
is_absolute_path = true;
}
if (is_absolute_path)
return fs_status(file_name) && file_name;
// console.log(search_path_list);
if (search_path_list
// .unique()
.some(function(directory) {
// directory === '': './'
if (directory && !directory_exists(directory)) {
return;
}
var exec_file_path = append_path_separator(directory || '.',
file_name);
// console.log('Test: ' + exec_file_path);
var fso_status = fs_status(exec_file_path);
if (!fso_status && library_namespace.platform('windows')
// env.PATHEXT @ Windows:
// .COM;.EXE;.BAT;.CMD;.VBS;.VBE;.JS;.JSE;.WSF;.WSH;.MSC
&& !/\.(?:exe|com|bat|cmd)$/i.test(exec_file_path)) {
fso_status = fs_status(exec_file_path + '.exe')
|| fs_status(exec_file_path + '.com');
}
// TODO: 應該測試是否可以執行。
if (fso_status) {
file_name = exec_file_path;
return true;
}
})) {
return file_name;
}
}
_.executable_file_path = executable_file_path;
// --------------------------------------------
// encode: ASCII string → Base64
// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/btoa
// https://gist.github.com/jmshal/b14199f7402c8f3a4568733d8bed0f25
// btoa 僅支持 ASCII
function btoa(stringToEncode) {
return Buffer.from(stringToEncode).toString('base64');
}
// decode: Base64 → ASCII string
// https://developer.mozilla.org/en-US/docs/Web/API/WindowOrWorkerGlobalScope/atob
function atob(encodedData) {
return Buffer.from(encodedData, 'base64').toString('binary');
}
// --------------------------------------------
// 為 electron-builder 📦安裝包/發行版
var is_installation_package = process.env.Apple_PubSub_Socket_Render;
if (!is_installation_package) {
is_installation_package = require.main && require.main.filename
// 2021/4/20 11:36:5 require.main===undefined @ new electron-builder
// package
// may use `module.filename`
|| module.filename;
is_installation_package = is_installation_package
// in electron-builder package: e.g.,
// "C:\Users\user_name\AppData\Local\Programs\work_crawler\resources\app.asar\gui_electron\gui_electron.html"
// NOT in electron-builder package: e.g.,
// "/program/work_crawler/gui_electron/gui_electron.html"
&& is_installation_package.replace(/[\\\/]app\.asar.+/, '')
//
=== process.resourcesPath && library_namespace.platform.OS;
}
if (false) {
// 這個方法僅能用在 app's main process file
(function() {
try {
// https://electronjs.org/docs/api/app
var updater = require("electron-updater"), autoUpdater = updater.autoUpdater;
is_installation_package = autoUpdater.app.isPackaged;
} catch (e) {
}
})();
}
_.is_installation_package = function() {
return is_installation_package;
};
// --------------------------------------------
function run_JScript(code, options) {
if (!library_namespace.platform('windows')) {
library_namespace.error([ 'run_JScript: ', {
T :
// gettext_config:{"id":"jscript-files-can-only-be-executed-in-windows-environment"}
'JScript 檔案只能在 Windows 環境下執行!'
} ]);
return;
}
// 前置處理。
if (typeof options === 'string') {
options = {
result_file_name : options
};
} else {
options = library_namespace.setup_options(options);
}
var script_file = append_path_separator(library_namespace.env.TEMP
|| library_namespace.env.TMP || '.', 'run_JScript.'
+ Math.random() + '.js');
// console.log('script_file: ' + script_file);
remove_fso(script_file);
if (options.attach_library) {
// console.log('attach library code: ' + run_JScript.library_code);
code = run_JScript.library_code + code;
}
var BOM = Buffer.from('fffe', 'hex');
code = Buffer.from(code, 'utf16le');
fs_writeFileSync(script_file, Buffer.concat([ BOM, code ], BOM.length
+ code.length));
var result, result_file_name = options.result_file_name;
if (result_file_name) {
remove_fso(result_file_name);
}
try {
result = child_process.spawnSync('CScript.exe', [ '//Nologo',
script_file ]);
} catch (e) {
// TODO: handle exception
}
// 去掉暫存檔(執行檔)
remove_fso(script_file);
if (result_file_name) {
// console.log('result_file_name: ' + result_file_name);
var result = /\.json$/i.test(result_file_name) ? get_JSON_file(
result_file_name, 'auto') : fs_readFileSync(
result_file_name, 'auto');
remove_fso(result_file_name);
// console.log(result);
return result;
}
return result;
}
_.run_JScript = run_JScript;
// 常用函數集。
run_JScript.library_code = "var WshShell=WScript.CreateObject('WScript.Shell'),FSO=WScript.CreateObject('Scripting.FileSystemObject'),WshProcessEnv=WshShell.Environment('Process'),"
// https://msdn.microsoft.com/ja-jp/library/cc364502.aspx
+ "tmp_dir=(WshProcessEnv('TEMP')||WshProcessEnv('TMP'))+'\\\\',"
// https://stackoverflow.com/questions/4388879/vbscript-output-to-console
+ "console={_stdout:FSO.GetStandardStream(1),_stderr:FSO.GetStandardStream(2),"
// + "log:function(m){console._stdout.WriteLine(m);},"
+ "log:function(m){WScript.Echo(m);},"
+ "error:function(m){console._stderr.WriteLine(m);}};"
+ "function add_quote(text){return '\"'+text.replace(/([\"\\\\])/g,'\\\\$1').replace(/[^\u0020-\u007e]/g,function($){$=$.charCodeAt(0).toString(16);return '\\\\u0000'.slice(0,-$.length)+$;})+'\"';}"
+ "function RegRead(key){try{return WshShell.RegRead(key);}catch(e){}}"
// WshShell.ExpandEnvironmentStrings('%TEMP%\\\\file_name')
// 2: ForWriting, -1: TristateTrue (Opens the file as Unicode)
+ "function write_file(file_name,content){var file=FSO.OpenTextFile(file_name,2,-1);content&&file.Write(content);file.Close();}";
// --------------------------------------------------------
// TODO:
// Buffer.prototype.indexOf()
// https://github.com/nodejs/node/blob/master/lib/buffer.js#L578
function export_function() {
library_namespace.set_method(node_fs, {
copy : fs_copy,
copySync : fs_copySync
}, null);
}
_['export'] = export_function;
// 當 require('fs') 得到同一 instance 時,才作 export。
if (node_fs === require('fs')) {
export_function();
}
library_namespace.set_method(library_namespace.env.global, {
btoa : btoa,
atob : atob
}, null);
return (_// JSDT:_module_
);
}