Document Unit Testing
Load the Folio library.
require 'folio'
Instantiate document object.
@foo = Folio.file('fixtures/foo.txt')
Confirm it is a a document.
@foo.assert.document?
Confirm it is equal to another document initialized identically.
@foo.assert == Folio.file('fixtures/foo.txt')
Confirm it is equal to the same file initialized from a different working directory.
f = nil Dir.chdir('fixtures') do f = Folio.file('foo.txt') end @foo.assert == f
Document can be read.
@foo.read.assert == "THIS IS FOO!\n"
The #readlines methods read the document into an array of lines.
@foo.readlines.assert == ["THIS IS FOO!\n"]
We can also open the document for reading using the more traditional open() method.
t = nil @foo.open('r'){ |f| t = f.read } t.assert == "THIS IS FOO!\n"
The #parent method return the Directory object in which the document is contained.
@foo.parent.assert == Folio.file('fixtures')
A document can be written to as well using the #write method.
out = Folio.doc('fixtures/out.txt') out.write('Hello') File.read(out.to_s).assert == 'Hello'
General Usage of Folio Link Object
Require the Folio library.
require 'folio'
Instantiate a soft link.
@foo = Folio.file('fixtures/foo_softlink.txt')
It is a link.
@foo.link?.assert == true
It is equal to a link defined identically.
@foo.assert == Folio.file('fixtures/foo_softlink.txt')
The link is not equal to the target document.
@foo.assert! == Folio.file('fixtures/foo.txt')
But it is equal to a same link instantiated from a differnt working directory.
f = nil Dir.chdir('fixtures') do f = Folio.file('foo_softlink.txt') end @foo.assert == f
Reads the linked document correctly.
@foo.read == "THIS IS FOO!"
Opens the linked document for reading.
t = nil @foo.open('r'){ |f| t = f.read } t.assert == "THIS IS FOO!\n"
It provides the parent directory object.
@foo.parent.assert == Folio.dir('fixtures')
General Usage of the Folio Shell
Require the Folio library.
require 'folio'
Open a new Folio::Shell object.
@c = Folio.shell('fixtures')
Method #work returns current working directory.
@c.work.assert == Folio.dir('fixtures')
Method #to_s returns current working dirname.
@c.to_s.assert == File.join(Dir.pwd, 'fixtures')
Method #entries returns a list of names of the current directory content.
@c.entries.sort.assert == ['foo.txt', 'foo_softlink.txt', 'out.txt']
Method #files returns an array of file objects in the current directory.
f = @c.files.to_a f.size.assert == 3 f.assert.include?(Folio.file('fixtures/foo_softlink.txt')) f.assert.include?(Folio.file('fixtures/foo.txt'))
Problem Scenerios
Non-existant paths
If a path does not exist, how do we know if it is intended to be a file, or directory, or what?
d1 = dir('home/trans') d2 = d1 / 'images'
Optons for resolution include:
- Do not support ambiguitous methods.
- Raise an error if non-existant.
- Default to a directory when ambiguious.
- Use a delegator.
The first solution is the most straightforward. The second is reasonably viable as well, but at least it provides some understanble use for such methods.
The return value of the third feels to quirky. And the last is a complex solution that effectively only leaves us with some sort of non-disk Pathname object; a solution almost a quirky.
Removal of file-io object
If we delete a file object, then the object looses it’s reference. This is especially problematic if we do not allow file objects to have non-existant paths.
d2 = d1.dir('images') d2.rm_r
Options for resolution:
- Do not support removal methods.
- Allow non-existant paths.
- Use delegator.
The first solution would mean using a parent directory object to delete anything.
f1 = file('/home/trans/.bashrc') f1.rm # not allowed f1.parent.rm(f1) # instead
I do not like this option becuase it means a directory object would behave substantially differnt from other file objects. That might not be so bad is Shell and Directory could be one and the same, but methods like #ctime would still clash.
The second option is the simplist. The file object simply takes on a "virtual" state. This could lead to errors being thrown, but it is not unacceptable.
f1 = file('/home/trans/.bashrc') f1.rm f1.ctime # error
The last solution creates an indirection for all file objects. Removing a file would redelegate to a virtual pathname object. This is a more complicated solution and is really no better than the previous solutution —MethodMissing would be the error. A delegtor solution is a bit more interesting when we consider virtual file objects that can mimic a file system. However that could be a rather misleading redelgation.
Looking at the Lager Picture
Do we really need these objects? Isn’t the core idea the Folio Shell? For the first issue, what is wrong with:
s1 = shell('home/trans') s2 = s1 / 'images'
Which would clearly raise an error if it were not existant.
As for the second. While it might not be quite as concise, it fits the shell prompt pattern:
s1 = shell('/home/trans') s1.rm('.bashrc')
Yea, ok. However, while we could pass around shell objects as a form of directory object, this would leave us no way to pass file objects.
Conclusion
It is fairly clear that the second solution to the second issue is the clearest choice. And that Directory and Shell should in fact stay separate objects. The "Larger Picture" should be the general pattern for theses cases, however this ought not prevent the use of the file objects as well.
As for the first issue, the first or second options the better choices. I will probably raise the error.