--Boundary-00 HPgGLx5wXMeysx
Content-Type: Multipart/Mixed;
boundaryoundary-00 HPgGLx5wXMeysx"
--Boundary-00 HPgGLx5wXMeysx
Content-Type: text/plain;
charset tf-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::Database (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
jessemerriman / warpmail.net
http://www.jessemerriman.com/
--Boundary-00 HPgGLx5wXMeysx
Content-Type: application/x-ruby;
name ames_db.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename ames_db.rb"
require 'sqlite'
# NamesDB wraps up a SQLite::Database.
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 alse, optimize rue
raise NonexistantDB if not create and not File.exists? filename
@filename ilename
@db QLite::Database.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[:pick].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 FF;'
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[:pick] db.prepare <<-SQL
SELECT name FROM ? ORDER BY random() LIMIT 1;
SQL
end
end
--Boundary-00 HPgGLx5wXMeysx
Content-Type: application/x-ruby;
name ickerface.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename ickerface.rb"
require 'names_db'
require 'name_image'
# Pickerface handles the interface for pick.rb.
class Pickerface
def initialize filename
@db_file ilename
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 amesDB.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 b.pick
db.delete_name name
yield name
else
yield 'No one left!'
end
end
end
end
--Boundary-00 HPgGLx5wXMeysx
Content-Type: application/x-ruby;
name ick.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename ick.rb"
#!/usr/bin/env ruby
# uby 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 etoptLong.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 il, []
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 pick_simple
when '--pick-fancy'; op pick_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 RGV.first
if op.nil?
File.exists?(db_file) ? op pick_fancy : op add_names
end
# Run.
Pickerface.new(db_file).send(op, *args)
end
--Boundary-00 HPgGLx5wXMeysx
Content-Type: application/x-ruby;
name ame_image.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
filename ame_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 dd_ripple(plain(name))
img.border! 4, 4, Colors[:border_in]
img.border! 4, 4, Colors[:border_out]
img dd_stars img
shake img
end
# Create a rather plain Image.
def NameImage.plain name
img agick::Image.new(1, 1) { self.background_color olors[:bg] }
gc agick::Draw.new
metrics c.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 agick::LighterWeight
gc.pointsize 0
gc.gravity agick::SouthEastGravity
gc.stroke olors[:text_stroke]
gc.fill olors[: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 agick::ImageList.new
xform agick::AffineMatrix.new 1.0, 0.0, Math::PI/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 agick::ImageList.new(StarFile).first
img_list agick::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 agick::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
animation.iterations 0000
animation
end
end
--Boundary-00 HPgGLx5wXMeysx--
--Boundary-00 HPgGLx5wXMeysx--