-
13
OCT
2008The 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()
andcompact()
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.
-
29
FEB
2008A 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
2006PStore 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:- Transactions with commit and rollbacks (automatic on exception).
- File locking, shared and exclusive.
- 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 usesMarshal
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 wantPStore
protection with theYAML
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 alterPStore
. The library only usesdump()
andload()
and those methods work the same withMarshal
andYAML
. -
8
JUL
2006The 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
-
4
JAN
2006Pathname 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 asPathname#entries
,Pathname#relative_path_from
, and more.