-
-
Save alloy-d/990435 to your computer and use it in GitHub Desktop.
#!/usr/bin/env ruby | |
require 'net/smtp' | |
require 'time' | |
require 'yaml' | |
size_map = { | |
'S' => 'small', | |
'M' => 'medium', | |
'L' => 'large', | |
'XL' => 'extra large', | |
} | |
number_map = { | |
2 => 'two', | |
} | |
def wrap_text(s, width=72) | |
s.gsub(/(.{1,#{width}})(\s+|\Z)/, "\\1\n") | |
end | |
if ARGV.length < 2 then | |
STDERR.puts "Give me an operation and a YAML file!" | |
exit(-1) | |
end | |
people = YAML.load_file ARGV[1] | |
verify = lambda do | |
people.each do |p| | |
shirt_string = '' | |
if not p['shirts'].nil? then | |
grammatical_items = p['shirts'].length | |
p['shirts'].each_with_index do |s, i| | |
quantity = (s['quantity'] if not s['quantity'].nil?) || 1 | |
known = { | |
:size => (not s['size'].nil?), | |
:color => (not s['color'].nil?), | |
} | |
if quantity > 1 then | |
shirt_string += number_map[s['quantity']] + ' ' | |
else | |
if grammatical_items > 1 then | |
shirt_string += 'one ' | |
# Call me crazy, but explicitly saying "one" when | |
# someone only wants one shirt total seems really | |
# stiff and awkward, but it makes perfect sense | |
# if they want more than that... | |
elsif known[:size] and size_map[s['size']][0] == 'e' then | |
shirt_string += 'an ' | |
else | |
shirt_string += 'a ' | |
end | |
end | |
shirt_string += size_map[s['size']] + ' ' if known[:size] | |
shirt_string += s['color'] + ' ' if known[:color] | |
shirt_string += 'shirt' | |
shirt_string += 's' if quantity > 1 | |
case | |
when (not known[:size] and not known[:color]) | |
shirt_string += ' of unknown size and color' | |
when (not known[:size]) | |
shirt_string += ' of unknown size' | |
when (not known[:color]) | |
shirt_string += ' of unknown color' | |
end | |
if i < grammatical_items-1 then | |
if grammatical_items > 2 then | |
shirt_string += ', ' | |
shirt_string += 'and ' if i == grammatical_items - 2 | |
else | |
shirt_string += ' and ' | |
end | |
end | |
end | |
else | |
shirt_string = 'a shirt' | |
end | |
out = p['given'] + ' ' + p['family'] + ' ' | |
out += "<#{p['email']}> " if p['email'] | |
out += 'wants ' + shirt_string + '.' | |
puts wrap_text(out) + "\n" | |
end | |
end | |
mail = lambda do | |
people.each do |p| | |
message = <<EOF | |
From: Adam Lloyd <[email protected]> | |
To: #{p['given']} #{p['family']} <#{p['email']}> | |
Subject: 2011 Congress of Jugglers T-shirts | |
Date: #{Time.now.rfc2822} | |
Hi, #{p['given']}! | |
We are putting together an order for more 2011 Congress shirts. | |
EOF | |
shirt_string = '' | |
unknown_string = '' | |
if not p['shirts'].nil? then | |
grammatical_items = p['shirts'].length | |
p['shirts'].each_with_index do |s, i| | |
quantity = (s['quantity'] if not s['quantity'].nil?) || 1 | |
known = { | |
:size => (not s['size'].nil?), | |
:color => (not s['color'].nil?), | |
} | |
if quantity > 1 then | |
shirt_string += number_map[s['quantity']] + ' ' | |
else | |
if grammatical_items > 1 then | |
shirt_string += 'one ' | |
# Call me crazy, but explicitly saying "one" when | |
# someone only wants one shirt total seems really | |
# stiff and awkward, but it makes perfect sense | |
# if they want more than that... | |
elsif known[:size] and size_map[s['size']][0] == 'e' then | |
shirt_string += 'an ' | |
else | |
shirt_string += 'a ' | |
end | |
end | |
shirt_string += size_map[s['size']] + ' ' if known[:size] | |
shirt_string += s['color'] + ' ' if known[:color] | |
shirt_string += 'shirt' | |
shirt_string += 's' if quantity > 1 | |
case | |
when (not known[:size] and not known[:color]) | |
if (quantity == 1) then | |
unknown_string += 'size and color' | |
else | |
unknown_string += 'sizes and colors' | |
end | |
when (not known[:size]) | |
unknown_string += 'size' | |
when (not known[:color]) | |
unknown_string += 'color' | |
end | |
if i < grammatical_items-1 then | |
if grammatical_items > 2 then | |
shirt_string += ', ' | |
shirt_string += 'and ' if i == grammatical_items - 2 | |
else | |
shirt_string += ' and ' | |
end | |
end | |
end | |
else | |
shirt_string = 'a shirt' | |
unknown_string = 'size and color' | |
end | |
if unknown_string != '' then | |
message += wrap_text <<EOF | |
At Congress, you signed up for #{shirt_string}, but we need to know what #{unknown_string} you'd like. | |
EOF | |
else | |
message += wrap_text <<EOF | |
At Congress, you signed up for #{shirt_string}. I'd just like to make sure this is correct before we send off the order. | |
EOF | |
end | |
message += "\n" | |
message += wrap_text <<EOF | |
If you'd like to take another look at the shirts, there are pictures available at <http://studentorg.umd.edu/juggling/congress/2011/#shirts>. | |
EOF | |
message += "\n" + <<EOF | |
Thank you! | |
-Adam Lloyd, UMD Juggling Club | |
EOF | |
if (not p['email'].nil?) then | |
Net::SMTP.start('127.0.0.1', 25) do |smtp| | |
smtp.send_message(message, | |
'[email protected]', | |
p['email']) | |
end | |
else | |
STDERR.puts "#{p['given']} #{p['family']} has no email address!" | |
end | |
end | |
end | |
if ARGV[0] == "verify" then | |
verify.call | |
elsif ARGV[0] == "mail" then | |
mail.call | |
end |
- given: John | |
family: Example | |
email: [email protected] | |
shirts: | |
- color: brown | |
size: M | |
- color: blue | |
size: S | |
quantity: 2 |
Also, it seems you tried to be robust and build code to handle "unknown size" and "unknown color." have you written any tests for this?
Given the amount of data that needs to be passed around to make sure that sentences are composed to be grammatically correct (or even sensible), "smaller utility methods" would (I think) do more to clutter up the code than make it clearer.
Also, the goal of this is just to get emails out to people. Adding code for "unknown size" and "unknown color" isn't just some goofy attempt at robustness, it's necessary code to account for the fact that, astoundingly, not everyone who wants a shirt supplies the information required to get one. (But to answer your question, the "test" is the actual list of shirt requests, in combination with my proofreading of the final emails.)
I like the idea of this. But since
I can't quite tell what this looks like when it's user-facing. I note, though, that lines 153--156 suggest you're going four levels deep in abstraction. That seems like a lot; like this code is crying out for being broken into smaller utility methods whose individual responsibilities are clearer.