/* globals Wsh: false */
/* globals __dirname: false */
/* globals __filename: false */
/* eslint no-unused-vars: "off" */
/**
* The script file path which is specified 1st argument by cscript.exe or wscript.exe. It is not this script path.
*
* @global
* @name __filename
* @constant {string}
*/
if (!__filename) var __filename = String(WScript.ScriptFullName);
/**
* The parent directory path of `__filename`. Note the difference from {@link https://tuckn.net/docs/WshProcess/process.html#.cwd|process.cwd} that is `WScript.CreateObject('WScript.Shell').CurrentDirectory`
*
* @global
* @name __dirname
* @constant {string}
*/
if (!__dirname) var __dirname = __filename.replace(/\\[^\\]+$/, '');
(function () {
if (Wsh && Wsh.Path) return;
/**
* Shorthand of WScript.CreateObject('WScript.Shell')
*
* @namespace Shell
* @memberof Wsh
*/
if (!Wsh.Shell) Wsh.Shell = WScript.CreateObject('WScript.Shell');
/**
* Shorthand of WScript.CreateObject('Scripting.FileSystemObject')
*
* @namespace FileSystemObject
* @memberof Wsh
*/
if (!Wsh.FileSystemObject) {
Wsh.FileSystemObject = WScript.CreateObject('Scripting.FileSystemObject');
}
/**
* Adds functions (similar to Node.js path) that handles file path strings into WSH.
*
* @namespace Path
* @memberof Wsh
* @requires {@link https://github.com/tuckn/WshUtil|WshUtil}
*/
Wsh.Path = {};
// Shorthands
var util = Wsh.Util;
var sh = Wsh.Shell;
var fso = Wsh.FileSystemObject;
var isString = util.isString;
var isSolidString = util.isSolidString;
var isSameStr = util.isSameMeaning;
var includes = util.includes;
var endsWith = util.endsWith;
var startsWith = util.startsWith;
var path = Wsh.Path;
/** @constant {string} */
var MODULE_TITLE = 'WshPath/Path.js';
var throwErrNonStr = function (functionName, typeErrVal) {
util.throwTypeError('string', MODULE_TITLE, functionName, typeErrVal);
};
/**
* Windows path delimiter.
*
* @example
var path = Wsh.Path; // Shorthand
console.log(path.delimiter); // ;
* @name delimiter
* @memberof Wsh.Path
* @constant {string}
*/
path.delimiter = ';'; // for Windows
/**
* Windows path segment separator.
*
* @example
var path = Wsh.Path; // Shorthand
console.log(path.sep); // \
* @name sep
* @memberof Wsh.Path
* @constant {string}
*/
path.sep = '\\'; // for Windows
// path.isUNC {{{
/**
* Checks if the string is UNC ({@link https://en.wikipedia.org/wiki/Path_(computing)#Universal_Naming_Convention|Universal Naming Convention}).
*
* @example
var path = Wsh.Path;
path.isUNC('\\\\CompName'); // false
path.isUNC('\\\\CompName\\'); // false
path.isUNC('\\\\CompName\\SharedDir'); // true
// Long UNC
path.isUNC('\\\\?\\UNC\\CompName\\SharedDir'); // true
// LFS (Local File System)
path.isUNC('C:\\foo\\bar.baz'); // false
// Unix-like (POSIX)
path.isUNC('//CompName//SharedDir'); // false
* @function isUNC
* @memberof Wsh.Path
* @param {string} str - The string to check.
* @returns {boolean} - Returns true if the string is an UNC, else false.
*/
path.isUNC = function (str) {
if (!isString(str)) throwErrNonStr('path.isUNC', str);
return /^\\{2}(\?UNC\\)?[^/\\]+[/\\][^/\\]+/i.test(str);
}; // }}}
// path.dirname {{{
/**
* Returns the directory name of the path. Similar to {@link https://nodejs.org/api/path.html#path_path_dirname_path|Node.js Path}.
*
* @example
var path = Wsh.Path;
path.dirname('C:\\My Data\\image.jpg');
// Returns: 'C:\\My Data'
path.dirname('C:\\My Data');
// Returns: 'C:\\'
path.dirname('C:\\');
// Returns: 'C:\\'
path.dirname('D:\\logs\\*.txt');
// Returns: 'D:\\logs'
// UNC
path.dirname('\\\\CompName\\SharedDir\\photo.jpg');
// Returns: '\\\\CompName\\SharedDir\\'
path.dirname('\\\\CompName\\SharedDir');
// Returns: '\\\\CompName\\SharedDir'
// Unix-like (POSIX)
path.dirname('C:/foo/bar/baz/asdf/quux');
// Returns: 'C:/foo/bar/baz/asdf'
* @function dirname
* @memberof Wsh.Path
* @param {string} str - The path to convert.
* @returns {string} - The directory name of path.
*/
path.dirname = function (str) {
if (!isString(str)) throwErrNonStr('path.dirname', str);
/*
* @note Node.jsでは "C:\"に相当するUNCパスは"\\cpmp\dir\"としているようだ
* これはUNCに対するfso.GetParentFolderNameの動作とは異なる
*/
if (/^[/\\]{2}[^/\\]+[/\\]?$/i.test(str)) return path.sep;
if (/^[/\\]{2}[^/\\]+[/\\][^/\\]+[/\\]?$/i.test(str)) return str;
if (/^[a-z]:[/\\]?$/i.test(str)) return str;
var dirname = fso.GetParentFolderName(str);
if (dirname === '') return '.';
/*
* @note fso.GetParentFolderNameは"\\cpmp\dir\..."のとき"\\cpmp\dir"となる
* これはNode.jsの動作とは異なるのであわせるため末尾に\\を付与
*/
if (/^[/\\]{2}[^/\\]+[/\\][^/\\]+$/i.test(dirname)) {
return dirname + path.sep;
}
return dirname;
}; // }}}
// path.basename {{{
/**
* Returns the last portion of the path all given path segments. Similar to {@link https://nodejs.org/api/path.html#path_path_basename_path_ext|Node.js Path}.
*
* @example
var path = Wsh.Path;
path.basename('C:\\foo\\bar\\baz\\quux.html');
// Returns: 'quux.html'
path.basename('C:\\foo\\bar\\baz\\quux.html', '.html');
// Returns: 'quux'
path.basename('C:\\my-repo\\.gitignore', '.gitignore');
// Returns: '.gitignore'
path.basename('C:\\My Data');
// Returns: 'My Data'
// UNC
path.basename('\\\\CompName\\SharedDir\\photo.jpg');
// Returns: 'photo.jpg'
path.basename('\\\\CompName\\SharedDir');
// Returns: 'SharedDir'
path.basename('\\\\CompName\\');
// Returns: 'CompName'
* @function basename
* @memberof Wsh.Path
* @param {string} str - The path to convert.
* @param {string} [ext] - An optional file extension.
* @returns {string} - The last position of the path.
*/
path.basename = function (str, ext) {
if (!isString(str)) throwErrNonStr('path.basename', str);
// @note Imitate the behavior of Node.js path.basename
if (/^[/\\]{2}/i.test(str)) {
str = str.replace(/^[/\\]{2}/, '');
}
var baseName = fso.GetFileName(str); // @NOTE basename = name + ext
var remExtName;
if (isSolidString(ext)) {
remExtName = fso.GetFileName(baseName).replace(new RegExp(ext + '$'), '');
if (remExtName === '') return baseName;
return remExtName;
}
return baseName;
}; // }}}
// path.extname {{{
/**
* Returns the extension of the path. Similar to {@link https://nodejs.org/api/path.html#path_path_extname_path|Node.js Path}.
*
* @example
var path = Wsh.Path;
path.extname('index.html');
// Returns: '.html'
path.extname('index.coffee.md');
// Returns: '.md'
path.extname('.gitignore');
// Returns: ''
path.extname('C:\\foo\\bar\\baz\\quux.html');
// Returns: '.html'
// UNC
path.extname('\\\\CompName\\SharedDir\\photo.jpg');
// Returns: '.jpg'
path.extname('\\\\CompName\\SharedDir');
// Returns: ''
* @function extname
* @memberof Wsh.Path
* @param {string} str - The path to convert.
* @returns {string} - the extension of the path.
*/
path.extname = function (str) {
if (!isString(str)) throwErrNonStr('path.extname', str);
if (endsWith(str, '.')) return '.';
var baseName = fso.GetFileName(str);
var extName = '.' + fso.GetExtensionName(str);
if (extName === baseName) return '';
return (extName === '.') ? '' : extName;
}; // }}}
// path.parse {{{
/**
* Returns an object whose properties. Similar to {@link https://nodejs.org/api/path.html#path_path_parse_path|Node.js Path}.
*
* @example
var path = Wsh.Path;
path.parse('C:\\home\\user\\dir\\file.txt');
// Returns:
// { root: 'C:\\',
// dir: 'C:\\home\\user\\dir',
// base: 'file.txt',
// ext: '.txt',
// name: 'file' };
path.parse('\\\\CompName\\Public\\photo3.jpg');
// Returns:
// { root: '\\\\CompName\\Public\\',
// dir: '\\\\CompName\\Public\\',
// base: 'photo3.jpg',
// ext: '.jpg',
// name: 'photo3' };
path.parse('C:\\.git');
// Returns:
// { root: 'C:\\',
// dir: 'C:\\',
// base: '.git',
// ext: '',
// name: '.git' };
// Unix-like (POSIX)
path.parse('C:/home/user/dir/file.txt');
// Returns:
// { root: 'C:/',
// dir: 'C:/home/user/dir',
// base: 'file.txt',
// ext: '.txt',
// name: 'file' };
* @function parse
* @memberof Wsh.Path
* @param {string} str - The path to parse.
* @returns {object} - The parsed object.
*/
path.parse = function (str) {
if (!isString(str)) throwErrNonStr('path.parse', str);
var root = '';
if (path.isUNC(str)) {
root = str.match(/^([/\\]{2}[^/\\]+[/\\]?([^/\\]+)?[/\\]?)/i)[1];
} else if (/^[a-z]:[/\\]?/i.test(str)) {
root = str.match(/^([a-z]:[/\\]?)/i)[1];
}
var baseName = path.basename(str); // 'bar.js' (basename = name + ext)
var extName = path.extname(str); // '.js'
return {
root: root,
dir: path.dirname(str),
base: baseName,
ext: extName,
name: path.basename(baseName, extName)
};
}; // }}}
// path.isAbsolute {{{
/**
* Determines if path is an absolute path. Similar to {@link https://nodejs.org/api/path.html#path_path_isabsolute_path|Node.js Path}.
*
* @example
var path = Wsh.Path;
path.isAbsolute('C:\\My Data\\hoge.png'); // true
path.isAbsolute('C:\\My Data\\..'); // true
path.isAbsolute('bar\\baz'); // false
path.isAbsolute('.'); // false
// UNC
path.isAbsolute('\\\\CompName'); // true
// Unix-like (POSIX)
path.isAbsolute('C:/My Data/hoge.png'); // true
path.isAbsolute('C:/My Data/..'); // true
path.isAbsolute('//CompName'); // true
path.isAbsolute('bar/baz'); // false
* @function isAbsolute
* @memberof Wsh.Path
* @param {string} str - The path to check.
* @returns {boolean} - Returns true if the string is an absolute, else false.
*/
path.isAbsolute = function (str) {
if (!isString(str)) throwErrNonStr('path.isAbsolute', str);
return /^([a-z]:|[/\\]{2}[^/\\]+)/i.test(str);
}; // }}}
// path.normalize {{{
/**
* Resolve '..' and '.' segments. Replace path segment separation characters. Similar to {@link https://nodejs.org/api/path.html#path_path_normalize_path|Node.js Path}.
*
* @todo '~' to '%USERPROFILE%'
* @example
var path = Wsh.Path;
path.normalize('C:\\temp\\\\foo\\bar\\..\\');
// Returns: 'C:\\temp\\foo\\'
path.normalize('C:////temp\\\\/\\/\\/foo/bar');
// Returns: 'C:\\temp\\foo\\bar'
// bash.exe は/C/~がC:\を意味するが、それは解釈しない
path.normalize('C/Git/mingw64/bin/git.exe');
// Returns: 'C\\Git\\mingw64\\bin\\git.exe'
path.normalize('C:\\Git\\mingw64\\lib\\..\\etc\\.gitconfig');
// Returns: 'C:\\Git\\mingw64\\etc\\.gitconfig'
path.normalize('..\\.config\\settings.json');
// Returns: '..\\.config\\settings.json'
path.normalize('settings.json');
// Returns: 'settings.json'
* @function normalize
* @memberof Wsh.Path
* @param {string} str - The path to convert.
* @returns {string} - The formatted path.
*/
path.normalize = function (str) {
if (!isString(str)) throwErrNonStr('path.isAbsolute', str);
if (str === '') return '.';
if (str === '.' || str === '..') return str;
// @TODO Replaces '\\' to path.sep.
var ph = str.replace(/\//g, path.sep);
if (!includes(ph, path.sep)) return ph;
var matches;
if (/^[a-z]:[\\]+/i.test(ph)) { // Windows
matches = ph.match(/^([a-z]:)[\\]+(.*)/i);
} else if (path.isUNC(ph)) { // UNC
matches = ph.match(/^(\\\\[^\\]+)[\\]+(.*)/i);
} else if (/^[^\\]+\\/i.test(ph)) { // Linux like
matches = ph.match(/^([^\\]+)[\\]+(.*)/i);
} else {
matches = [null, '', ph];
}
var head = matches[1];
var tail = matches[2];
var branches = tail.split(path.sep);
var normalized = branches.reduce(function (acc, branch) {
if (!isSolidString(branch) || branch === '.') {
return acc;
} else if (branch === '..') {
return acc.replace(/[^\\]+\\?$/, '');
} else {
return fso.BuildPath(acc, branch);
}
}, '');
if (head !== '') normalized = head + path.sep + normalized;
// Restore the last '\\' <- 2 characters!!
// if (!endsWith(str, path.sep)) normalized += path.sep;
return normalized;
}; // }}}
// path.join {{{
/**
* Joins all given path segments. Similar to {@link https://nodejs.org/api/path.html#path_path_join_paths|Node.js Path}.
*
* @example
var path = Wsh.Path;
path.join('C:', 'Program Files (x86)', 'Microsoft.NET', 'RedistList', 'AssemblyList_4_client.xml');
// Returns: 'C:\\Program Files (x86)\\Microsoft.NET\\RedistList\\AssemblyList_4_client.xml'
path.join('C:\\Git\\', '\\mingw64\\', 'lib', '..\\etc', '.gitconfig');
// Returns: 'C:\\Git\\mingw64\\etc\\.gitconfig''
var argVar = ['mingw64\\lib', '..\\etc', '.gitconfig'];
path.join.apply(null, argVar);
// Returns: 'mingw64\\etc\\.gitconfig'
path.join('\\\\CompName', 'Public', 'photo3.jpg');
// Returns: '\\\\CompName\\Public\\photo3.jpg'
* @function join
* @memberof Wsh.Path
* @param {...string} arguments - Parts of the file path.
* @returns {string} - The Joined patha
*/
path.join = function (/* ...parts */) {
var parts = Array.from(arguments);
var pth = parts.shift();
if (endsWith(pth, ':')) pth += path.sep;
parts.forEach(function (part) {
if (!isString(part)) throwErrNonStr('path.join', part);
pth = fso.BuildPath(pth, part);
});
return path.normalize(pth);
}; // }}}
// path.resolve {{{
/**
* Resolves the sequence of paths or path segments into an absolute path. Similar to {@link https://nodejs.org/api/path.html#path_path_resolve_paths|Node.js Path}.
*
* @example
var path = Wsh.Path;
path.resolve('');
// Returns: <The current direcotry path>
path.resolve('C:', 'Program Files (x86)', 'Microsoft.NET', 'RedistList', 'AssemblyList_4_client.xml');
// Returns: 'C:\\Program Files (x86)\\Microsoft.NET\\RedistList\\AssemblyList_4_client.xml'
path.resolve('C:\\Git\\', '\\mingw64\\', 'lib', '..\\etc', '.gitconfig');
// Returns: 'C:\\Git\\mingw64\\etc\\.gitconfig'
path.resolve('mingw64\\lib', '..\\etc', '.gitconfig');
// Returns: '<Current Path>\\mingw64\\etc\\.gitconfig'
path.resolve('\\\\CompName', 'Public', 'photo3.jpg');
// Returns: '\\\\CompName\\Public\\photo3.jpg'
var argVar = ['C:\\Git', 'D:\\bin\\git\\', '\\etc', '.gitconfig'];
path.resolve.apply(null, argVar);
// Returns: 'D:\\bin\\git\\etc\\.gitconfig'
* @function resolve
* @memberof Wsh.Path
* @param {...string} arguments - Parts of the file path.
* @returns {string} - The resolved path.
*/
path.resolve = function (/* ...parts */) {
var parts = Array.from(arguments);
var pth = parts.shift();
if (endsWith(pth, ':')) pth += path.sep;
/**
* Retrieve or change the current directory.
*
* @name CurrentDirectory
* @type {string}
* @memberof Wsh.Shell
*/
if (!path.isAbsolute(pth)) pth = path.join(sh.CurrentDirectory, pth);
parts.forEach(function (part) {
if (!isString(part)) throwErrNonStr('path.resolve', part);
if (/^[a-z]:/i.test(part)) {
pth = part;
} else {
pth = fso.BuildPath(pth, part);
}
});
return path.normalize(pth);
}; // }}}
// path.toUNC {{{
/**
* Convert the LFS (Local File System) path to UNC.
*
* @example
var path = Wsh.Path;
path.toUNC('//CompName');
// Returns: '//CompName'
path.toUNC('C:\\foo\\bar.baz');
// Returns: '\\\\<Your CompName>\\C$\\foo\\bar.baz'
// Unix-like (POSIX)
path.toUNC('C:/foo/bar.baz');
// Returns: '\\\\<Your CompName>\\C$\\foo\\bar.baz'
path.toUNC('');
// Returns: ''
* @function toUNC
* @memberof Wsh.Path
* @param {string} str - The path to convert.
* @returns {string} - The UNC path.
*/
path.toUNC = function (str) {
if (!isString(str)) throwErrNonStr('path.toUNC', str);
if (str === '') return '';
if (/^[/\\]{2}[^/\\]+/i.test(str)) return str;
var network = WScript.CreateObject('WScript.Network');
var compName = String(network.ComputerName);
network = null;
return path.resolve(str).replace(/^([a-z]):/i, '\\\\' + compName + '\\$1$');
}; // }}}
// path.relative {{{
/**
* Returns the relative path from fromDir to toPath. Similar to {@link https://nodejs.org/api/path.html#path_path_relative_from_to|Node.js Path}.
*
* @example
var path = Wsh.Path;
var pathA, pathB;
pathA = 'C:\\MyApps\\Gimp';
pathB = 'D:\\Apps\\ImageMagick\\convert.exe';
path.relative(pathA, pathB);
// Returns: 'D:\\Apps\\ImageMagick\\convert.exe'
pathA = 'C:\\MyApps';
pathB = 'C:\\MyApps\\convert.exe';
path.relative(pathA, pathB);
// Returns: 'convert.exe'
pathA = 'C:\\MyApps\\Paint\\Gimp';
pathB = 'C:\\MyApps\\Converter\\ImageMagick\\convert.exe';
path.relative(pathA, pathB);
// Returns: '..\\..\\Converter\\ImageMagick\\convert.exe'
pathA = 'C:\\MyApps\\Gimp';
pathB = 'C:\\ImageMagick\\convert.exe';
path.relative(pathA, pathB);
// Returns: '..\\..\\ImageMagick\\convert.exe'
pathA = 'C:\\MyApps\\Paint\\Gimp';
pathB = 'C:\\convert.exe';
path.relative(pathA, pathB);
// Returns: '..\\..\\..\\convert.exe'
* @function relative
* @memberof Wsh.Path
* @param {string} fromDir - The start directory path.
* @param {string} toPath - The end path.
* @returns {string} - The relative path.
*/
path.relative = function (fromDir, toPath) {
if (!isString(fromDir)) throwErrNonStr('path.relative', fromDir);
if (!isString(toPath)) throwErrNonStr('path.relative', toPath);
if (fromDir === '' && toPath === '') return '';
fromDir = path.resolve(fromDir);
toPath = path.resolve(toPath);
if (!isSameStr(path.parse(fromDir).root, path.parse(toPath).root)) {
return toPath;
}
var fromNodes = fromDir.split(path.sep);
var toNodes = toPath.split(path.sep);
var rtnRelPath = toPath;
var matchedNum = 0;
for (var i = 0, I = fromNodes.length; i < I; i++) {
if (isSameStr(fromNodes[i], toNodes[i])) {
rtnRelPath = rtnRelPath.replace(toNodes[i] + path.sep, '');
matchedNum++;
} else {
break;
}
}
var restFrom = fromNodes.length - matchedNum;
if (restFrom === 0) {
rtnRelPath = '.' + path.sep + rtnRelPath;
} else {
var upNum = restFrom;
while (upNum-- > 0) {
rtnRelPath = '..' + path.sep + rtnRelPath;
}
}
if (startsWith(rtnRelPath, '.' + path.sep)) { // Remove .\\
return rtnRelPath.slice(('.' + path.sep).length);
}
return rtnRelPath;
}; // }}}
})();
// vim:set foldmethod=marker commentstring=//%s :