--Boundary-00=_YHPgGLx5wXMeysx
Content-Type: Multipart/Mixed;
boundary="Boundary-00=_YHPgGLx5wXMeysx"
--Boundary-00=_YHPgGLx5wXMeysx
Content-Type: text/plain;
charset="utf-8"
Content-Transfer-Encoding: 7bit
Content-Disposition: inline
My solution uses SQLite to store a DB of names, and RMagick to display chosen
names. Consider the display part a first draft - I think I'll redo it, maybe
in a completely different way - and submit again later, though the backend
shouldn't change.
There are 4 files that roughly follow MVC:
names_db.rb: Wrapper around a SQLite:

atabase (M)
pick.rb: Main script (C)
pickerface.rb: Interface stuff (V)
name_image.rb: RMagick stuff (V)
The DB stores just one thing: names. When a name is chosen, its simply deleted.
I thought of marking the names as either picked or unpicked, or storing chosen
names in another table, but I'm not sure if that would be used, and in any
event it would be easy enough to add if needed.
When pick.rb is run without a command option, it either creates a DB and starts
adding names if the DB doesn't exist, or picks and displays a name if it does.
See the top of pick.rb, or run pick.rb --help for a full usage message.
For all text-mode-only operations, names can be Unicode, but RMagick doesn't
seem to like that, so they can't be displayed nicely. Magick::ImageList.animate
is used for displaying, so it won't work for any name on Windows.
I bracket chosen names in stars taken from:
http://lonestarrubyconf.com/stylesheets/lsrc_2007/images/LSRC_big_title.jpg
I'm not sure this list will accept messages containing graphics, so you'll
have to fetch this image and place it in the same directory as my code:
http://www.jessemerriman.com/images/ruby_quiz/129_lsrc_name_picker/star_small.png
--
Jesse Merriman
(e-mail address removed)
http://www.jessemerriman.com/
--Boundary-00=_YHPgGLx5wXMeysx
Content-Type: application/x-ruby;
name="names_db.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="names_db.rb"
require 'sqlite'
# NamesDB wraps up a SQLite:

atabase.
class NamesDB
class AlreadyAdded < StandardError; end
class NonexistantDB < StandardError; end
NamesTable = 'names'
# Initialize a new NamesDB.
# - filename: The filename of the DB.
# - create: If true, and the DB doesn't exist, it'll be created. If false,
# and the DB doesn't exist, raise a NonexistantDB exception.
# - optimize: Enable optimizations.
def initialize filename, create = false, optimize = true
raise NonexistantDB if not create and not File.exists? filename
@filename = filename
@db = SQLite:

atabase.new filename
create_names_table if @db.table_info(NamesTable).empty?
enable_optimizations if optimize
prepare_statements
self
end
# Add a name to the DB.
def add_name name
begin
@statements[:add_name].execute! NamesTable, name
rescue SQLite::Exceptions::SQLException
raise AlreadyAdded.new, "#{name} is already in the DB!"
end
end
# Delete a name from the DB.
def delete_name name
@statements[:delete_name].execute! NamesTable, name
end
# Yield each name in the DB.
def each_name
@statements[:each_name].execute!(NamesTable) { |res| yield res[0] }
end
# Clear the DB of all names.
def clear
@statements[:clear].execute! NamesTable
end
# Destroy the DB.
def destroy
@db.close
File.delete @filename
end
# Return a randomly-chosen name, or nil if there are non left.
def pick
res = @statements[

ick].execute! NamesTable
res.empty? ? nil : res[0][0]
end
private
def create_names_table
@db.execute <<-SQL, NamesTable
CREATE TABLE ? (
name TEXT PRIMARY KEY
);
SQL
end
def enable_optimizations
@db.execute 'PRAGMA default_synchronous = OFF;'
end
def prepare_statements
@statements = {}
@statements[:add_name] = @db.prepare 'INSERT INTO ? (name) VALUES (?);'
@statements[:delete_name] = @db.prepare 'DELETE FROM ? WHERE name = ?;'
@statements[:each_name] = @db.prepare 'SELECT name FROM ?;'
@statements[:clear] = @db.prepare 'DELETE FROM ?; VACUUM;'
@statements[

ick] = @db.prepare <<-SQL
SELECT name FROM ? ORDER BY random() LIMIT 1;
SQL
end
end
--Boundary-00=_YHPgGLx5wXMeysx
Content-Type: application/x-ruby;
name="pickerface.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="pickerface.rb"
require 'names_db'
require 'name_image'
# Pickerface handles the interface for pick.rb.
class Pickerface
def initialize filename
@db_file = filename
self
end
# Read names from standard input and add the to the DB.
def add_names
puts 'Enter names to add, one per line. Blank or ^D to stop.'
db = NamesDB.new @db_file, true
while name = $stdin.gets and name != "\n"
begin
db.add_name name.chomp
rescue NamesDB::AlreadyAdded => ex
$stderr.puts ex
end
end
end
# List all names to standard output.
def list_names
existing_db do |db|
puts str = "Contents of #{@db_file}:"
puts '-' * str.length
db.each_name { |name| puts name }
end
end
# Clear the DB of all names.
def clear
existing_db do |db|
db.clear
puts "#{@db_file} has been cleared of all names."
end
end
# Randomly choose a name, write it to standard output, and delete it from the
# DB.
def pick_simple
pick { |name| puts name }
end
# Randomly choose a name, display it in a fancy way, and delete it from the
# DB.
def pick_fancy
pick { |name| NameImage.fancy(name).animate }
end
# Like pick_fancy, but save the image to the given filename instead of
# displaying it.
def save_fancy filename
pick { |name| NameImage.fancy(name).write(filename) }
end
# Destroy the DB.
def destroy
existing_db do |db|
db.destroy
puts "#{@db_file} has been destroyed."
end
end
private
# Yield a NamesDB if @db_file exists, otherwise print an error.
def existing_db
begin
yield NamesDB.new(@db_file)
rescue NamesDB::NonexistantDB
$stderr.puts "Error: #{@db_file} does not exist!"
end
end
# Yield a randomly-picked name, or a message that there are none left.
def pick
existing_db do |db|
if name = db.pick
db.delete_name name
yield name
else
yield 'No one left!'
end
end
end
end
--Boundary-00=_YHPgGLx5wXMeysx
Content-Type: application/x-ruby;
name="pick.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="pick.rb"
#!/usr/bin/env ruby
# = Ruby Quiz 129: LSRC Name Picker
# Author: Jesse Merriman
#
# == Usage
#
# pick.rb [OPTION] [DB-FILE]
#
# -h, --help:
# Print out usage message.
#
# -a, --add-names:
# Take a list of names on standard input and add them to the DB. Creates the
# DB if it does not exist.
#
# -l, --list-names:
# List out all names in the DB to standard output, if it exists.
#
# -c, --clear-names:
# Clear the DB of all names, if it exists.
#
# -p, --pick-simple
# Randomly choose an unchosen name from the DB, if it exists.
#
# -f, --pick-fancy
# Like --pick, but display the chosen name in a fancy way.
#
# -s, --save-fancy FILENAME
# Like --pick-fancy, but save the graphic to FILENAME instead of displaying.
# Very SLOW.
#
# -d, --destroy-db:
# Destroy the DB, if it exists.
#
# If no option is given, the default is --add-names if no DB exists, or
# --pick-fancy if it does.
#
# DB-FILE defaults to 'names.db'.
require 'pickerface'
require 'getoptlong'
require 'rdoc/usage'
if __FILE__ == $0
Opts = GetoptLong.new(
[ '--help', '-h', GetoptLong::NO_ARGUMENT ],
[ '--add-names', '-a', GetoptLong::NO_ARGUMENT ],
[ '--list-names', '-l', GetoptLong::NO_ARGUMENT ],
[ '--clear-names', '-c', GetoptLong::NO_ARGUMENT ],
[ '--pick-simple', '-p', GetoptLong::NO_ARGUMENT ],
[ '--pick-fancy', '-f', GetoptLong::NO_ARGUMENT ],
[ '--save-fancy', '-s', GetoptLong::REQUIRED_ARGUMENT ],
[ '--destroy-db', '-d', GetoptLong::NO_ARGUMENT ] )
# Handle arguments.
op, args = nil, []
Opts.each do |opt, arg|
case opt
when '--help'; RDoc::usage
when '--add-names'; op = :add_names
when '--list-names'; op = :list_names
when '--clear-names'; op = :clear
when '--pick-simple'; op =

ick_simple
when '--pick-fancy'; op =

ick_fancy
when '--save-fancy'; op = :save_fancy; args << arg
when '--destroy-db'; op = :destroy
end
end
# Setup default arguments.
ARGV.empty? ? db_file = 'names.db' : db_file = ARGV.first
if op.nil?
File.exists?(db_file) ? op =

ick_fancy : op = :add_names
end
# Run.
Pickerface.new(db_file).send(op, *args)
end
--Boundary-00=_YHPgGLx5wXMeysx
Content-Type: application/x-ruby;
name="name_image.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename="name_image.rb"
require 'RMagick'
# A few methods for creating images of names with RMagick.
module NameImage
StarFile = './star_small.png'
Colors = { :bg => 'black', :border_in => 'darkblue', :border_out => 'black',
:text_stroke => 'darkblue', :text_fill => 'gold' }
# Create a fancy animated ImageList.
def NameImage.fancy name
img = add_ripple(plain(name))
img.border! 4, 4, Colors[:border_in]
img.border! 4, 4, Colors[:border_out]
img = add_stars img
shake img
end
# Create a rather plain Image.
def NameImage.plain name
img = Magick::Image.new(1, 1) { self.background_color = Colors[:bg] }
gc = Magick:

raw.new
metrics = gc.get_multiline_type_metrics img, name
img.resize! metrics.width * 4, metrics.height * 4
gc.annotate(img, 0, 0, 0, 0, name) do |gc|
gc.font_family = 'Verdana'
gc.font_weight = Magick::LighterWeight
gc.pointsize = 40
gc.gravity = Magick::SouthEastGravity
gc.stroke = Colors[:text_stroke]
gc.fill = Colors[:text_fill]
end
img
end
# Create a new image consisting of img and a rippling reflection under it.
def NameImage.add_ripple img
img_list = Magick::ImageList.new
xform = Magick::AffineMatrix.new 1.0, 0.0, Math:

I/4.0, 1.0, 0.0, 0.0
img_list << img
img_list << img.wet_floor(0.5, 0.7).affine_transform(xform).
rotate(90).wave(2, 10).rotate(-90)
img_list.append true
end
# Create a new image consisting of img with stars on either side.
def NameImage.add_stars img
begin
star = Magick::ImageList.new(StarFile).first
img_list = Magick::ImageList.new
img_list << star.copy
img_list << img
img_list << star
img_list.append false
rescue Magick::ImageMagickError
$stderr.puts "Couldn't open #{StarFile}. Did you download it?"
img
end
end
# Create an animated ImageList of img shaking.
def NameImage.shake img
animation = Magick::ImageList.new
20.times { animation << img.copy }
0.3.step(0.6, 0.3) do |deg|
animation << img.rotate(deg)
animation << img.rotate(-deg)
end
animation << img.radial_blur(6)
animation.delay = 5
animation.iterations = 10000
animation
end
end
--Boundary-00=_YHPgGLx5wXMeysx--
--Boundary-00=_YHPgGLx5wXMeysx--