Object-Oriented File Manipulation

ruby rush python

Fri Aug 29 13:38:00 -0700 2008

The file manipulation routines in Ruby’s stdlib are a bit of a mess. Operations are scattered among different classes - File, Dir, and FileUtils. Methods often aren’t where you expect them - like File.directory? but not Dir.directory?. But worst of all, they aren’t object-oriented. You’d want to make calls like f.size or f.directory?, but instead you’re stuck with File.stat(f).size and File.directory?(f).

Pow is a library that attempts to clean up this mess. The file portions of rush are my approach. Comparing the three for similar tasks gives some insight into the nature of this problem:

Listing Files In a Directory

Ruby stdlib

Dir.open('tmp').each do |fname|
  next if fname == '.' or fname == '..'
  puts "#{fname} - #{File.directory?(fname) ? "dir" : "file"}"
end

Pow

Pow("tmp").each do |child|
  puts "#{child} - #{child.class}"
end

rush

pwd['tmp'].entries.map do |entry|
  "#{entry} - #{entry.class}"
end

Ruby stdlib and Pow both have implicit state in the form of the current working directory. This bit of hidden and subtle state is the cause of many bugs when doing systems work (and the reason why RAILS_ROOT is so useful). As a compromise, the rush shell offers pwd as a convenience variable created based on the directory when you launched it, but pwd won’t change in the lifetime of the shell (unless you explicitly assign the variable to something else).

The rush example uses map and a string value return, instead of puts, because the rush interactive shell automatically displays results nicely printed. (Not like the irb shell, which gives you the results of inspect, which are not terribly readable for arrays.)

Sorting by mtime

Ruby stdlib

dirname = 'images'
files = Dir.open(dirname).map.reject { |f| File.directory?("#{dirname}/#{f}") }
files.sort { |a, b| File.stat("#{dirname}/#{a}").mtime <=> File.stat("#{dirname}/#{b}").mtime }

Pow

Pow('images').files.sort_by { |file| file.modified_at }

rush

pwd['images/'].files.sort_by { |file| file.last_modified }

Here it becomes obvious where Ruby stdlib’s non-adherance to OO principles is a weakness. The language is built around object operations giving access to features like sort_by - but you can’t use these without proper object representations of the items being manipulated.

Create Subdir, Write File

Ruby’s sister language, Python, has a similar issue. Tommi Virtanen addresses the problem in a way that looks remarkably similar to Pow or rush. I lifted this Python code from slide 40, and I couldn’t resist doing a direct translation to Ruby and rush:

Pythonic FS

top = FilePath('toplevel')
sub = top.child('foo')
sub.createDirectory()
p = sub.child('bar')
with p.open('w') as f:
  f.write('foo bar\n')

rush

top = pwd['toplevel']
sub = top['foo/']
sub.create
p = sub['bar']
p.write "foo bar\n"

Or a more succinct version:

foo = pwd['toplevel/foo/'].create
foo['bar'].write "foo bar\n"