Sorry catalogers, a programming post.
Thor is a great ruby library for automating tasks. It’s really slick.
It’s the engine on top of which Rails3 Generators are built. That’s where I’m working with them.
However, a bunch of things are pretty poorly documented. Made even more confusing by the fact that Rails changes thor, in the context of Rails generators, in some not entirely documented ways. So I figure, okay, I’ll look at the source — but there are some things I’m not successful at finding even in the source, wondering if I’ve succesfully found the latest version of the source as used in Rails3 generators or what.
So I’m going to document a few useful things I’ve discovered here, so maybe someone else can find it on google and save some time. This is also a cry for help, if anyone can explain what’s going on and where the right source is.
The Actions You Didn’t Know About
In the context of Rails3 generators, Rails3 adds some more actions, which are mentioned in the Rails3 Generators Guide. Okay, I get it, Rails is subclassing or otherwise adding more stuff, and great job in actually documenting it in the Guide, no prob.
But there are a bunch more thor actions available too. Not sure how I found this, but I found them listed on this other automatically generated thor rdoc, at http://textmate.rubyforge.org/thor/Thor/Actions.html.
The actions listed there actually do work in a Rails3 generator. I found “get” to fetch a remote http file particularly useful, and there were a few others there I had started to write myself before discovering there were built in actions for em.
You’ve got to look in all three documentation places to get all of em; each has their own, with no overlap.
But where the heck are these actions coming from? Anyone know? I can’t find em in the source at github wycats/thor. Is that not actually the version of thor used in Rails? textmate.rubyforge.org…. is the most recent version of thor actually on rubyforge not github? That’d be odd. But textmate.rubyforge.org, that’s a project for textmate, huh? Anyone have any idea what’s up there? There are a few questions I have about some of those actions which could be answered by the source, and a few places I wouldn’t mind submitting a patch to improve things if I knew where the source was and who to submit it to. Mysteries.
Intentionally fatal erroring
Let’s say you are writing a task/generator, and something happens and you want to end the task early. You know, an “exceptional” condition. Say you are downloading something from a remote location but it can’t be downloaded. You want to let the user know and cancel the rest of the task.
If you just raise an exception, thor will catch it and stop running the rest of the task. But it’ll also print out an ugly complete stack trace to the user, assuming this is a developer error that the developer will want a stack trace for.
But in this case it’s not a developer error, we intentionally want to end the task early for reason of exceptional conditions.
Turns out if you raise a Thor::Error instead, thor will catch it and print out the #message without a stack trace. I did find that one by looking through the Thor source on github wycats/thor. It was documented kind of suggesting it was only meant to be used internally by Thor for argument-parsing type errors, but it works, and Dan Funk noted it’s used by Rails3 generators too (thanks Dan), so if it’s good enough for Rails source it’s good enough for me.
Except my example above was kind of a lie. “If you try to download something from a remote location…” If you use the hidden thor get action for that, if you pass it a LOCAL file path (not documented to work but it does) that does not exist, thor will indeed raise a Thor::Error and print out an error without a backtrace. But if you ask for a remote http: address, which returns a 404 or is otherwise unreachable…. the exception raised by open-uri is just raised all the way up, and thor will print out the stack trace for it. Annoying. This is something I’d submit a patch enhancement for if I could figure out where the source was and who (if anyone) maintained it. (I don’t have the energy to muss my code up trying to catch it myself and re-raise as Thor::Action).
Rails Generator difference via Thor: arguments/options
A Rails3 generator is mostly just an ordinary Thor task, with some extra built-in actions provided by Rails3.
But it’s different in one key way. An ordinary Thor task — at least according to the documentation, I haven’t tried it myself — has you put individual actions which take their own arguments and are run individually, in individual methods in the task class.
A Rails3 generator, on the other hand, will run every task method in the class when executed. This fact is mentioned in the Rails Guide.
But this has an implication for arguments/options that isn’t covered in the Rails Guide. And when you go looking at the Thor documentation (at least the documentation I’ve found!) for more details, this might confuse you. Ordinarily, in the Thor documentation, it talks about using #method_option and #argument to add options to a task. But in the Rails3 generator world, you use #class_option (a class method that takes the same arguments as #method_option), and a class-level #argument method (taking the same arguments as the instance level #argument method) to do the same thing.
Neither of these methods you actually use in Rails Generators are documented in Thor, neither are they mentioned in the Rails Guide. I found em by looking at existing Rails generators source on the web. Perhaps they’re added by Rails extensions to thor. I haven’t hunted down the source code for Rails extensions yet, or any available rdoc if there are any. (If such exist, it might be nice if the rails Guide linked to em).
I haven’t figured out if it’s possible to ask Rails to run just one method from your Rails Generator, classic thor style. Sometimes it would be useful.
#say and #say_status are standard in Thor and documented (sort of), but not mentioned on the Generators Guide. But you can use em in generators. #say_status is what will get you those nice padded label/sentance outputs you see in Rails generators frequently.
It’s hard to find the rdoc or source becuase of the way it’s implemented with delegation to use different versions depending on whether a color terminal is present. (But I found the source/rdoc eventually, not too bad).
You can pass a color option for the text in #say or the label in #say_status that will use color if there’s a color interactive terminal (and I _think_ avoid the color if it’s not an interactive TTY but I’m not sure).
say("A message", :green) say_status("something", "did it") # will be green say_status("error", "this is bad", :red)
The colors available correspond to constants in Thor::Shell::Color