1
0
mirror of https://github.com/gryf/pentadactyl-pm.git synced 2025-12-20 06:07:59 +01:00

Add IO#File.

New review:
owner: dougkearns
I like this for the most part, except that it has to go to lengths
to wrap the original nsIFile correctly, an that it can't be passed
directly to other XPCOM components. It makes most operations on
files a lot cleaner, though.
This commit is contained in:
Kris Maglione
2009-10-31 17:40:16 -04:00
parent d880d4afcb
commit 7a9889b18c
9 changed files with 244 additions and 232 deletions

View File

@@ -177,7 +177,7 @@ function Browser() //{{{
{
if (/^file:\/|^\//.test(url))
{
let file = io.getFile(url);
let file = io.File(url);
return file.exists() && file.isDirectory();
}
else

View File

@@ -181,7 +181,7 @@ function Buffer() //{{{
function openUploadPrompt(elem)
{
commandline.input("Upload file: ", function (path) {
let file = io.getFile(path);
let file = io.File(path);
if (!file.exists())
return void liberator.beep();
@@ -541,7 +541,7 @@ function Buffer() //{{{
if (arg)
{
options.setPref("print.print_to_file", "true");
options.setPref("print.print_to_filename", io.getFile(arg.substr(1)).path);
options.setPref("print.print_to_filename", io.File(arg.substr(1)).path);
liberator.echomsg("Printing to file: " + arg.substr(1));
}
else
@@ -617,7 +617,7 @@ function Buffer() //{{{
if (filename)
{
let file = io.getFile(filename);
let file = io.File(filename);
if (file.exists() && !args.bang)
return void liberator.echoerr("E13: File exists (add ! to override)");

View File

@@ -892,16 +892,16 @@ function Editor() //{{{
textBox.style.backgroundColor = "#bbbbbb";
}
if (!io.writeFile(tmpfile, text))
throw "Input contains characters not valid in the current " +
"file encoding";
if (!tmpfile.write(text))
throw Error("Input contains characters not valid in the current " +
"file encoding");
this.editFileExternally(tmpfile.path);
if (textBox)
textBox.removeAttribute("readonly");
let val = io.readFile(tmpfile);
let val = tmpfile.read();
if (textBox)
textBox.value = val;
else

View File

@@ -563,21 +563,20 @@ function Events() //{{{
for (let [, dir] in Iterator(dirs))
{
liberator.echomsg('Searching for "macros/*" in "' + dir.path + '"', 2);
liberator.log("Sourcing macros directory: " + dir.path + "...", 3);
let files = io.readDirectory(dir.path);
files.forEach(function (file) {
if (!file.exists() || file.isDirectory() ||
!file.isReadable() || !/^[\w_-]+(\.vimp)?$/i.test(file.leafName))
return;
for (let file in dir.iterDirectory())
{
if (file.exists() && !file.isDirectory() && file.isReadable() &&
/^[\w_-]+(\.vimp)?$/i.test(file.leafName))
{
let name = file.leafName.replace(/\.vimp$/i, "");
macros.set(name, io.readFile(file).split("\n")[0]);
macros.set(name, file.read().split("\n")[0]);
liberator.log("Macro " + name + " added: " + macros.get(name), 5);
});
}
}
}
}
else

View File

@@ -77,7 +77,6 @@ function IO() //{{{
{
if (!list)
return [];
else
// empty list item means the current directory
return list.replace(/,$/, "").split(",")
.map(function (dir) dir == "" ? io.getCurrentDirectory().path : dir);
@@ -87,7 +86,7 @@ function IO() //{{{
function joinPaths(head, tail)
{
let path = self.getFile(head);
let path = self.File(head);
try
{
path.appendRelativePath(self.expandPath(tail, true)); // FIXME: should only expand env vars and normalise path separators
@@ -257,7 +256,7 @@ function IO() //{{{
return void liberator.echoerr("E172: Only one file name allowed");
let filename = args[0] || io.getRCFile(null, true).path;
let file = io.getFile(filename);
let file = io.File(filename);
if (file.exists() && !args.bang)
return void liberator.echoerr("E189: \"" + filename + "\" exists (add ! to override)");
@@ -281,7 +280,7 @@ function IO() //{{{
try
{
io.writeFile(file, lines.join("\n"));
file.write(lines.join("\n"));
}
catch (e)
{
@@ -372,7 +371,7 @@ function IO() //{{{
/////////////////////////////////////////////////////////////////////////////{{{
liberator.registerObserver("load_completion", function () {
completion.setFunctionCompleter([self.getFile, self.expandPath],
completion.setFunctionCompleter([self.File, self.expandPath],
[function (context, obj, args) {
context.quote[2] = "";
completion.file(context, true);
@@ -451,10 +450,10 @@ function IO() //{{{
for (let [, dirName] in Iterator(dirNames))
{
let dir = io.getFile(dirName);
let dir = io.File(dirName);
if (dir.exists() && dir.isDirectory())
{
commands.push([[file.leafName, dir.path] for ([i, file] in Iterator(io.readDirectory(dir)))
commands.push([[file.leafName, dir.path] for (file in dir.iterDirectory())
if (file.isFile() && file.isExecutable())]);
}
}
@@ -466,11 +465,201 @@ function IO() //{{{
completion.addUrlCompleter("f", "Local files", completion.file);
});
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// File ////////////////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
/**
* @class File A class to wrap nsIFile objects and simplify operations
* thereon.
*
* @param {nsIFile|string} path Expanded according to {@link IO#expandPath}
* @param {boolean} checkPWD Whether to allow expansion relative to the
* current directory. @default true
*/
function File(path, checkPWD)
{
let self = { __proto__: File.prototype }
if (arguments.length < 2)
checkPWD = true;
self.file = services.create("file");
if (path instanceof Ci.nsIFile)
self.file = path;
else if (/file:\/\//.test(path))
self.file = services.create("file:").getFileFromURLSpec(path);
else
{
let expandedPath = io.expandPath(path);
if (!isAbsolutePath(expandedPath) && checkPWD)
self = joinPaths(io.getCurrentDirectory().path, expandedPath);
else
self.file.initWithPath(expandedPath);
}
self.wrappedNative = self.file;
return self;
}
File.prototype = {
__noSuchMethod__: function (meth, args)
{
return this.wrappedNative[meth].apply(this.wrappedNative,
args.map(function (a) a instanceof File ? a.wrappedNative : a));
},
/**
* Iterates over the objects in this directory.
*/
iterDirectory: function ()
{
if (!this.file.isDirectory())
throw Error("Not a directory");
let entries = this.file.directoryEntries;
while (entries.hasMoreElements())
yield File(entries.getNext().QueryInterface(Ci.nsIFile));
},
/**
* Returns the list of files in this directory.
*
* @param {boolean} sort Whether to sort the returned directory
* entries.
* @returns {nsIFile[]}
*/
readDirectory: function (sort)
{
if (!this.file.isDirectory())
throw Error("Not a directory");
let array = [e for (e in this.iterDirectory())];
if (sort)
array.sort(function (a, b) b.isDirectory() - a.isDirectory() || String.localeCompare(a.path, b.path));
return array;
},
/**
* Reads this file's entire contents in "text" mode and returns the
* content as a string.
*
* @param {string} encoding The encoding from which to decode the file.
* @default options["fileencoding"]
* @returns {string}
*/
read: function (encoding)
{
let ifstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
let icstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(Ci.nsIConverterInputStream);
if (!encoding)
encoding = options["fileencoding"];
ifstream.init(this.file, -1, 0, 0);
icstream.init(ifstream, encoding, 4096, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); // 4096 bytes buffering
let buffer = [];
let str = {};
while (icstream.readString(4096, str) != 0)
buffer.push(str.value);
icstream.close();
ifstream.close();
return buffer.join("");
},
/**
* Writes the string <b>buf</b> to this file.
*
* @param {string} buf The file content.
* @param {string|number} mode The file access mode, a bitwise OR of
* the following flags:
* {@link #MODE_RDONLY}: 0x01
* {@link #MODE_WRONLY}: 0x02
* {@link #MODE_RDWR}: 0x04
* {@link #MODE_CREATE}: 0x08
* {@link #MODE_APPEND}: 0x10
* {@link #MODE_TRUNCATE}: 0x20
* {@link #MODE_SYNC}: 0x40
* Alternatively, the following abbreviations may be used:
* ">" is equivalent to {@link #MODE_WRONLY} | {@link #MODE_CREATE} | {@link #MODE_TRUNCATE}
* ">>" is equivalent to {@link #MODE_WRONLY} | {@link #MODE_CREATE} | {@link #MODE_APPEND}
* @default ">"
* @param {number} perms The file mode bits of the created file. This
* is only used when creating a new file and does not change
* permissions if the file exists.
* @default 0644
* @param {string} encoding The encoding to used to write the file.
* @default options["fileencoding"]
*/
write: function (buf, mode, perms, encoding)
{
let ofstream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
function getStream(defaultChar)
{
let stream = Cc["@mozilla.org/intl/converter-output-stream;1"].createInstance(Ci.nsIConverterOutputStream);
stream.init(ofstream, encoding, 0, defaultChar);
return stream;
}
if (!encoding)
encoding = options["fileencoding"];
if (mode == ">>")
mode = self.MODE_WRONLY | self.MODE_CREATE | self.MODE_APPEND;
else if (!mode || mode == ">")
mode = self.MODE_WRONLY | self.MODE_CREATE | self.MODE_TRUNCATE;
if (!perms)
perms = 0644;
ofstream.init(this.file, mode, perms, 0);
let ocstream = getStream(0);
try
{
ocstream.writeString(buf);
}
catch (e)
{
liberator.dump(e);
if (e.result == Cr.NS_ERROR_LOSS_OF_SIGNIFICANT_DATA)
{
ocstream = getStream("?".charCodeAt(0));
ocstream.writeString(buf);
return false;
}
else
throw e;
}
finally
{
try
{
ocstream.close();
}
catch (e) {}
ofstream.close();
}
return true;
},
};
/* It would be nice if there were a simpler way to do this. */
("leafName nativeLeafName permissions permissionsOfLink lastModifiedTime " +
"lastModifiedTimeOfLink fileSize fileSizeOfLink target nativeTarget path " +
"nativePath parent directoryEntries").split(" ").forEach(function (p) {
File.prototype.__defineGetter__(p, function () this.file[p]);
File.prototype.__defineSetter__(p, function (val) this.file[p] = val);
});
/////////////////////////////////////////////////////////////////////////////}}}
////////////////////// PUBLIC SECTION //////////////////////////////////////////
/////////////////////////////////////////////////////////////////////////////{{{
const self = {
/**
* @property {function} File class.
* @final
*/
File: File,
/**
* @property {number} Open for reading only.
@@ -562,7 +751,7 @@ function IO() //{{{
*/
getCurrentDirectory: function ()
{
let dir = self.getFile(cwd.path);
let dir = self.File(cwd.path);
// NOTE: the directory could have been deleted underneath us so
// fallback to the process's CWD
@@ -586,7 +775,7 @@ function IO() //{{{
[cwd, oldcwd] = [oldcwd, this.getCurrentDirectory()];
else
{
let dir = self.getFile(newDir);
let dir = self.File(newDir);
if (!dir.exists() || !dir.isDirectory())
{
@@ -612,7 +801,6 @@ function IO() //{{{
dirs = dirs.map(function (dir) joinPaths(dir, name))
.filter(function (dir) dir.exists() && dir.isDirectory() && dir.isReadable());
return dirs;
},
@@ -644,44 +832,11 @@ function IO() //{{{
return null;
},
// return a nsILocalFile for path where you can call isDirectory(), etc. on
// caller must check with .exists() if the returned file really exists
// also expands relative paths
/**
* Returns an nsIFile object for <b>path</b>, which is expanded
* according to {@link #expandPath}.
*
* @param {string} path The path used to create the file object.
* @param {boolean} noCheckPWD Whether to allow a relative path.
* @returns {nsIFile}
*/
getFile: function (path, noCheckPWD)
{
let file = services.create("file");
if (/file:\/\//.test(path))
{
file = Cc["@mozilla.org/network/protocol;1?name=file"].createInstance(Ci.nsIFileProtocolHandler)
.getFileFromURLSpec(path);
}
else
{
let expandedPath = self.expandPath(path);
if (!isAbsolutePath(expandedPath) && !noCheckPWD)
file = joinPaths(self.getCurrentDirectory().path, expandedPath);
else
file.initWithPath(expandedPath);
}
return file;
},
// TODO: make secure
/**
* Creates a temporary file.
*
* @returns {nsIFile}
* @returns {File}
*/
createTempFile: function ()
{
@@ -690,148 +845,9 @@ function IO() //{{{
file.append(config.tempFile);
file.createUnique(Ci.nsIFile.NORMAL_FILE_TYPE, 0600);
return file;
return self.File(file);
},
/**
* Returns the list of files in <b>dir</b>.
*
* @param {nsIFile|string} dir The directory to read, either a full
* pathname or an instance of nsIFile.
* @param {boolean} sort Whether to sort the returned directory
* entries.
* @returns {nsIFile[]}
*/
readDirectory: function (dir, sort)
{
if (typeof dir == "string")
dir = self.getFile(dir);
if (dir.isDirectory())
{
let entries = dir.directoryEntries;
let array = [];
while (entries.hasMoreElements())
{
let entry = entries.getNext();
array.push(entry.QueryInterface(Ci.nsIFile));
}
if (sort)
array.sort(function (a, b) b.isDirectory() - a.isDirectory() || String.localeCompare(a.path, b.path));
return array;
}
else
return []; // XXX: or should it throw an error, probably yes?
// Yes, though frankly this should be a precondition so... --djk
},
/**
* Reads a file in "text" mode and returns the content as a string.
*
* @param {nsIFile|string} file The file to read, either a full
* pathname or an instance of nsIFile.
* @returns {string}
*/
readFile: function (file, encoding)
{
let ifstream = Cc["@mozilla.org/network/file-input-stream;1"].createInstance(Ci.nsIFileInputStream);
let icstream = Cc["@mozilla.org/intl/converter-input-stream;1"].createInstance(Ci.nsIConverterInputStream);
if (!encoding)
encoding = options["fileencoding"];
if (typeof file == "string")
file = self.getFile(file);
ifstream.init(file, -1, 0, 0);
icstream.init(ifstream, encoding, 4096, Ci.nsIConverterInputStream.DEFAULT_REPLACEMENT_CHARACTER); // 4096 bytes buffering
let buffer = "";
let str = {};
while (icstream.readString(4096, str) != 0)
buffer += str.value;
icstream.close();
ifstream.close();
return buffer;
},
/**
* Writes the string <b>buf</b> to a file.
*
* @param {nsIFile|string} file The file to write, either a full
* pathname or an instance of nsIFile.
* @param {string} buf The file content.
* @param {string|number} mode The file access mode, a bitwise OR of
* the following flags:
* {@link #MODE_RDONLY}: 0x01
* {@link #MODE_WRONLY}: 0x02
* {@link #MODE_RDWR}: 0x04
* {@link #MODE_CREATE}: 0x08
* {@link #MODE_APPEND}: 0x10
* {@link #MODE_TRUNCATE}: 0x20
* {@link #MODE_SYNC}: 0x40
* Alternatively, the following abbreviations may be used:
* ">" is equivalent to {@link #MODE_WRONLY} | {@link #MODE_CREATE} | {@link #MODE_TRUNCATE}
* ">>" is equivalent to {@link #MODE_WRONLY} | {@link #MODE_CREATE} | {@link #MODE_APPEND}
* @default ">"
* @param {number} perms The file mode bits of the created file. This
* is only used when creating a new file and does not change
* permissions if the file exists.
* @default 0644
*/
writeFile: function (file, buf, mode, perms, encoding)
{
let ofstream = Cc["@mozilla.org/network/file-output-stream;1"].createInstance(Ci.nsIFileOutputStream);
function getStream(defaultChar)
{
let stream = Cc["@mozilla.org/intl/converter-output-stream;1"].createInstance(Ci.nsIConverterOutputStream);
stream.init(ofstream, encoding, 0, defaultChar);
return stream;
}
if (!encoding)
encoding = options["fileencoding"];
if (typeof file == "string")
file = self.getFile(file);
if (mode == ">>")
mode = self.MODE_WRONLY | self.MODE_CREATE | self.MODE_APPEND;
else if (!mode || mode == ">")
mode = self.MODE_WRONLY | self.MODE_CREATE | self.MODE_TRUNCATE;
if (!perms)
perms = 0644;
ofstream.init(file, mode, perms, 0);
let ocstream = getStream(0);
try
{
ocstream.writeString(buf);
}
catch (e)
{
liberator.dump(e);
if (e.result == Cr.NS_ERROR_LOSS_OF_SIGNIFICANT_DATA)
{
ocstream = getStream("?".charCodeAt(0));
ocstream.writeString(buf);
return false;
}
else
throw e;
}
finally
{
try
{
ocstream.close();
}
catch (e) {}
ofstream.close();
}
return true;
},
/**
* Runs an external program.
@@ -849,7 +865,7 @@ function IO() //{{{
let file;
if (isAbsolutePath(program))
file = self.getFile(program, true);
file = self.File(program, true);
else
{
let dirs = services.get("environment").get("PATH").split(WINDOWS ? ";" : ":");
@@ -891,7 +907,7 @@ lookup:
let process = services.create("process");
process.init(file);
process.init(file.wrappedNative);
process.run(blocking, args.map(String), args.length);
return process.exitValue;
@@ -952,7 +968,7 @@ lookup:
let wasSourcing = self.sourcing;
try
{
var file = self.getFile(filename);
var file = self.File(filename);
self.sourcing = {
file: file.path,
line: 0
@@ -975,8 +991,8 @@ lookup:
liberator.echomsg("sourcing \"" + filename + "\"", 2);
let str = self.readFile(file);
let uri = services.get("io").newFileURI(file);
let str = file.read();
let uri = services.get("io").newFileURI(file.wrappedNative);
// handle pure JavaScript files specially
if (/\.js$/.test(filename))
@@ -990,7 +1006,7 @@ lookup:
let err = new Error();
for (let [k, v] in Iterator(e))
err[k] = v;
err.echoerr = file.path + ":" + e.lineNumber + ": " + e;
err.echoerr = <>{file.path}:{e.lineNumber}: {e}</>;
throw err;
}
}
@@ -1076,8 +1092,8 @@ lookup:
}
catch (e)
{
let message = "Sourcing file: " + (e.echoerr || file.path + ": " + e);
liberator.reportError(e);
let message = "Sourcing file: " + (e.echoerr || file.path + ": " + e);
if (!silent)
liberator.echoerr(message);
}
@@ -1105,7 +1121,7 @@ lookup:
return this.withTempFiles(function (stdin, stdout, cmd) {
if (input)
this.writeFile(stdin, input);
stdin.write(input);
// TODO: implement 'shellredir'
if (WINDOWS)
@@ -1115,13 +1131,13 @@ lookup:
}
else
{
this.writeFile(cmd, "cd " + escape(cwd.path) + "\n" +
cmd.write("cd " + escape(cwd.path) + "\n" +
["exec", ">" + escape(stdout.path), "2>&1", "<" + escape(stdin.path),
escape(options["shell"]), options["shellcmdflag"], escape(command)].join(" "));
res = this.run("/bin/sh", ["-e", cmd.path], true);
}
let output = self.readFile(stdout);
let output = stdout.read();
if (res > 0)
output += "\nshell returned " + res;
// if there is only one \n at the end, chop it off

View File

@@ -402,10 +402,10 @@ const liberator = (function () //{{{
"Install an extension",
function (args)
{
let file = io.getFile(args[0]);
let file = io.File(args[0]);
if (file.exists() && file.isReadable() && file.isFile())
services.get("extensionManager").installItemFromFile(file, "app-profile");
services.get("extensionManager").installItemFromFile(file.file, "app-profile");
else
{
if (file.exists() && file.isDirectory())
@@ -1483,7 +1483,7 @@ const liberator = (function () //{{{
return void liberator.echoerr("E484: Can't open file " + dir.path);
liberator.log("Sourcing plugin directory: " + dir.path + "...", 3);
io.readDirectory(dir.path, true).forEach(function (file) {
dir.readDirectory(true).forEach(function (file) {
if (file.isFile() && /\.(js|vimp)$/i.test(file.path) && !(file.path in liberator.pluginFiles))
{
try
@@ -1879,7 +1879,7 @@ const liberator = (function () //{{{
{
let filename = liberator.commandLineOptions.rcFile;
if (!/^(NONE|NORC)$/.test(filename))
io.source(io.getFile(filename).path, false); // let io.source handle any read failure like Vim
io.source(io.File(filename).path, false); // let io.source handle any read failure like Vim
}
else
{

View File

@@ -114,6 +114,7 @@ function Services()
self.add("xulAppInfo", "@mozilla.org/xre/app-info;1", Ci.nsIXULAppInfo);
self.addClass("file", "@mozilla.org/file/local;1", Ci.nsILocalFile);
self.addClass("file:", "@mozilla.org/network/protocol;1?name=file", Ci.nsIFileProtocolHandler);
self.addClass("find", "@mozilla.org/embedcomp/rangefind;1", Ci.nsIFind);
self.addClass("process", "@mozilla.org/process/util;1", Ci.nsIProcess);

View File

@@ -825,17 +825,13 @@ liberator.registerObserver("load_completion", function () {
]);
completion.colorScheme = function colorScheme(context) {
let colors = [];
io.getRuntimeDirectories("colors").forEach(function (dir) {
io.readDirectory(dir).forEach(function (file) {
if (/\.vimp$/.test(file.leafName) && !colors.some(function (c) c.leafName == file.leafName))
colors.push(file);
});
});
context.title = ["Color Scheme", "Runtime Path"];
context.completions = [[c.leafName.replace(/\.vimp$/, ""), c.parent.path] for ([, c] in Iterator(colors))]
context.keys = { text: function (f) f.leafName.replace(/\.vimp$/, ""), description: "parent.path" };
context.completions = util.Array.flatten(
io.getRuntimeDirectories("colors").map(
function (dir) dir.readDirectory().filter(
function (file) /\.vimp$/.test(file.leafName) && !colors.some(function (c) c.leafName == file.leafName))))
};
completion.highlightGroup = function highlightGroup(context) {

View File

@@ -688,9 +688,9 @@ const util = { //{{{
try
{
// Try to find a matching file.
let file = io.getFile(url);
let file = io.File(url);
if (file.exists() && file.isReadable())
return services.get("io").newFileURI(file).spec;
return services.get("io").newFileURI(file.wrappedNative).spec;
}
catch (e) {}