--Boundary-00HPgGLx5wXMeysx
Content-Type: Multipart/Mixed;
  boundaryoundary-00HPgGLx5wXMeysx"

--Boundary-00HPgGLx5wXMeysx
Content-Type: text/plain;
  charsettf-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-00HPgGLx5wXMeysx
Content-Type: application/x-ruby;
  nameames_db.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
	filenameames_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-00HPgGLx5wXMeysx
Content-Type: application/x-ruby;
  nameickerface.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
	filenameickerface.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-00HPgGLx5wXMeysx
Content-Type: application/x-ruby;
  nameick.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
	filenameick.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-00HPgGLx5wXMeysx
Content-Type: application/x-ruby;
  nameame_image.rb"
Content-Transfer-Encoding: 7bit
Content-Disposition: attachment;
	filenameame_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-00HPgGLx5wXMeysx--
--Boundary-00HPgGLx5wXMeysx--