advertisement

Print

How to Build Simple Console Apps with Ruby and ActiveRecord
Pages: 1, 2, 3, 4

Simple Parsing of Input

You might have noticed that all data is entered as a single string to EarGTD.



For example, you can enter a task like:

$ earGTD + "a trivial task <a project> [a context]"

This is meant to simplify command processing. Since Ruby's regular expression support is quite good, the task of parsing that data is easy. Though it's possible to do it all in one sweep, I've opted to break it out into three different methods for clarity:

def parse_project(string)
  string =~ /<(.*)>/ && $1.strip
end

def parse_task(string)
  string.gsub(/[<\[].*[>\]]/,"").strip
end

def parse_context(string)
  string =~ /\[(.*)\]/ && $1.strip
end

Regular Expressions often look like line noise to the uninitiated, but they're really quite simple, and essential for pattern matching tasks.

Let's break these down, one by one.

parse_project looks for any amount of characters (.*) between angle braces, and stores them in the first "group." If the pattern matches, this is stored in $1, and then the method returns the results with all leading and trailing whitespace stripped.

The tests for this show what the method does:

@message = " do the foo < protocol 4 > [context]"  
expected = "protocol 4" 
assert_equal expected, EarGTD.parse_project(@message)

parse_context does the same thing, but for angle braces instead. The added noise with the back slashes in the pattern are because [] is a special construct in regexp, so they need to be escaped to match literals. This is quite similar to how you need to escape quotes in string, like \"this\"

The tests for parse_context are straightforward:

@message = " do the foo < protocol 4 > [context]"  
expected = "context" 
assert_equal expected, EarGTD.parse_context(@message)

parse_task

This code actually uses the [] construct we just mentioned. This is a character class, which will match any of the characters within it once. It also does a substitution via gsub. In english, the command:

  string.gsub(/[<\[].*[>\]]/,"").strip

means, "Take the string and anywhere you find some text between angle braces or square brackets, replace them with an empty string".

Here are the tests which show it in action:

@message = " do the foo < protocol 4 > [context]"    
expected = "do the foo" 
assert_equal expected, EarGTD.parse_task(@message)

Using these three helper methods, you can see the add_task method is actually quite simple.

def add_task(description)

  task_desc = parse_task(description)
  task = Models::Task.create(:description => task_desc)

  if (project=parse_project(description))
    Models::Project.find_or_create_by_name(project).tasks << task
  end

  if (context=parse_context(description))
    Models::Context.find_or_create_by_name(context).tasks << task
  end

end

If things got a little heavier, you might want to build a custom parser class, but this is about perfect for our needs. By breaking the parsers out into their own functions, we can change how they work without having to change the add_task code.

File and Console I/O

I/O is something that often trips up newbies in Ruby, but it's really quite simple. For example, printing a line to the screen is just puts "something" and reading a file is just File.read("somefile").

A simple example of writing to file is EarGTD's dump functionality. You can dump tasks to a file, as a backup that can be imported later if needed.

The following sessions shows how this works, after displaying the current tasks.

$ ./earGTD @
1. Check in accounting module <Foo> [work]
2. Add tests for credit card validation <Foo> [work]
3. Call Joe Frasier [work]
4. Plant a tree <Beautification> [home]
6. Paint the shed [home]
7. Play tennis for 60 hours

$ ./earGTD dump foo.txt

$ cat foo.txt 
Check in accounting module <Foo> [work]
Add tests for credit card validation <Foo> [work]
Call Joe Frasier [work]
Plant a tree <Beautification> [home]
Paint the shed [home]
Play tennis for 60 hours

The example above calls EarGTD.dump('foo.txt'). The implementation is shown below for reference:

def dump(file=nil)
  content = Models::Task.dump
  if file
    File.open(file,"w") { |f| f.puts(content) }
  end
  return content
end

We're basically just printing out the array of Task objects that is returned by Models::Task.dump.

This code is actually a little more clever than it looks, though. The object we're printing isn't a string, which means Ruby is making some assumptions about how to represent our data.

Fun Ruby stuff: puts()

As a print function, puts is actually very powerful.

It will iterate through an array printing the elements one at a time, and calling to_s on them along the way. Below is a simple example of how this works

>> class A
>>   def initialize(name)
>>     @name = name
>>   end
>>   def to_s
>>     @name.reverse
>>   end
>> end

>> a = [A.new("greg"),A.new("pete"),A.new("paul")]

>> puts a
gerg
etep
luap

puts will also try to call to_s on any object you pass it.

>> puts A.new("nick")
kcin

EarGTD's Task model implements a to_s method, which is why printing an array of tasks just works.

This can come in handy whenever you want to represent an object in a particular way for output. No explicit conversions to string representation are necessary.

Because of features like these, I/O is fairly comfortable to work within Ruby.

The End of the Tour

We might be able to squeeze some more lessons out of EarGTD, but we've covered enough ground for now.

Though this article is unlikely to make you an instant Ruby guru, it has hopefully exposed some interesting idioms to you for further exploration. Leveraging the libraries that make up the Rails underbelly can be a really powerful skill, and allows you to gain the best of both worlds for a lot of different problems.

I should mention that for more complex console application needs, you'll often want to use a tool that will help handle the low level bits for you, such as Ruby's standard library OptionParser, or for interactive applications that need input validation, HighLine.

Of course, there's nothing wrong with keeping it simple, either!

Gregory Brown is a New Haven, CT based Rubyist who spends most of his time on free software projects in Ruby. He is the original author of Ruby Reports.


Return to Ruby.