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"