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] }
んーかなり神がかったコードで説明もむずいんで明日やろう。
続く