mirror of
https://github.com/mozilla/gecko-dev.git
synced 2024-12-27 02:43:07 +00:00
365 lines
9.5 KiB
JavaScript
365 lines
9.5 KiB
JavaScript
/**
|
|
* Handling native paths.
|
|
*
|
|
* This module contains a number of functions destined to simplify
|
|
* working with native paths through a cross-platform API. Functions
|
|
* of this module will only work with the following assumptions:
|
|
*
|
|
* - paths are valid;
|
|
* - paths are defined with one of the grammars that this module can
|
|
* parse (see later);
|
|
* - all path concatenations go through function |join|.
|
|
*
|
|
* Limitations of this implementation.
|
|
*
|
|
* Windows supports 6 distinct grammars for paths. For the moment, this
|
|
* implementation supports the following subset:
|
|
*
|
|
* - drivename:backslash-separated components
|
|
* - backslash-separated components
|
|
* - \\drivename\ followed by backslash-separated components
|
|
*
|
|
* Additionally, |normalize| can convert a path containing slash-
|
|
* separated components to a path containing backslash-separated
|
|
* components.
|
|
*/
|
|
|
|
"use strict";
|
|
|
|
// Boilerplate used to be able to import this module both from the main
|
|
// thread and from worker threads.
|
|
if (typeof Components != "undefined") {
|
|
Components.utils.importGlobalProperties(["URL"]);
|
|
// Global definition of |exports|, to keep everybody happy.
|
|
// In non-main thread, |exports| is provided by the module
|
|
// loader.
|
|
this.exports = {};
|
|
} else if (typeof "module" == "undefined" || typeof "exports" == "undefined") {
|
|
throw new Error("Please load this module using require()");
|
|
}
|
|
|
|
let EXPORTED_SYMBOLS = [
|
|
"basename",
|
|
"dirname",
|
|
"join",
|
|
"normalize",
|
|
"split",
|
|
"winGetDrive",
|
|
"winIsAbsolute",
|
|
"toFileURI",
|
|
"fromFileURI",
|
|
];
|
|
|
|
/**
|
|
* Return the final part of the path.
|
|
* The final part of the path is everything after the last "\\".
|
|
*/
|
|
let basename = function(path) {
|
|
if (path.startsWith("\\\\")) {
|
|
// UNC-style path
|
|
let index = path.lastIndexOf("\\");
|
|
if (index != 1) {
|
|
return path.slice(index + 1);
|
|
}
|
|
return ""; // Degenerate case
|
|
}
|
|
return path.slice(Math.max(path.lastIndexOf("\\"),
|
|
path.lastIndexOf(":")) + 1);
|
|
};
|
|
exports.basename = basename;
|
|
|
|
/**
|
|
* Return the directory part of the path.
|
|
*
|
|
* If the path contains no directory, return the drive letter,
|
|
* or "." if the path contains no drive letter or if option
|
|
* |winNoDrive| is set.
|
|
*
|
|
* Otherwise, return everything before the last backslash,
|
|
* including the drive/server name.
|
|
*
|
|
*
|
|
* @param {string} path The path.
|
|
* @param {*=} options Platform-specific options controlling the behavior
|
|
* of this function. This implementation supports the following options:
|
|
* - |winNoDrive| If |true|, also remove the letter from the path name.
|
|
*/
|
|
let dirname = function(path, options) {
|
|
let noDrive = (options && options.winNoDrive);
|
|
|
|
// Find the last occurrence of "\\"
|
|
let index = path.lastIndexOf("\\");
|
|
if (index == -1) {
|
|
// If there is no directory component...
|
|
if (!noDrive) {
|
|
// Return the drive path if possible, falling back to "."
|
|
return this.winGetDrive(path) || ".";
|
|
} else {
|
|
// Or just "."
|
|
return ".";
|
|
}
|
|
}
|
|
|
|
if (index == 1 && path.charAt(0) == "\\") {
|
|
// The path is reduced to a UNC drive
|
|
if (noDrive) {
|
|
return ".";
|
|
} else {
|
|
return path;
|
|
}
|
|
}
|
|
|
|
// Ignore any occurrence of "\\: immediately before that one
|
|
while (index >= 0 && path[index] == "\\") {
|
|
--index;
|
|
}
|
|
|
|
// Compute what is left, removing the drive name if necessary
|
|
let start;
|
|
if (noDrive) {
|
|
start = (this.winGetDrive(path) || "").length;
|
|
} else {
|
|
start = 0;
|
|
}
|
|
return path.slice(start, index + 1);
|
|
};
|
|
exports.dirname = dirname;
|
|
|
|
/**
|
|
* Join path components.
|
|
* This is the recommended manner of getting the path of a file/subdirectory
|
|
* in a directory.
|
|
*
|
|
* Example: Obtaining $TMP/foo/bar in an OS-independent manner
|
|
* var tmpDir = OS.Constants.Path.tmpDir;
|
|
* var path = OS.Path.join(tmpDir, "foo", "bar");
|
|
*
|
|
* Under Windows, this will return "$TMP\foo\bar".
|
|
*/
|
|
let join = function(...path) {
|
|
let paths = [];
|
|
let root;
|
|
let absolute = false;
|
|
for (let subpath of path) {
|
|
if (subpath == null) {
|
|
throw new TypeError("invalid path component");
|
|
}
|
|
let drive = this.winGetDrive(subpath);
|
|
if (drive) {
|
|
root = drive;
|
|
let component = trimBackslashes(subpath.slice(drive.length));
|
|
if (component) {
|
|
paths = [component];
|
|
} else {
|
|
paths = [];
|
|
}
|
|
absolute = true;
|
|
} else if (this.winIsAbsolute(subpath)) {
|
|
paths = [trimBackslashes(subpath)];
|
|
absolute = true;
|
|
} else {
|
|
paths.push(trimBackslashes(subpath));
|
|
}
|
|
}
|
|
let result = "";
|
|
if (root) {
|
|
result += root;
|
|
}
|
|
if (absolute) {
|
|
result += "\\";
|
|
}
|
|
result += paths.join("\\");
|
|
return result;
|
|
};
|
|
exports.join = join;
|
|
|
|
/**
|
|
* Return the drive name of a path, or |null| if the path does
|
|
* not contain a drive name.
|
|
*
|
|
* Drive name appear either as "DriveName:..." (the return drive
|
|
* name includes the ":") or "\\\\DriveName..." (the returned drive name
|
|
* includes "\\\\").
|
|
*/
|
|
let winGetDrive = function(path) {
|
|
if (path == null) {
|
|
throw new TypeError("path is invalid");
|
|
}
|
|
|
|
if (path.startsWith("\\\\")) {
|
|
// UNC path
|
|
if (path.length == 2) {
|
|
return null;
|
|
}
|
|
let index = path.indexOf("\\", 2);
|
|
if (index == -1) {
|
|
return path;
|
|
}
|
|
return path.slice(0, index);
|
|
}
|
|
// Non-UNC path
|
|
let index = path.indexOf(":");
|
|
if (index <= 0) return null;
|
|
return path.slice(0, index + 1);
|
|
};
|
|
exports.winGetDrive = winGetDrive;
|
|
|
|
/**
|
|
* Return |true| if the path is absolute, |false| otherwise.
|
|
*
|
|
* We consider that a path is absolute if it starts with "\\"
|
|
* or "driveletter:\\".
|
|
*/
|
|
let winIsAbsolute = function(path) {
|
|
let index = path.indexOf(":");
|
|
return path.length > index + 1 && path[index + 1] == "\\";
|
|
};
|
|
exports.winIsAbsolute = winIsAbsolute;
|
|
|
|
/**
|
|
* Normalize a path by removing any unneeded ".", "..", "\\".
|
|
* Also convert any "/" to a "\\".
|
|
*/
|
|
let normalize = function(path) {
|
|
let stack = [];
|
|
|
|
if (!path.startsWith("\\\\")) {
|
|
// Normalize "/" to "\\"
|
|
path = path.replace(/\//g, "\\");
|
|
}
|
|
|
|
// Remove the drive (we will put it back at the end)
|
|
let root = this.winGetDrive(path);
|
|
if (root) {
|
|
path = path.slice(root.length);
|
|
}
|
|
|
|
// Remember whether we need to restore a leading "\\" or drive name.
|
|
let absolute = this.winIsAbsolute(path);
|
|
|
|
// And now, fill |stack| from the components,
|
|
// popping whenever there is a ".."
|
|
path.split("\\").forEach(function loop(v) {
|
|
switch (v) {
|
|
case "": case ".": // Ignore
|
|
break;
|
|
case "..":
|
|
if (stack.length == 0) {
|
|
if (absolute) {
|
|
throw new Error("Path is ill-formed: attempting to go past root");
|
|
} else {
|
|
stack.push("..");
|
|
}
|
|
} else {
|
|
if (stack[stack.length - 1] == "..") {
|
|
stack.push("..");
|
|
} else {
|
|
stack.pop();
|
|
}
|
|
}
|
|
break;
|
|
default:
|
|
stack.push(v);
|
|
}
|
|
});
|
|
|
|
// Put everything back together
|
|
let result = stack.join("\\");
|
|
if (absolute || root) {
|
|
result = "\\" + result;
|
|
}
|
|
if (root) {
|
|
result = root + result;
|
|
}
|
|
return result;
|
|
};
|
|
exports.normalize = normalize;
|
|
|
|
/**
|
|
* Return the components of a path.
|
|
* You should generally apply this function to a normalized path.
|
|
*
|
|
* @return {{
|
|
* {bool} absolute |true| if the path is absolute, |false| otherwise
|
|
* {array} components the string components of the path
|
|
* {string?} winDrive the drive or server for this path
|
|
* }}
|
|
*
|
|
* Other implementations may add additional OS-specific informations.
|
|
*/
|
|
let split = function(path) {
|
|
return {
|
|
absolute: this.winIsAbsolute(path),
|
|
winDrive: this.winGetDrive(path),
|
|
components: path.split("\\")
|
|
};
|
|
};
|
|
exports.split = split;
|
|
|
|
/**
|
|
* Return the file:// URI file path of the given local file path.
|
|
*/
|
|
// The case of %3b is designed to match Services.io, but fundamentally doesn't matter.
|
|
let toFileURIExtraEncodings = {';': '%3b', '?': '%3F', "'": '%27', '#': '%23'};
|
|
let toFileURI = function toFileURI(path) {
|
|
// URI-escape forward slashes and convert backward slashes to forward
|
|
path = this.normalize(path).replace(/[\\\/]/g, m => (m=='\\')? '/' : '%2F');
|
|
let uri = encodeURI(path);
|
|
|
|
// add a prefix, and encodeURI doesn't escape a few characters that we do
|
|
// want to escape, so fix that up
|
|
let prefix = "file:///";
|
|
uri = prefix + uri.replace(/[;?'#]/g, match => toFileURIExtraEncodings[match]);
|
|
|
|
// turn e.g., file:///C: into file:///C:/
|
|
if (uri.charAt(uri.length - 1) === ':') {
|
|
uri += "/"
|
|
}
|
|
|
|
return uri;
|
|
};
|
|
exports.toFileURI = toFileURI;
|
|
|
|
/**
|
|
* Returns the local file path from a given file URI.
|
|
*/
|
|
let fromFileURI = function fromFileURI(uri) {
|
|
let url = new URL(uri);
|
|
if (url.protocol != 'file:') {
|
|
throw new Error("fromFileURI expects a file URI");
|
|
}
|
|
|
|
// strip leading slash, since Windows paths don't start with one
|
|
uri = url.pathname.substr(1);
|
|
|
|
let path = decodeURI(uri);
|
|
// decode a few characters where URL's parsing is overzealous
|
|
path = path.replace(/%(3b|3f|23)/gi,
|
|
match => decodeURIComponent(match));
|
|
path = this.normalize(path);
|
|
|
|
// this.normalize() does not remove the trailing slash if the path
|
|
// component is a drive letter. eg. 'C:\'' will not get normalized.
|
|
if (path.endsWith(":\\")) {
|
|
path = path.substr(0, path.length - 1);
|
|
}
|
|
return this.normalize(path);
|
|
};
|
|
exports.fromFileURI = fromFileURI;
|
|
|
|
/**
|
|
* Utility function: Remove any leading/trailing backslashes
|
|
* from a string.
|
|
*/
|
|
let trimBackslashes = function trimBackslashes(string) {
|
|
return string.replace(/^\\+|\\+$/g,'');
|
|
};
|
|
|
|
//////////// Boilerplate
|
|
if (typeof Components != "undefined") {
|
|
this.EXPORTED_SYMBOLS = EXPORTED_SYMBOLS;
|
|
for (let symbol of EXPORTED_SYMBOLS) {
|
|
this[symbol] = exports[symbol];
|
|
}
|
|
}
|