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:

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:

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.