-
17
MAY
2014A 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.
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. -
2
JAN
2008Getting 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 withfaster_csv.rb
. With only hours to make the change it was a little harder than I expected. TheFasterCSV
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 UpOne 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
-
18
NOV
2007Ghost 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
-
16
APR
2007No 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 forLightCsv
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. -
1
OCT
2006I 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 fullHighLine
system. This was done just because this is such a real and common problem. This section ofHighLine
is one pure Ruby file, so feel free to vendor it if the external dependency is an issue.