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
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.
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,
Datewill 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!
PStore Meets YAML
I love the
PStorestandard 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.
PStoredoes 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
Marshalunder the hood it doesn't create files you can easily browse or tweak by hand. (
Marshalis a feature, don't get me wrong. It's fast, which is very helpful.) Sometimes though I want
PStoreprotection with the
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
Marshalconstant in a scope that should only alter
PStore. The library only uses
load()and those methods work the same with
The Books are Wrong About Logger
I've read several books that introduced the standard
Loggerlibrary 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
Loggerscript, 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
#!/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
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.
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
if some_dir.exist? data = (some_dir.dirname + "another_dir" + some_file.basename).read # ... end
If you prefer the second version, the
Pathnamelibrary 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
Pathnameto be a superior interface. My current project makes great use of the methods in the above example as well as
Pathname#relative_path_from, and more.