The Standard Library

Digging into those helpful libraries that ship with Ruby.
  • 13

    OCT
    2008

    The Secret Shell Helper

    Someone pops onto the Ruby Talk mailing list fairly regularly asking how to break up content like:

    one "two" "a longer three"
    

    They expect to end with a three element Array, where the third item will contain spaces. They generally expect the quotes will have been removed as well.

    If your needs are very, very simple you may be able to handle this with a regular expression:

    data = 'one "two" "a longer three"'
    p data.scan(/"([^"]*)"|(\S+)/).flatten.compact
    # >> ["one", "two", "a longer three"]
    

    That just searches for either a set of quotes with some non-quote characters between them or a run of non-whitespace characters. Those are the two possibilities for the fields. Note that the two separate capture here mean scan() will returns contents in the form:

    [[nil, "one"], ["two", nil], ["a longer three", nil]]
    

    That's why I added a flatten() and compact() to get down to the actual matches.

    The regular expression approach can get pretty complex though if any kind of escaping for quotes is involved. When that happens, you may need to step up to a parser.

    Read more…

  • 29

    FEB
    2008

    A Bug, Today Only

    I had to debug some tests that just started failing first thing this morning. I guess I should have procrastinated though, because they would have magically fixed themselves tomorrow morning. The cause of the one day only bug: Leap Year Day, of course.

    If you run the following code on a day like today, February 29th 2008, Date will choke on your invalid date:

    require "date"  
    
    class Date
      # Returns a Date in the past +year_offset+ years ago.
      def self.years_ago(year_offset)
        now = today
        Date.civil(now.year - year_offset, now.month, now.day)
      end
    end
    
    puts Date.years_ago(1)
    

    I came up with the following fix, which is accurate enough for my purposes:

    require "date"
    
    class Date   # Returns a Date in the past +year_offset+ years ago.
      def self.years_ago(year_offset)
        today - 365 * year_offset
      end
    end
    
    puts Date.years_ago(1)
    

    I'm not 100% sure that covers all cases though, so use with caution. Date's are tricky business!

  • 30

    JUL
    2006

    PStore Meets YAML

    I love the PStore standard library. It's a very graceful interface to get some fairly robust serialized mini-database handling in just a few lines. With it you get:

    1. Transactions with commit and rollbacks (automatic on exception).
    2. File locking, shared and exclusive.
    3. Multiprocessing safety.

    PStore does even more, including some file mode checking and MD5 hashing to avoid unneeded writes, but the above are the major selling points for me.

    Now, if I had to level any one complaint at PStore, it would be that because it uses Marshal under the hood it doesn't create files you can easily browse or tweak by hand. (Marshal is a feature, don't get me wrong. It's fast, which is very helpful.) Sometimes though I want PStore protection with the YAML file format.

    I'm embarrassed to admit that I use to use a hack for this:

    require "pstore"
    require "yaml"
    class PStore; Marshal = YAML; end
    

    That just redefines the Marshal constant in a scope that should only alter PStore. The library only uses dump() and load() and those methods work the same with Marshal and YAML.

    Read more…

  • 8

    JUL
    2006

    The Books are Wrong About Logger

    I've read several books that introduced the standard Logger library and they all agree on one thing: you can't customize the output. That's so last version in thinking! Behold…

    Here's a trivial Logger script, showing basic functionality:

    #!/usr/bin/env ruby -w
    
    require "logger"
    
    def expensive_error_report
      sleep 3  # Heavy Computation Simulation (patent pending)
      "YOU BROKE IT!"
    end
    
    log       = Logger.new(STDOUT)
    log.level = Logger::INFO  # set out output level above the DEBUG default
    
    log.debug("We're not in the verbose debug mode.")
    log.info("We do see informative logs though.")
    if log.error?  # check that this will be printed, before waste time
      log.error(expensive_error_report)
    end
    

    If you run that you will see:

    I, [2006-07-08T11:17:19.531943 #340]  INFO -- : We do see informative logs though.
    E, [2006-07-08T11:17:22.532424 #340] ERROR -- : YOU BROKE IT!
    

    Now everyone has always known you can format the date and time display using a strftime() compatible pattern:

    #!/usr/bin/env ruby -w
    
    require "logger"
    
    def expensive_error_report
      sleep 3
      "YOU BROKE IT!"
    end
    
    log                 = Logger.new(STDOUT)
    log.level           = Logger::INFO
    log.datetime_format = "%Y-%m-%d %H:%M "  # simplify time output
    
    log.debug("We're not in the verbose debug mode.")
    log.info("We do see informative logs though.")
    if log.error?
      log.error(expensive_error_report)
    end
    

    Read more…

  • 4

    JAN
    2006

    Pathname and Enumerator

    I'm always digging around in Ruby's standard library looking for new toys. It's one-stop-shopping for geeks. I love it.

    There really are some great tools in there that can help you do your work easier. Let me tell you a little about two I am using on a current application.

    Pathname

    Pop Quiz: Which would you rather work with?

    if File.exist? some_dir
      data = File.read( File.join( File.dirname(some_dir),
                                   "another_dir",
                                   File.basename(some_file) ) )
      # ...
    end
    

    Or:

    if some_dir.exist?
      data = (some_dir.dirname + "another_dir" + some_file.basename).read
      # ...
    end
    

    If you prefer the second version, the Pathname library is for you.

    Ruby's file manipulations are usually fine for normal work, but anytime I start messing with directories I really start feeling the pain. For that kind of work, I find Pathname to be a superior interface. My current project makes great use of the methods in the above example as well as Pathname#entries, Pathname#relative_path_from, and more.

    Read more…