Q1解答

3つ解答があってまず一つ目。

# use Ruby's standard template engine
require "erb"

# storage for keyed question reuse
$answers = Hash.new

# asks a madlib question and returns an answer
def q_to_a( question )
  question.gsub!(/\s+/, " ")       # normalize spacing
  
  if $answers.include? question    # keyed question
    $answers[question]
  else                             # new question
    key = if question.sub!(/^\s*(.+?)\s*:\s*/, "") then $1 else nil end
    
    print "Give me #{question}:  "
    answer = $stdin.gets.chomp
    
    $answers[key] = answer unless key.nil?
    
    answer
  end
end

# usage
unless ARGV.size == 1 and test(?e, ARGV[0])
  puts "Usage:  #{File.basename($PROGRAM_NAME)} MADLIB_FILE"
  exit  
end

# load Madlib, with title
madlib = "\n#{File.basename(ARGV.first, '.madlib').tr('_', ' ')}\n\n" +
         File.read(ARGV.first)
# convert ((...)) to <%= q_to_a('...') %>
madlib.gsub!(/\(\(\s*(.+?)\s*\)\)/, "<%= q_to_a('\\1') %>")
# run template
ERB.new(madlib).run

'(('と'))'に挟まれた部分をERBの式に置き換えてERBに値の埋め込みをやらせるという方法。
正直ね、"その発想はなかったわ"という言葉を実際言ってしまいそうになる手法。いや、まぁなんでもありだからこれもありだけどさ。。。。
勉強になったのはtestという関数かな。ファイルが存在してるかとかファイルのサイズが0でないとかの確認が簡単にできる関数。
まぁ知ってると便利だよね。

次、2つ目

# A placeholder in the story for a reused value.
class Replacement
  # Only if we have a replacement for a given token is this class a match.
  def self.parse?( token, replacements )
    if token[0..1] == "((" and replacements.include? token[2..-1]
      new(token[2..-1], replacements)
    else
      false
    end
  end
  
  def initialize( name, replacements )
    @name         = name
    @replacements = replacements
  end
  
  def to_s
    @replacements[@name]
  end
end



# A question for the user, to be replaced with their answer.
class Question
  # If we see a ((, it's a prompt.  
 # Save their answer if a name is given.
  def self.parse?( prompt, replacements )
    if prompt.sub!(/^\(\(/, "")
      prompt, name = prompt.split(":").reverse

      replacements[name] = nil unless name.nil?
      
      new(prompt, name, replacements)
    else
      false
    end
  end
  
  def initialize( prompt, name, replacements )
    @prompt       = prompt
    @name         = name
    @replacements = replacements
  end
  
  def to_s
    print "Enter #{@prompt}:  "
    answer = $stdin.gets.to_s.strip
  
    @replacements[@name] = answer unless @name.nil?
    
    answer
  end
end



# Ordinary prose.
class String
  # Anything is acceptable.
  def self.parse?( token, replacements )
    new(token)
  end
end



# argument parsing
unless ARGV.size == 1 and test(?e, ARGV[0])
  puts "Usage:  #{File.basename($PROGRAM_NAME)} MADLIB_FILE"
  exit  
end
madlib = <<MADLIB

#{File.basename(ARGV.first, ".madlib").tr("_", " ")}

#{File.read(ARGV.first)}
MADLIB

# tokenize input
tokens = madlib.split(/(\(\([^)]+)\)\)/).map do |token|
  token[0..1] == "((" ? token.gsub(/\s+/, " ") : token
end

# identify each part of the story
answers = Hash.new
story   = tokens.map do |token|
  [Replacement, Question, String].inject(false) do |element, kind|
    element = kind.parse?(token, answers) and break element
  end
end

# share the results
puts story.join

これは"apple ((orange)) ((tag:banana))"という文字列をそれぞれ"apple", "((orange", ")((tag:banana"
と区切ってReplacement型, Question型(, String型に対応させて型ごとに解析メソッドを書くやりかた。
それぞれの型はReplacementは")(("と"))"にはさまれていてすでに入力された文字列を扱う型、
Questionは"(("と"))"にはさまれていてまだ入力されていない文字列を扱う型、
StringはReplacementとQuestion以外の文字列を扱う型ってことみたい。
なんかすごいオブジェクト指向っぽい感じ。
で、勉強になったのは、
"prompt, name = prompt.split(":").reverse"
の部分。 初め"name, prompt = prompt.split(":")"にしても同じじゃねーか、さぼってんのか?と思ったけど
実は意味あって"name, prompt = prompt.split(":")"だと":"でsplitできなかった時promptがnilになって画面になにも出なかったりする。
なんで、"prompt, name = prompt.split(":").reverse"として":"でsplitできる場合もできない場合もかならずpromptには何かが入ってるようにしてある。
いや、ぶっちゃけわかりづらいんだけど、なんかハカーって感じでちょっと感動した。
あと、勉強になったのは
"element = kind.parse?(token, answers) and break element"のbreakの使い方。
rubyのbreakはバージョン1.7から引数がとれて引数をとった場合その引数を戻り値として返せるらしい。
なかなかcoolですな。

で、最後3つ目

keys=Hash.new { |h, k|
   puts "Give me #{k.sub(/\A([^:]+):/, "")}:"
   h[$1]=$stdin.gets.chomp
}
puts "", $*[0].split(".")[0].gsub("_", " "),
     IO.read($*[0]).gsub(/\(\(([^)]+)\)\)/) { keys[$1] }

んーかなり神がかったコードで説明もむずいんで明日やろう。

続く