Skip to content

Instantly share code, notes, and snippets.

@h3rald
Created September 11, 2011 19:18
Show Gist options
  • Save h3rald/1209991 to your computer and use it in GitHub Desktop.
Save h3rald/1209991 to your computer and use it in GitHub Desktop.
A simple DSL to create checklists and questionnaires.
class Symbol
def / sym
:"#{self}/#{sym}"
end
def ~
Checklist.lists[self].start
end
end
module Checklist
class << self; attr_accessor :lists; end
def checklist label=nil, description=nil, &block
label ||= "list-#{Time.now.to_i}-#{rand 1000}"
Checklist.lists ||= {}
Checklist.lists[label] = List.new label, description, &block
end
class List
attr_accessor :current, :score_override, :total_override
attr_reader :checks
def initialize label, description=nil, &block
@label = label
@description = description
@checks = []
instance_eval &block
end
def check description=nil, options={}, &block
options[:checklist] ||= self
@checks << Check.new(description, options, &block)
end
def previous
@current > 0 ? @checks[@current-1].run : @checks[@current].run
end
def next
available = @current+1 <= @checks.size-1
if available then
@checks[@current+1].run
else
@checks[@current].answer ? finish : @checks[@current].run
end
end
def start
@checks.first.run
end
def finish
report
exit
end
def report
puts
puts "="*50
@checks.each_with_index do |check, i|
puts "Check\t##{i+1}: #{check.score}/#{check.total}"
end
puts "-"*50
puts "Score: #{score}/#{total}"
puts "="*50
end
def score
@checks.map{|c| c.score}.reduce :"+"
end
def total
@checks.map{|c| c.total}.reduce :"+"
end
end
class Check
attr_reader :answer, :score
def initialize description=nil, options={}, &block
@description = description
@options = options
@options[:value] ||= 1
@options[:mandatory] ||= false
@score = 0
instance_eval &block if block_given?
raise RuntimeError, "Task #{@options[:label]} is not assigned to any checklist" unless @options[:checklist]
raise RuntimeError, "No description provided for task #{@options[:label]}" unless @description
[:value, :label, :mandatory, :checklist].each do |v|
var = :"@#{v}"
instance_variable_set var, @options[v] unless instance_variable_get var
end
end
def total
if @choices then
@choices.map{|c| c[:value]}.max
else
@value
end
end
def description text
@description = text
end
def mandatory
@mandatory = true
end
def beforehand &block
@beforehand = block
end
def afterwards &block
@afterwards = block
end
def value n
@value = n
end
def choice description, value=1
@choices ||= []
@choices << {description: description, value: value}
end
def run
@checklist.current = @checklist.checks.index self
instance_eval &@beforehand if @beforehand
loop do
puts "="*50
puts "Check #{@checklist.current+1}/#{@checklist.checks.size} (Score: #{@checklist.score}/#{@checklist.total})"
puts "-"*50
describe
break if (@choices ? choose : confirm)
end
instance_eval &@afterwards if @afterwards
@checklist.next
end
protected
def describe
print @description
end
def choose
print "\n"
@choices.each_with_index do |choice, i|
puts "-- #{i+1}. #{choice[:description]}"
end
@answer = input do |answer|
sel = answer.to_i
sel > 0 && sel <= @choices.size
end
@score = @choices[@answer.to_i-1][:value] if @answer
end
def confirm
print " [y/n]\n"
@answer = input do |answer|
answer =~ /^(y(es)?|n(o)?)$/i
end
if @answer then
@score = @answer =~ /^y(es)?$/i ? @value : 0
end
end
def finish
@checklist.finish
end
def separate
puts '='*50
end
def input(message="=> Error: Invalid answer.", prompt='~> ', &block)
puts "~"*50
print prompt
answer = gets.chomp
case answer
when '<' then
@checklist.previous
return
when '>' then
if @mandatory then
message = "=> Error: check cannot be skipped."
else
@checklist.next
return
end
when '<<' then
@checklist.start
return
when '>>' then
@checklist.finish
end
valid = block.call(answer)
if valid then
answer
else
puts message
false
end
end
end
end
###########################
include Checklist
checklist :test/:checklist do
check "Ready?"
check do
description "Choose something"
mandatory
choice "Do this"
choice "Do that", 0
choice "None of the above", 2
afterwards do
if @score > 1 then
finish
end
end
end
check do
description "Do something else?"
value 3
end
check "OK?", value: 10, mandatory: true
end
~(:test/:checklist)
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment