[file] A Graphic Depiction

1 view
Skip to first unread message

Kris Kowal

unread,
Apr 14, 2009, 2:40:51 AM4/14/09
to serv...@googlegroups.com
I prototyped the current draft of the file API proposal in Narwhal last week.

https://wiki.mozilla.org/ServerJS/API/file

Ondras had expressed in IRC that the proposal didn't feel object
oriented enough, in that I hadn't written in a type that would
represent a path to a file or directory. To entertain the idea, I
threw a Path type into the filesystem root object, so you can create
references to paths without committing to opening a stream, listing a
directory's contents, or manipulating its metadata. I am now prepared
to concede that it was wrong to omit a Path type.

I called it Path in contrast to Java io's and Spidermonkey's File
since File is too easy for a polyglot to confuse for a stream object,
rather than an inert noncommittal path reference. Java seems to have
taken the same turn with its new "nio" package.

What I like about the path object is that it is possible to "chain"
path manipulation functions. I set it up so that Path objects were
loose wrappers around a file system root object and a path string,
with a suite of member functions that were simply shortcuts for the
members on the file system, where the first path argument was
implicitly filled in. So, the following were equivalent:

fs.read(path)
fs.open(path).read()
fs.path(path).open().read()

And here's a more fun example from packages.js in Narwhal:

var packagesDir = fs.path(system.env.NARWHAL_HOME).join('packages');
packagesDir.list().forEach(function (packageName) {
var packageJson = packagesDir.join(packageName, 'package.json');
if (packageJson.exists()) {
packageJson.read();
}
});

I think that these Path objects offer a really nice workflow.

In the process of doing the prototype, I found words to articulate the
difference between "join" and "resolve". "join" is as simple as
delimiting arguments with the directory separator, which is nice
because you can use it in cases where the base path is known to be a
directory. "resolve" is very handy for implementing other functions
like "absolute", "normal", and "dirname". The difference is that
resolve, like URL resolution, treats a leaf directory of a path
differently if it ends with a directory separator "/" than if it does
not. That is, if there's a "/" you're referring to the location
inside the directory, and without the "/", you're referring to the
directory containing the leaf. So, when you resolve "bar" relative to
"foo", you'll get "bar", but if you resolve "bar" relative to "foo/",
you'll get "foo/bar". With "join", you would get "foo/bar" in both
cases.

Path and resolve are pure-JavaScript, so they're suitable for sharing
in the standard library file.js module, while functions like
"canonical" will need to be platform-specific. Here's "resolve" and
you can find "Path" in the same file.

http://github.com/kriskowal/narwhal/blob/b5cfcd8416847660028046d2df0d80593a407cbc/lib/file.js#L85

I've attached an illustration of the channels, by way of method calls,
through which you can go from a file system root to a directory
listing, a stat object, or a stream. This is in the abstract, so
"root" is meant to depict a file system root object, albeith
"system.fs" or that returned by require("file") in the proposal.
"open" can naturally open both input and output streams for both byte
and character string data. It's meant to illustrate that creating a
Path object is equivalent to returning a new object for which all the
same functions as are available on a file system root are supplied,
but partially applied on given path.

One interesting turn with the Path API I threw in Narwhal: When you
get a Path object, all of the methods that return paths are chainable
Path instances, on which toString() returns the wrapped String of the
Path. However, all of the file system root methods return plain old
Strings. I think this is a good compromise and will yield few
surprises.

assert typeof fs.canonical("/") == "/"
assert fs.path("/").canonical() instanceof fs.Path

Kris Kowal

fileapi.png
fileapi.svg

Ondrej Zara

unread,
Apr 14, 2009, 3:16:28 AM4/14/09
to serv...@googlegroups.com
Ondras had expressed in IRC that the proposal didn't feel object
oriented enough, in that I hadn't written in a type that would
represent a path to a file or directory.  To entertain the idea, I
threw a Path type into the filesystem root object, so you can create
references to paths without committing to opening a stream, listing a
directory's contents, or manipulating its metadata.  I am now prepared
to concede that it was wrong to omit a Path type.


Thanks for taking my ideas into consideration :)

 fs.read(path)
 fs.open(path).read()
 fs.path(path).open().read()

What is puzzling me a bit is that I am missing a "new" keyword in all your examples. I believe that it is possible to create a very long and complex discussion about the supporting of "non-new constructors", but for the sake of clarity, please use "new" when you create new instances.

So, do I get it correct that the proposed API now offers:

(new fs.Path("/home/ondras/file.txt")).open(mode).read() ?

 Also, I am not very comfortable with the fact that "everone has everything" in a sense that we have fs.read, path.read, file.read, ... in my opinion, we should standardize exactly three classes:

Path - generic filesystem path, offers string manipulation methods (join, normalize, dirname, basename) and testing methods (isFile(), isDirectory(), stat())
File - you get this when you open() a Path which .isFile(). File offers stream methods for reading and writing, creation and deletion.
Directory - I am not exactly sure how you get to this from path. However, Directory offers methods for creation, deletion and listing.


On the other hand, I am not convinced that Path should offer an "open" method. Only files can be opened, so .open() is more of a file method... to be honest, every FS API proposal seen so far seems to have some weaknesses...


Ondrej

Reply all
Reply to author
Forward
0 new messages