Zsh and Ruby: Toward a universal directory/file opener

This post has been deprecated. Please read this post instead.

I’ve been using Linux for a while now (almost 2 years?), and have learned quite a bit about it. However, ever since I switched to Xmonad as my window manager, I’ve always been tied to the “urxvt + zsh” GUI. Don’t get me wrong — I really enjoy this setup (a plain old terminal has a beauty all of its own in how things get done), but it could be even better.

Lately, I’ve noticed my ~/.zshrc file grow larger and larger, with the addition of new aliases to open up common programs, like “m” for mplayer (audio, movies) and “e” for evince (PDF’s). To get rid of this clutter, and also to automate things, I decided to make a new, simple alias to handle pretty much everything I throw at it. The new functionality lets Zsh, with the help of Ruby, open up just about any file I throw at it, while also integrating the common “cd” command for directories — all from the command line.

The required files/code are as follows: (1) a custom zsh function (autoloaded into your zsh session) and (2) a custom Ruby script. I’ve only tested this on Ruby 1.9.1p243 (2009-07-16 revision 24175) [x86_64-linux].

For the custom zsh function, here is how I autoload it from ~/.zshrc:

fpath=(~/.zsh/func $fpath) # add ~/.zsh/func to $fpath
autoload -U ~/.zsh/func/*(:t) # load all functions in ~/.zsh/func

The function itself is as follows:

 1 # Author: Shinobu (https://zuttobenkyou.wordpress.com)
 2 # Date: December 2009
 3 # License: PUBLIC DOMAIN
 4 # Program Name: univ_open()
 5 # Description: zsh function to open up either directories or files
 6 univ_open() {
 7     ls_pretty="ls -Ahs --color=auto"
 8     if [[ ! -e $@ ]]; then
 9         # go to the parent directory if given path does not exist
10         /home/shinobu/syscfg/shellscripts/sys/univ_handler.rb $@ "parent" | read parent
11         cd $parent && ${=ls_pretty}
12     elif [[ -d $@ ]]; then
13         cd $@ && ${=ls_pretty}
14     else
15         # if it does exist, but is not a directory, open it up with the
16         # universal readerscript, to execute an appropriate program for the
17         # extension
18         /home/shinobu/syscfg/shellscripts/sys/univ_handler.rb $@ "extension" | read outputcmd
19         # since zsh does not split words by default, we need to manually do
20         # this by invoking outputcmd with the ${=VAR} syntax; this is because
21         # univ_handler will sometimes give a multi-word command where we use
22         # one or more commandline options, such as "mplayer -loop 0" or
23         # "soffice -writer" -- to prevent zsh from interpreting the entire
24         # string as the name of a command, we have to split it up into an array
25         ${=outputcmd} $@
26     fi
27 }

The file that contains this function is also called “univ_open”, which resides in ~/.zsh/func/. Anyway, this function is quite simple — it checks to see if the given argument does not exist (line 8), does exist and is a directory (line 12), or does exist and is a file (line 14).

In the “if” statement at line 8, it calls our custom Ruby script, univ_handler.rb, with an argument called “parent”. For lines 12-13, we don’t need to intelligently handle anything since the given argument is a valid directory path, so we just “cd” into it. Lastly, if the argument is a valid file (line 14), we call univ_handler.rb with the “extension” argument to determine which command to use to open up that file.

The univ_handler.rb file looks like this:

 1 #!/usr/bin/ruby
 2 # Author: Shinobu (https://zuttobenkyou.wordpress.com)
 3 # Date: December 2009
 4 # License: PUBLIC DOMAIN
 5 # Program Name: univ_handler.rb
 6 # Description: with the help of smart input from zsh, this script helps zsh
 7 # open up directories and files intelligently
 9 class Ext
10     @doc = %w{doc odf odt rtf}
11     @web = %w{htm html}
12     @pdf = %w{pdf ps}
13     @img = %w{bmp gif jpg jpeg png svg tiff}
14     @img2 = %w{psd xcf}
15     @audio = %w{flac mp3 wma}
16     @midi = %w{mid midi}
17     @movie = %w{avi flv ogg mkv mov mp4 mpg mpeg wmv}
19     class<<self; attr_reader :doc end
20     class<<self; attr_reader :web end
21     class<<self; attr_reader :pdf end
22     class<<self; attr_reader :img end
23     class<<self; attr_reader :img2 end
24     class<<self; attr_reader :audio end
25     class<<self; attr_reader :midi  end
26     class<<self; attr_reader :movie end
27 end
29 file = ARGV.shift
30 arg = ARGV.shift
32 # zsh will read whatever we ultimately "print" to STDOUT!
33 if file.nil? # if the user does not specify an argument
34     print ""
35 else # figure out file extension, or if directory, the parent directory
36     if arg == "extension"
37         case file.split(".").last.downcase
38         when *Ext.doc
39             print "soffice -writer"
40         when *Ext.web
41             print "firefox"
42         when *Ext.pdf
43             print "evince"
44         # image files -- typical simple files for plain viewing
45         when *Ext.img
46             print "eog"
47         # image files -- ones meant to be edited (strange looking ones and also
48         # gimp's native format)
49         when *Ext.img2
50             print "gimp"
51         # audio files
52         when *Ext.audio
53             print "mplayer -loop 0"
54         # midi files
55         when *Ext.midi
56             print "timidity"
57         # movies
58         when *Ext.movie
59             print "mplayer -loop 0"
60         else
61             # use vim to access files by default, including those without an
62             # extension
63             print "vim"
64         end
65     elsif arg == "parent" # find the closest valid parent directory
66         if file.scan("/").empty? # if there is no "/" in the argument
67             print "." # let's keep the user in the same directory
68         else # if we have a filename (at least the user thinks so), but it is
69              # not a valid file path = file.split("/")
70             path.pop
71             # return 1 directory higher than this one -- we assume that the
72             # reader used tab completions enough to avoid having more than 1
73             # bad directory/file level (that only the text following the last
74             # "/" is invalid)
75             print path.join("/")
76         end
77     end
78 end

You don’t even need to know Ruby to see what’s going on. The important thing is what’s inside the curly braces — we simply group common extensions together based on the file they usually represent. We don’t bother with uppercased combinations, since we use Ruby’s String class’s “downcase” method on the original user-given argument before handling it. Since I love vim, I use it as the “catch-all” to open up any other kind of file (such as files that don’t have extensions).

Now, the most important part — the one alias to rule them all (in your ~/.zshrc):

alias d='univ_open' # alias to use univ_open()

And… that’s it! All you have to do is now do d [file, directory, path, symlink to either a directory or path] to open it up intelligently! I personally use “d” since historically I’ve used it to cd into a directory (and used “k” to go back up); I also find using my middle finger much easier to press keys than my index finger. Probably the best benefit of this setup is that you can do “d TAB TAB …” to go deep into directories, and then just press ENTER when you find the file you want to edit — no more cd-ing deep into a subdirectory first, and then doing “ls” on it to see what’s inside, and deciding which command to use to open up that file. Now it’s just “d” and away you go.

It took me about half a day to get everything working, with help from googling, of course. I hope you can make use of it as it is, or even better, to customize it to your own needs. There’s enough “foundation” code in there with enough complexity to let anyone customize it a great deal and add more extensions to it.

As an added bonus, notice how the code here does not mention symlinks at all — and yet they are handled properly (don’t you love symlinks?)).

UPDATE December 6, 2009: Apparently, there is a similar command line script to open anything called “gnome-open”: http://embraceubuntu.com/2006/12/16/gnome-open-open-anything-from-the-command-line/. You can open up URLs in addition to directories and files. I don’t really see the advantages of opening up a URL from the command line, though — I’m already on Xmonad and going to my supremely powerful Firefox + Vimperator setup is at most 2 or 3 keystrokes away. Still, it looks interesting.

UPDATE December 31, 2009: This post has been deprecated. Please read this post instead.

My Newbie Experience With Ruby on Rails

So for the past month, I decided to learn Ruby and also its popular web framework, Ruby on Rails (or just “Rails”). The motivation behind this learning spree was the creation of my own website that I could use to help me study better (as a sort of drop-in replacement for MS Word for writing outlines). First, let me say that learning Ruby was great and really fun. However, Rails was really, to be honest, a pain in the @ss for pretty much everything.

I’m not going to go off on a long rant here (like what this ex-Rails core contributor did); instead, let me just point out some of the biggest weaknesses of Rails:

  1. Its documentation is pathetic. The Rails API doesn’t really mean anything to me–it’s just an index of every feature of Rails with short tidbits that are often sorely lacking in coherence. Or at least something that a newbie programmer like me can understand. And, I don’t know why, but the Rails community decided to put together a Wiki that is, at least for everything I was interested in, either obsolete, or poorly organized. It’s like reading a IRC chat log, except with prettified code blocks. Like this page.
  2. Somewhat related to #1, Rails has few, if any, tutorials that work. This is because Rails changes every 2 weeks. For some reason, every book I’ve used on Rails so far had to be modified by myself to work on my Windows XP box (I’d like to get into the world of Linux and open source operating systems more, but the last time I tried to install Ubuntu 7.04, it refused to work with my dual LCD monitor setup). And that means every time I want to do something–say, add a feature to my Rails app–I need to first google some vague description of it, and hope that someone on the Rails mailing list has it figured out. And about 75% of all useful tutorials on Rails seem to come from blogs. This gives me the impression that Rails developers don’t want to share clear, concise knowledge with other people in general. That’s why crappy sites like the Rails Wiki I mentioned above pops up in the top ten search results on google so often.
  3. Aside from horrible documentation and a sore lack of working tutorials, Rails seems hypnotized with its own mantra of “do everything in 20 minutes or less!”. By this I mean–so many articles about Rails are caught in the hysteria that it’s always better to write less lines of code, that it’s so great to do something in 5 minutes. There’s a common thread running through these tutorials/blog articles about Rails–they’re concerned first about quickly implementing something instead of creating something that is sturdy, reliable, and is general enough to work in a wide variety of situations. Usually, many of the Rails “solutions” or “recipes” that I encounter across the web while googling for Rails developer wisdom are too specialized to be of any use to anyone but themselves. If you’re going to share something with other people, it better be easily adaptable/changeable so that people can actually make use of it, and hey, perhaps make it better.
  4. In line with what I said in #3, I really despise how so many tutorials make use of Rails’s “scaffolding” feature. Good grief–I don’t want auto-generated code flying around in my Rails app to add just one little feature. You know what, I think the whole “scaffolding” feature is retarded. It’s only good if you want to create shit-looking websites that look as good as the tutorial’s scaffolded views. Seriously–do people really use scaffolding? I think it’s just a big joke–a marketing scheme. Scaffolding only lets you create something really fast–so that the Rails community can boast about development speed. And why is it that every tutorial out there on scaffolding is concerned about creating a weblog? With “Posts” and “Comments” as models? What if I don’t want to create a weblog? What then?
  5. Some features that should be really, really, really simple and built-into Rails don’t exist at all! For example, there is no simple way to upload files. Just google “rails upload files” and you will see. The answers do not converge to same/similar solutions. This is madness! Rails wasn’t built yesterday. It’s been several years since DHH became an international figure. And yet, all through that time, it has failed to create a simple feature that allows users to upload files to a basic Rails app! Also, did I mention that Rails migrations do NOT support the creation of MEDIUMBLOB/MEDIUMTEXT or LONGBLOB/LONGTEXT types in MySQL? Why? Why do I have to execute ugly SQL code inside my Rails migration files? Apparently, Rails likes to think that everything should be smaller than 64KB. What a joke!

That being said, I’ll still say that Rails has taught me a lot about web design and what it could be (my last foray into website design was in 2001 with pure HTML and Notepad). But it leaves a lot to be desired. I’ll keep playing around with Rails on my windows box for now, but my desire to learn the deeper intricacies of Rails has all but died out. The bad news is that Rails is the only heavily-developed framework out there based on Ruby–my favorite scripting language. Yes, I know of other alternatives like Merb, but bad documentation is better than no documentation (the only reason why I decided to learn Rails for what it was worth was because no other Ruby-based framework had a similar amount of documentation). And for the record, I did end up creating a decent website to suit my needs (a replacement for MS Word). But it could have been better, and more pleasurable, had it not been for the 5 annoying-as-hell points I listed above.

My advice to those wanting to learn a web framework is this: wait a couple years! Hopefully, either Rails or some other alternative will be mature enough to be easily digestible. But if you have countless hours to burn reading blogs and googling for things–you have my blessing.

UPDATE March 25, 2008: I see that people are visiting this blog for Rails tips (like what I was doing 3 months ago). If you want to learn Rails without the crappy feature called “scaffolding” that I ranted about above, try “Build Your Own Ruby Applications” by Patrick Lenz. It’s outdated, yes, and it has its share of typos, but once you get through at least the first few chapters (especially the ones where he builds from scratch how to make editable text input boxes and do the basic CRUD features on them), you should be able to make some basic but still functional (and not “scaffolded”–ugh) websites. The SitePoint publisher forum for this book is quite helpful for common typos, etc (Lenz even has a page dedicated to errata/corrections).The key is to copy code and modify them to your liking.

I also recommend the NetBeans IDE. It’s superb. Also, get the HAML ruby gem (which comes with SASS support), and the corresponding NetBeans HAML/SASS syntax highlighter (google it), and you’ll be on your way to some mad-quick development. HAML will save you quite a bit of typing–hours of typing in the long run (no closing tags!). Oh, and I highly recommend the FireBug Firefox extension if you don’t have it already. The SASS + FireBug duo is probably 10 ~ 15 times quicker than plain CSS editing (I can’t believe there are actually sites that still talk about CSS, when SASS is ultra fast for development (you could, if you want, use SASS for development only and just copy/paste the resulting CSS generated by SASS, statically, on your non-Rails sites).

Oh, and lastly, use XAMPP (or any other similar package). Why? No need to type in MySQL commands, if you’re using MySQL (like me, b/c to me it seems that MySQL has the most dominant presence right now). I worked through Lenz’s beginning chapters without typing in any unpalatable MySQL commands. 😉 Just use the phpMyAdmin web app that comes with XAMPP on localhost to browse through database entries and do CRUD operations on them with intuitive, easy mouse-clicks. (And since you’ll probably be using FireFox for phpMyAdmin, this means that you can keep your prototype Rails app on the tab next to it (and while you’re at it, a tab with FireBug enabled on another part of the site you’re fixing up, etc etc)).

And finally, if you’re NEW to Rails and am just starting out from almost no programming background (like how I was), DO NOT bother with creating unit tests, functional tests, or any other tests. It is a waste of time when you’re trying to create a simple, ultra basic web app for learning purposes. I cannot tell you how many hours I spent trying to fix typos in Lenz’s book (either that or the test code in the book was outdated by the time Rails 2.0 came around) to get every unit test to work. Besides, Rails by default will present you with EXACTLY where the source code encountered a problem. I still haven’t written tests for my app–but I made sure during development to try out bogus input and to purposely make my app break for every method I wrote. And now my app has reached a point where it hasn’t displayed any errors (or anomalies) for the past month or so of continued usage. It’s actually better this way, in my opinion, because you’re FIRST learning to write good, reliable code from the get-go, instead of getting into the mindset of (“I’ll just test it later”). Writing function tests are not easy (just look at Lenz’s book–he has like 30-something tests for his super basic web app… I mean, how does he know that he only needs 30, and not 31? Or 29? So to sum up: don’t bother with test code if you’re starting out in Rails. Get the “feel” of Rails down first, and how the app behaves/responds to input, and then implement tests if you wish (…to work as a Rails developer, that is, and not just as a hobby).

UPDATE April 25, 2008: OK, people are still visiting this page to find out about LONGTEXT/LONGBLOB types for migrations. Here is my SQL-command that I used for my migration (in the full context of my 001_create_articles.rb migration file:

class CreateArticles < ActiveRecord::Migration
  def self.up
    create_table :articles do |t|
      t.string :title
      # and some other stuff that look like t.text or t.string
      # or whatever else
    end # END create_table

    execute 'ALTER TABLE articles ADD COLUMN body_text MEDIUMTEXT'

  end # END self.up

  def self.down
    drop_table :articles

end # END class definition

You can replace mediumtext with longtext or longblob or anything else. The command above adds a new column called body_text to the articles table that already exists on the database.

UPDATE March 11, 2009: I don’t want to give out bad advice. I should have stated a long time ago that you should NOT use any IDE, even NetBeans. IDE’s and their GUIs and all that baggage slow you down a lot. Personally I use Vim, but any solid text editor should do.