The other day, I came across this interesting post by Tom Moertel. The blog post is called “A simple directory-tree printer in Haskell”. Haskell is interesting and this kind of programming exercise would really show how Haskell deals with real-world resources (the filesystem) as well as rendering the output in a nice human readable representation.
Here is some sample output:
. |-- haskell.hs |-- test.rb |-- tree.rb `-- tree.rb.html
His post was well written and I really liked how concise the resulting Haskell code was. I also liked the way he dealt with tree printing. I had never seen a tree printer algorithm like this before since I’m used to graphical versions which generally end up having a lot more code. For example, if I was doing this, I would have built a generic tree printer class and passed a lot more state around to each node (like the depth, sibling count, layout algorithms, etc.) Tom’s method is a great little pattern for console style tree output (like pstree) with only a few lines of code. For some past projects, such a simple routine would have come in handy.
So last night, I wondered what the Ruby version would look like. I tried to keep the example in the same functional style… just like his first example. The Ruby code stayed pretty concise.
Here is my Ruby version of Tom’s Haskell version:
1 2 require 'pathname' 3 4 $ArmMap = Hash.new("| ") 5 $ArmMap[""] = "" 6 $ArmMap["`"] = " " 7 8 def visit(path, leader, tie, arm, node) 9 print "#{leader}#{arm}#{tie}#{node}\n" 10 visitChildren(path + node, leader + $ArmMap[arm]) 11 end 12 13 def visitChildren(path, leader) 14 return unless FileTest.directory? path 15 return unless FileTest.readable? path 16 files = path.children(false).sort #false = return name, not full path 17 return if files.empty? 18 19 arms = Array.new(files.length - 1, "|") << "`" 20 pairs = files.zip(arms) 21 pairs.each { |e| visit(path, leader, "-- ", e[1], e[0]) } 22 end 23 24 ARGV << "." if ARGV.empty? 25 ARGV.map{ |path| visit Pathname.new("."), "","","",Pathname.new(path) } 26 27
A simple amelioration if you want to include this script in your Rakefile. Then you simply to :
require ‘pathname’
desc “Print directory-tree” task :tree, [:folder, :folderonly] do |t, args| args.withdefaults(:folder_only => false)
$folderonly = args.folderonly == “true”
$ArmMap = Hash.new(“| “) $ArmMap[“”] = “” $ArmMap[“`”] = ” “
def visit path, leader, tie, arm, node print(“#{leader}#{arm}#{tie}#{node}n”) if $folder_only and File.directory?(path+node) visitChildren(path + node, leader + $ArmMap[arm]) end
def visitChildren path, leader return unless FileTest.directory? path return unless FileTest.readable? path files = path.children(false).sort #false = return name, not full path return if files.empty?
end
visit Pathname.new(“.”), “”,””,””,Pathname.new(args.folder) end