My Projects

The projects that I have spent significant time on.
  • 17

    MAY
    2014

    A Library in One Day

    I was super inspired by Darius Kazemi's recent blog post on small projects, so I've been looking for ways to speed up my process.

    Today, I tried an experiment: develop a library in one day. I wanted to go from an empty repository to a published gem that I could start using.

    Mission accomplished.

    Topic

    Obviously, I had to select a pretty simple idea to use. I wouldn't have time to do a huge project.

    I think this may be the killer feature of this technique.

    On one hand, you could argue that what I built may not be very library worthy. It's around 50 lines of code. It has ten specs and they really cover what it does. This isn't a complex beast and you could pretty easily hand roll a solution to replace it.

    But in some ways that's the best part. I've dropped a 50 line pattern that I like down to a one line Gemfile include. I'm making it even easier for myself to get some mileage out of experimenting with this code. I can mix and match this new library with other small tools to build up the ecosystem that I want for a project. Plus, if it turns out to be something I regret, it's not like I'm tied down to a huge dependency when I go to rip it out. This thinking actually has me wanting to keep this library minimal, at least for now.

    Read more…

  • 2

    JAN
    2008

    Getting FasterCSV Ready for Ruby 1.9

    The call came down from on high just before the Ruby 1.9 release: replace the standard csv.rb library with faster_csv.rb. With only hours to make the change it was a little harder than I expected. The FasterCSV code base was pretty vanilla Ruby, but it required more work than I would have guessed to get running on Ruby 1.9. Let me share a few of the tips I learned while doctoring the code in the hope that it will help others get their code ready for Ruby 1.9.

    Ruby's String Class Grows Up

    One of the biggest changes in Ruby 1.9 is the addition of m17n (multilingualization). This means that Ruby's Strings are now encoding aware and we must clarify in our code if we are working with bytes, characters, or lines.

    This is a good change, but the odds are that most of us have lazily used the old way to our advantage in the past. If you've ever written code like:

    lines = str.to_a
    

    you have bad habits to break. I sure did. Under Ruby 1.9 that code would translate to:

    lines = str.lines.to_a
    

    Read more…

  • 18

    NOV
    2007

    Ghost Wheel Example

    There has been a fair bit of buzz around the Treetop parser in the Ruby community lately. Part of that is fueled by the nice screencast that shows off how to use the parser generator.

    It doesn't get talked about as much, but I wrote a parser generator too, called Ghost Wheel. Probably the main reason Ghost Wheel doesn't receive much attention yet is that I have been slow in getting the documentation written. Given that, I thought I would show how the code built in the Treetop screencast translates to Ghost Wheel:

    #!/usr/bin/env ruby -wKU
    
    require "rubygems"
    require "ghost_wheel"
    
    # define a parser using Ghost Wheel's Ruby DSL
    RubyParser    = GhostWheel.build_parser do
      rule( :additive,
            alt( seq( :multiplicative,
                      :space,
                      :additive_op,
                      :space,
                      :additive ) { |add| add[0].send(add[2], add[-1])},
                 :multiplicative ) )
      rule(:additive_op, alt("+", "-"))
    
      rule( :multiplicative,
            alt( seq( :primary,
                      :space,
                      :multiplicative_op,
                      :space,
                      :multiplicative ) { |mul| mul[0].send(mul[2], mul[-1])},
                 :primary ) )
      rule(:multiplicative_op, alt("*", "/"))
    
      rule(:primary, alt(:parenthized_additive, :number))
      rule( :parenthized_additive,
            seq("(", :space, :additive, :space, ")") { |par| par[2] } )
      rule(:number, /[1-9][0-9]*|0/) { |n| Integer(n) }
    
      rule(:space, /\s*/)
      parser(:exp, seq(:additive, eof) { |e| e[0] })
    end
    
    # define a parser using Ghost Wheel's grammar syntax
    GrammarParser = GhostWheel.build_parser %q{
      additive             =  multiplicative space additive_op space additive
                              { ast[0].send(ast[2], ast[-1]) }
                           |  multiplicative
      additive_op          =  "+" | "-"
    
      multiplicative       =  primary space multiplicative_op space multiplicative
                              { ast[0].send(ast[2], ast[-1])}
                           |  primary
      multiplicative_op    =  "*" | "/"
    
      primary              = parenthized_additive | number
      parenthized_additive =  "(" space additive space ")" { ast[2] }
      number               =  /[1-9][0-9]*|0/ { Integer(ast) }
    
      space                =  /\s*/
      exp                  := additive EOF { ast[0] }
    }
    
    if __FILE__ == $PROGRAM_NAME
      require "test/unit"
    
      class TestArithmetic < Test::Unit::TestCase
        def test_paring_numbers
          assert_parses         "0"
          assert_parses         "1"
          assert_parses         "123"
          assert_does_not_parse "01"
        end
    
        def test_parsing_multiplicative
          assert_parses "1*2"
          assert_parses "1 * 2"
          assert_parses "1/2"
          assert_parses "1 / 2"
        end
    
        def test_parsing_additive
          assert_parses "1+2"
          assert_parses "1 + 2"
          assert_parses "1-2"
          assert_parses "1 - 2"
    
          assert_parses "1*2 + 3 * 4"
        end
    
        def test_parsing_parenthized_expressions
          assert_parses "1 * (2 + 3) * 4"
        end
    
        def test_parse_results
          assert_correct_result "0"
          assert_correct_result "1"
          assert_correct_result "123"
    
          assert_correct_result "1*2"
          assert_correct_result "1 * 2"
          assert_correct_result "1/2"
          assert_correct_result "1 / 2"
    
          assert_correct_result "1+2"
          assert_correct_result "1 + 2"
          assert_correct_result "1-2"
          assert_correct_result "1 - 2"
    
          assert_correct_result "1*2 + 3 * 4"
          assert_correct_result "1 * (2 + 3) * 4"
        end
    
        private
    
        PARSERS = [RubyParser, GrammarParser]
    
        def assert_parses(input)
          PARSERS.each do |parser|
            assert_nothing_raised(GhostWheel::FailedParseError) do
              parser.parse(input)
            end
          end
        end
    
        def assert_does_not_parse(input)
          PARSERS.each do |parser|
            assert_raises(GhostWheel::FailedParseError) { parser.parse(input) }
          end
        end
    
        def assert_correct_result(input)
          PARSERS.each { |parser| assert_equal(eval(input), parser.parse(input)) }
        end
      end
    end
    

    Read more…

  • 16

    APR
    2007

    No Longer the Fastest Game in Town

    If your number one concern when working with CSV data in Ruby is raw speed, you might want to know that FasterCSV is no longer the fastest option.

    There are a couple of new contenders for Ruby CSV processing including a C extension called SimpleCSV and a pure Ruby library called LightCsv. I haven't been able to test SimpleCSV locally, because I can't get it to build on my box, but users do tell me it's faster. I have run some trivial benchmarks for LightCsv though and it too is pretty quick:

    $ rake benchmark
    (in /Users/james/Documents/faster_csv)
    time ruby -r csv -e '6.times { CSV.foreach("test/test_data.csv") { |row| } }'
    
    real    0m5.481s
    user    0m5.468s
    sys     0m0.010s
    time ruby -r lightcsv -e \
    '6.times { LightCsv.foreach("test/test_data.csv") { |row| } }'
    
    real    0m0.358s
    user    0m0.349s
    sys     0m0.008s
    time ruby -r lib/faster_csv -e \
    '6.times { FasterCSV.foreach("test/test_data.csv") { |row| } }'
    
    real    0m0.742s
    user    0m0.732s
    sys     0m0.009s
    

    It's important to note that LightCsv is indeed very "light." FasterCSV has grown up into a feature rich library that provides many different ways to look at your data. In contrast, LightCsv doesn't yet allow you to set column or row separators. Given that, it's only an option for vanilla CSV you just need to iterate over. If that's what you have though, and speed counts, it might just be the right choice.

    Read more…

  • 1

    OCT
    2006

    I Just Want One Character!

    Every so often a person asks the question on Ruby Talk, "How can I get just one character from the keyboard (without needing the user to hit return)?" Everyone is always quick to post solutions, but sadly there are some issues with almost every one of them.

    The general consensus is that this is a tough problem to solve correctly. I say that's the exact reason to let HighLine handle this for you:

    #!/usr/bin/env ruby -w
    
    require "highline/system_extensions"
    include HighLine::SystemExtensions
    
    print "Enter one character:  "
    char = get_character
    puts char.chr
    

    That doesn't look too tough, does it?

    What's terrific about this solution is that under-the-hood HighLine will check your platform and libraries and then try to use the solution that makes the most sense for your environment. The code is really pretty robust too, because people a lot smarter than me have been sending in patches for over a year, slowly eliminating all of those tricky edge cases.

    As you can see, I've split this functionality of HighLine into a separate module so you don't even need to load the full HighLine system. This was done just because this is such a real and common problem. This section of HighLine is one pure Ruby file, so feel free to vendor it if the external dependency is an issue.

    Read more…