gecko/toolkit/components/osfile/ospath_win_back.jsm
2013-03-29 11:29:28 -04:00

301 lines
8.9 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.
*/
if (typeof Components != "undefined") {
this.EXPORTED_SYMBOLS = ["OS"];
let Scope = {};
Components.utils.import("resource://gre/modules/Services.jsm", Scope);
// Some tests need to import this module from any platform.
// We detect this by setting a bogus preference "toolkit.osfile.test.syslib_necessary"
// from the test suite
let syslib_necessary = true;
try {
syslib_necessary = Scope.Services.prefs.getBoolPref("toolkit.osfile.test.syslib_necessary");
} catch (x) {
// Ignore errors
}
try {
Components.utils.import("resource://gre/modules/osfile/osfile_win_allthreads.jsm", this);
} catch (ex if !syslib_necessary && ex.message.startsWith("Could not open system library:")) {
// Executing this module without a libc is acceptable for this test
}
}
(function(exports) {
"use strict";
if (!exports.OS) {
exports.OS = {};
}
if (!exports.OS.Win) {
exports.OS.Win = {};
}
if (exports.OS.Win.Path) {
return; // Avoid double-initialization
}
exports.OS.Win.Path = {
/**
* Return the final part of the path.
* The final part of the path is everything after the last "\\".
*/
basename: function basename(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);
},
/**
* 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.
*/
dirname: function dirname(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);
},
/**
* 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".
*/
join: function join(path /*...*/) {
let paths = [];
let root;
let absolute = false;
for each(let subpath in arguments) {
let drive = this.winGetDrive(subpath);
let abs = this.winIsAbsolute(subpath);
if (drive) {
root = drive;
let component = trimBackslashes(subpath.slice(drive.length));
if (component) {
paths = [component];
} else {
paths = [];
}
absolute = abs;
} else if (abs) {
paths = [trimBackslashes(subpath)];
absolute = true;
} else {
paths.push(trimBackslashes(subpath));
}
}
let result = "";
if (root) {
result += root;
}
if (absolute) {
result += "\\";
}
result += paths.join("\\");
return result;
},
/**
* 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 "\\\\").
*/
winGetDrive: function winGetDrive(path) {
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);
},
/**
* Return |true| if the path is absolute, |false| otherwise.
*
* We consider that a path is absolute if it starts with "\\"
* or "driveletter:\\".
*/
winIsAbsolute: function winIsAbsolute(path) {
let index = path.indexOf(":");
return path.length > index + 1 && path[index + 1] == "\\";
},
/**
* Normalize a path by removing any unneeded ".", "..", "\\".
* Also convert any "/" to a "\\".
*/
normalize: function normalize(path) {
let stack = [];
// 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);
// Normalize "/" to "\\"
path = path.replace("/", "\\");
// 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) {
result = "\\" + result;
}
if (root) {
result = root + result;
}
return result;
},
/**
* 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.
*/
split: function split(path) {
return {
absolute: this.winIsAbsolute(path),
winDrive: this.winGetDrive(path),
components: path.split("\\")
};
}
};
/**
* Utility function: Remove any leading/trailing backslashes
* from a string.
*/
let trimBackslashes = function trimBackslashes(string) {
return string.replace(/^\\+|\\+$/g,'');
};
exports.OS.Path = exports.OS.Win.Path;
}(this));