gipper 0.0.1
Sign up to get free protection for your applications and to get access to all the features.
- data/.gitignore +7 -0
- data/PostInstall.txt +5 -0
- data/README.rdoc +49 -0
- data/Rakefile +69 -0
- data/VERSION.yml +4 -0
- data/docs/gift_reference.pdf +0 -0
- data/features/fixtures/matching.gift +9 -0
- data/features/fixtures/missing_word.gift +8 -0
- data/features/fixtures/multiple_choice.gift +41 -0
- data/features/fixtures/numerical.gift +10 -0
- data/features/fixtures/short_answer.gift +10 -0
- data/features/fixtures/super_gift.txt +169 -0
- data/features/fixtures/true_or_false.gift +19 -0
- data/features/gift_parsing.feature +10 -0
- data/features/parse_matching_questions.feature +10 -0
- data/features/parse_missing_word.feature +10 -0
- data/features/parse_multiple_choice_questions.feature +10 -0
- data/features/parse_numerical_questions.feature +10 -0
- data/features/parse_short_answer_questions.feature +10 -0
- data/features/parse_true_or_false_questions.feature +10 -0
- data/features/steps/gift_parsing_steps.rb +546 -0
- data/features/steps/parse_matching_steps.rb +30 -0
- data/features/steps/parse_missing_word_steps.rb +58 -0
- data/features/steps/parse_multiple_choice_steps.rb +107 -0
- data/features/steps/parse_numerical_steps.rb +35 -0
- data/features/steps/parse_short_answer_steps.rb +42 -0
- data/features/steps/parse_steps.rb +7 -0
- data/features/steps/parse_true_or_false_steps.rb +61 -0
- data/features/support/env.rb +10 -0
- data/lib/answer.rb +165 -0
- data/lib/answers.rb +99 -0
- data/lib/gipper.rb +15 -0
- data/lib/question.rb +53 -0
- data/lib/quiz.rb +31 -0
- data/lib/special_charater_handler.rb +8 -0
- data/script/txt2html +71 -0
- data/script/txt2html.cmd +1 -0
- data/tasks/rspec.rake +21 -0
- data/test/answer_test.rb +152 -0
- data/test/answers_test.rb +126 -0
- data/test/custom_assertions.rb +68 -0
- data/test/fixtures/begins_with_comment.txt +6 -0
- data/test/question_test.rb +86 -0
- data/test/quiz_test.rb +65 -0
- data/test/special_character_handler_test.rb +43 -0
- data/test/test_helper.rb +19 -0
- metadata +125 -0
data/lib/answers.rb
ADDED
@@ -0,0 +1,99 @@
|
|
1
|
+
require 'answer'
|
2
|
+
|
3
|
+
module Gipper
|
4
|
+
# == Gipper::Answers
|
5
|
+
#
|
6
|
+
# This class acts like an Array that contains each Answer.
|
7
|
+
#
|
8
|
+
# To popuate the object use the parse method.
|
9
|
+
#
|
10
|
+
# answers = Gipper::Answers.new
|
11
|
+
# answers.parse "~ wrong = correct # you are right!"
|
12
|
+
#
|
13
|
+
# answers.length
|
14
|
+
# >> 2
|
15
|
+
#
|
16
|
+
# answers[0].correct
|
17
|
+
# >> false
|
18
|
+
#
|
19
|
+
# answers.find_style
|
20
|
+
# >> :multiple_choice
|
21
|
+
|
22
|
+
|
23
|
+
class Answers < Array
|
24
|
+
include Oniguruma
|
25
|
+
|
26
|
+
# parses the answers an array of answer objects.
|
27
|
+
def parse answer
|
28
|
+
@answer_text = set_numerical_indicator answer
|
29
|
+
parts = split_apart @answer_text
|
30
|
+
|
31
|
+
parts.each do |clause|
|
32
|
+
answer = Answer.new
|
33
|
+
answer.parse(clause, @style_hint)
|
34
|
+
self << answer
|
35
|
+
end
|
36
|
+
end
|
37
|
+
|
38
|
+
# find_style does it's best to determine the questions style. It cannot determine
|
39
|
+
# the missing word questions since the necessary information is in the question object.
|
40
|
+
# use question.find_style for that purpose.
|
41
|
+
def find_style
|
42
|
+
return @style_hint if @style_hint
|
43
|
+
|
44
|
+
if self.length == 1 && self[0].text.nil? && boolean?(self[0].correct.class)
|
45
|
+
return :true_false
|
46
|
+
end
|
47
|
+
|
48
|
+
if self[0].text && self[0].correct.class == String
|
49
|
+
return :matching
|
50
|
+
end
|
51
|
+
|
52
|
+
if (count_true < 2) && (self.length > 1)
|
53
|
+
return :multiple_choice
|
54
|
+
else
|
55
|
+
return :short_answer
|
56
|
+
end
|
57
|
+
end
|
58
|
+
|
59
|
+
private
|
60
|
+
def boolean? klass
|
61
|
+
klass == TrueClass || klass == FalseClass
|
62
|
+
end
|
63
|
+
|
64
|
+
def count_true
|
65
|
+
true_count = 0
|
66
|
+
self.each do |hash|
|
67
|
+
if (hash.correct == true)
|
68
|
+
true_count = true_count + 1
|
69
|
+
end
|
70
|
+
end
|
71
|
+
true_count
|
72
|
+
end
|
73
|
+
|
74
|
+
def set_numerical_indicator answer
|
75
|
+
if is_numerical? answer
|
76
|
+
@style_hint = :numerical
|
77
|
+
answer[1..-1]
|
78
|
+
else
|
79
|
+
answer
|
80
|
+
end
|
81
|
+
end
|
82
|
+
|
83
|
+
def is_numerical? answer
|
84
|
+
answer[0] == 35
|
85
|
+
end
|
86
|
+
|
87
|
+
# Iterates through the answer clauses
|
88
|
+
def split_apart clauses
|
89
|
+
reg = ORegexp.new('(?<!\\\\)(?:[~=])(.(?!(?<!\\\\)[~=]))*', :options => OPTION_MULTILINE)
|
90
|
+
|
91
|
+
matches = reg.scan(clauses)
|
92
|
+
|
93
|
+
# if we didn't find any ~ or = then there is only a single answer
|
94
|
+
matches = [clauses] unless matches
|
95
|
+
|
96
|
+
matches.map { |m| m.to_s.strip}
|
97
|
+
end
|
98
|
+
end
|
99
|
+
end
|
data/lib/gipper.rb
ADDED
data/lib/question.rb
ADDED
@@ -0,0 +1,53 @@
|
|
1
|
+
require 'special_charater_handler'
|
2
|
+
|
3
|
+
module Gipper
|
4
|
+
class Question
|
5
|
+
include Oniguruma
|
6
|
+
include Gipper::SpecialCharacterHandler
|
7
|
+
|
8
|
+
attr_accessor :text_post, :answer, :title, :text, :style, :format
|
9
|
+
|
10
|
+
# instance method to parse the question
|
11
|
+
def parse text
|
12
|
+
reg = ORegexp.new('(.+)(?<!\\\\)\{(?<!\\\\)(.+)\}(.*)', :options => OPTION_MULTILINE)
|
13
|
+
matches = reg.match(text.strip).captures.map { |s| s.strip }
|
14
|
+
|
15
|
+
@text_post = unescape(matches[2]) unless matches[2].empty?
|
16
|
+
|
17
|
+
answer = matches[1]
|
18
|
+
|
19
|
+
@answer = Gipper::Answers.new
|
20
|
+
@answer.parse(answer)
|
21
|
+
|
22
|
+
@format, @title, @text = to_parts(matches[0]).map { |s| unescape s}
|
23
|
+
|
24
|
+
@style = find_style @answer
|
25
|
+
end
|
26
|
+
|
27
|
+
def find_style answers
|
28
|
+
if @text_post
|
29
|
+
return :missing_word
|
30
|
+
end
|
31
|
+
|
32
|
+
answers.find_style
|
33
|
+
end
|
34
|
+
|
35
|
+
private
|
36
|
+
def to_parts question
|
37
|
+
format_part = '(\[(?<format>(.*))\])'
|
38
|
+
title_part = '(:{2}(?<title>(.*)):{2})'
|
39
|
+
question_part = '(?<question>(.*))'
|
40
|
+
whole_regex = "^#{format_part}?\\s*#{title_part}?\\s*#{question_part}$"
|
41
|
+
|
42
|
+
reg = ORegexp.new(whole_regex, :options => OPTION_MULTILINE)
|
43
|
+
parts = reg.match(question.strip)
|
44
|
+
|
45
|
+
format = parts[:format].strip if parts[:format]
|
46
|
+
title = parts[:title].strip if parts[:title]
|
47
|
+
question = parts[:question].strip
|
48
|
+
|
49
|
+
return [format, title, question]
|
50
|
+
end
|
51
|
+
end
|
52
|
+
end
|
53
|
+
|
data/lib/quiz.rb
ADDED
@@ -0,0 +1,31 @@
|
|
1
|
+
require 'answers'
|
2
|
+
require 'question'
|
3
|
+
|
4
|
+
module Gipper
|
5
|
+
class Quiz
|
6
|
+
def self.parse gift_file
|
7
|
+
quiz = Quiz.new()
|
8
|
+
gift = []
|
9
|
+
gift_file.strip!
|
10
|
+
|
11
|
+
quiz.iterate_through gift_file do |question|
|
12
|
+
q = Gipper::Question.new
|
13
|
+
q.parse(question)
|
14
|
+
gift << q
|
15
|
+
end
|
16
|
+
|
17
|
+
gift
|
18
|
+
end
|
19
|
+
|
20
|
+
def iterate_through questions
|
21
|
+
questions.gsub!(/^\s*\/\/.*$/, "") # strip out comment lines
|
22
|
+
|
23
|
+
list = questions.split(/[\r\n][\r\n\t\s]*[\r\n]/)
|
24
|
+
list.each do |item|
|
25
|
+
if item != ''
|
26
|
+
yield item
|
27
|
+
end
|
28
|
+
end
|
29
|
+
end
|
30
|
+
end
|
31
|
+
end
|
data/script/txt2html
ADDED
@@ -0,0 +1,71 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
|
3
|
+
load File.dirname(__FILE__) + "/../Rakefile"
|
4
|
+
require 'rubyforge'
|
5
|
+
require 'redcloth'
|
6
|
+
require 'syntax/convertors/html'
|
7
|
+
require 'erb'
|
8
|
+
|
9
|
+
download = "http://rubyforge.org/projects/#{$hoe.rubyforge_name}"
|
10
|
+
version = $hoe.version
|
11
|
+
|
12
|
+
def rubyforge_project_id
|
13
|
+
RubyForge.new.configure.autoconfig["group_ids"][$hoe.rubyforge_name]
|
14
|
+
end
|
15
|
+
|
16
|
+
class Fixnum
|
17
|
+
def ordinal
|
18
|
+
# teens
|
19
|
+
return 'th' if (10..19).include?(self % 100)
|
20
|
+
# others
|
21
|
+
case self % 10
|
22
|
+
when 1 then return 'st'
|
23
|
+
when 2 then return 'nd'
|
24
|
+
when 3 then return 'rd'
|
25
|
+
else return 'th'
|
26
|
+
end
|
27
|
+
end
|
28
|
+
end
|
29
|
+
|
30
|
+
class Time
|
31
|
+
def pretty
|
32
|
+
return "#{mday}#{mday.ordinal} #{strftime('%B')} #{year}"
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
def convert_syntax(syntax, source)
|
37
|
+
return Syntax::Convertors::HTML.for_syntax(syntax).convert(source).gsub(%r!^<pre>|</pre>$!,'')
|
38
|
+
end
|
39
|
+
|
40
|
+
if ARGV.length >= 1
|
41
|
+
src, template = ARGV
|
42
|
+
template ||= File.join(File.dirname(__FILE__), '/../website/template.html.erb')
|
43
|
+
else
|
44
|
+
puts("Usage: #{File.split($0).last} source.txt [template.html.erb] > output.html")
|
45
|
+
exit!
|
46
|
+
end
|
47
|
+
|
48
|
+
template = ERB.new(File.open(template).read)
|
49
|
+
|
50
|
+
title = nil
|
51
|
+
body = nil
|
52
|
+
File.open(src) do |fsrc|
|
53
|
+
title_text = fsrc.readline
|
54
|
+
body_text_template = fsrc.read
|
55
|
+
body_text = ERB.new(body_text_template).result(binding)
|
56
|
+
syntax_items = []
|
57
|
+
body_text.gsub!(%r!<(pre|code)[^>]*?syntax=['"]([^'"]+)[^>]*>(.*?)</\1>!m){
|
58
|
+
ident = syntax_items.length
|
59
|
+
element, syntax, source = $1, $2, $3
|
60
|
+
syntax_items << "<#{element} class='syntax'>#{convert_syntax(syntax, source)}</#{element}>"
|
61
|
+
"syntax-temp-#{ident}"
|
62
|
+
}
|
63
|
+
title = RedCloth.new(title_text).to_html.gsub(%r!<.*?>!,'').strip
|
64
|
+
body = RedCloth.new(body_text).to_html
|
65
|
+
body.gsub!(%r!(?:<pre><code>)?syntax-temp-(\d+)(?:</code></pre>)?!){ syntax_items[$1.to_i] }
|
66
|
+
end
|
67
|
+
stat = File.stat(src)
|
68
|
+
created = stat.ctime
|
69
|
+
modified = stat.mtime
|
70
|
+
|
71
|
+
$stdout << template.result(binding)
|
data/script/txt2html.cmd
ADDED
@@ -0,0 +1 @@
|
|
1
|
+
@ruby script/txt2html %*
|
data/tasks/rspec.rake
ADDED
@@ -0,0 +1,21 @@
|
|
1
|
+
begin
|
2
|
+
require 'spec'
|
3
|
+
rescue LoadError
|
4
|
+
require 'rubygems'
|
5
|
+
require 'spec'
|
6
|
+
end
|
7
|
+
begin
|
8
|
+
require 'spec/rake/spectask'
|
9
|
+
rescue LoadError
|
10
|
+
puts <<-EOS
|
11
|
+
To use rspec for testing you must install rspec gem:
|
12
|
+
gem install rspec
|
13
|
+
EOS
|
14
|
+
exit(0)
|
15
|
+
end
|
16
|
+
|
17
|
+
desc "Run the specs under spec/models"
|
18
|
+
Spec::Rake::SpecTask.new do |t|
|
19
|
+
t.spec_opts = ['--options', "spec/spec.opts"]
|
20
|
+
t.spec_files = FileList['spec/**/*_spec.rb']
|
21
|
+
end
|
data/test/answer_test.rb
ADDED
@@ -0,0 +1,152 @@
|
|
1
|
+
require 'test_helper'
|
2
|
+
|
3
|
+
class AnswerTest < Test::Unit::TestCase
|
4
|
+
def setup
|
5
|
+
@answer = Gipper::Answer.new
|
6
|
+
end
|
7
|
+
|
8
|
+
should "accept a single answer without equals as the correct and only answer" do
|
9
|
+
@answer.parse("5")
|
10
|
+
assert_equal true, @answer.correct
|
11
|
+
assert_equal "5", @answer.text
|
12
|
+
end
|
13
|
+
|
14
|
+
context "determining weight" do
|
15
|
+
should "have a default weight of nil" do
|
16
|
+
@answer.parse("foo" )
|
17
|
+
assert_nil @answer.weight
|
18
|
+
end
|
19
|
+
|
20
|
+
should "parse weight from numerical and non-numerical answers" do
|
21
|
+
@answer.parse("%50%yadda yadda" )
|
22
|
+
assert_equal 50, @answer.weight
|
23
|
+
end
|
24
|
+
end
|
25
|
+
|
26
|
+
should "use decimals for numerical answers" do
|
27
|
+
assert_answer_parsing "5.6765", :correct => 5.6765, :weight => nil, :range => 0, :numerical => true
|
28
|
+
end
|
29
|
+
|
30
|
+
should "handle alternate range format for numerical answers" do
|
31
|
+
assert_answer_parsing "5.1..5.9", :correct => 5.5, :weight => nil, :range => 0.4, :numerical => true
|
32
|
+
end
|
33
|
+
|
34
|
+
should "handle alternate range format for numerical answers with multiple decimal places" do
|
35
|
+
assert_answer_parsing "5.6765..5.6766", :correct => 5.67655, :weight => nil, :range => 0.00005, :numerical => true
|
36
|
+
end
|
37
|
+
|
38
|
+
context "parsing true false questions" do
|
39
|
+
should "recognize feedback" do
|
40
|
+
assert_answer_parsing " T#foo", :correct => true, :text => nil, :comment => "foo"
|
41
|
+
end
|
42
|
+
|
43
|
+
should "be tolerant of input variance" do
|
44
|
+
assert_answer_parsing " T", :correct => true
|
45
|
+
assert_answer_parsing "TrUE ", :correct => true
|
46
|
+
assert_answer_parsing " true", :correct => true
|
47
|
+
assert_answer_parsing " t ", :correct => true
|
48
|
+
assert_answer_parsing "F ", :correct => false
|
49
|
+
assert_answer_parsing " false ", :correct => false
|
50
|
+
assert_answer_parsing " faLse", :correct => false
|
51
|
+
assert_answer_parsing " f ", :correct => false
|
52
|
+
end
|
53
|
+
end
|
54
|
+
|
55
|
+
should "identify matching questions and place the match into correct" do
|
56
|
+
assert_answer_parsing "=waffle -> cone", :correct => "cone", :text => "waffle"
|
57
|
+
end
|
58
|
+
|
59
|
+
should "be tolerant of input variance" do
|
60
|
+
assert_answer_parsing "~ %%%%%%%", :correct => false, :text => "%%%%%%%"
|
61
|
+
assert_answer_parsing "~UUUUUUUUU", :correct => false, :text => "UUUUUUUUU"
|
62
|
+
end
|
63
|
+
|
64
|
+
should "get answer conmments" do
|
65
|
+
assert_answer_parsing "~ %%%%%%%#foo", :correct => false, :text => "%%%%%%%", :comment => "foo"
|
66
|
+
end
|
67
|
+
|
68
|
+
should "get answer conmments when preceeded by a new line" do
|
69
|
+
assert_answer_parsing "~ Oompa\r\n#kun", :correct => false, :text => "Oompa", :comment => "kun"
|
70
|
+
end
|
71
|
+
|
72
|
+
# If you want to use curly braces, { or }, or equal sign, =,
|
73
|
+
# or # or ~ in a GIFT file (for example in a math question including
|
74
|
+
# TeX expressions) you must "escape" them by preceding them with a \
|
75
|
+
# directly in front of each { or } or =.
|
76
|
+
should "ignore all escaped characters" do
|
77
|
+
assert_answer_parsing "~ \\{\\}\\~\\=\\#foo", :correct => false, :text => "{}~=#foo", :comment => nil
|
78
|
+
end
|
79
|
+
|
80
|
+
context "numerical answers" do
|
81
|
+
should "understand numerical answer format" do
|
82
|
+
assert_answer_parsing "2000:3", :correct => 2000, :range => 3, :text => nil, :comment => nil, :weight => nil, :numerical => true
|
83
|
+
end
|
84
|
+
|
85
|
+
should "simple numerical answer format" do
|
86
|
+
assert_answer_parsing "=2000:0 #Whoopdee do!", :correct => 2000, :range => 0, :text => nil, :comment => "Whoopdee do!", :weight => nil, :numerical => true
|
87
|
+
end
|
88
|
+
|
89
|
+
should "percent based numerical answer format" do
|
90
|
+
assert_answer_parsing "=%50%2000:3 #Yippers", :correct => 2000, :range => 3, :text => nil, :comment => "Yippers", :weight => 50, :numerical => true
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
|
95
|
+
context "feedback comment splitting" do
|
96
|
+
should "return nils when passed empty string" do
|
97
|
+
text, comment = @answer.send :split_comment, ""
|
98
|
+
assert_nil comment
|
99
|
+
assert_nil text
|
100
|
+
end
|
101
|
+
|
102
|
+
should "return nils when passed nil" do
|
103
|
+
text, comment = @answer.send :split_comment, nil
|
104
|
+
assert_nil comment
|
105
|
+
assert_nil text
|
106
|
+
end
|
107
|
+
|
108
|
+
should "return nil comment and full text when no comment" do
|
109
|
+
text, comment = @answer.send :split_comment, "foo"
|
110
|
+
assert_nil comment
|
111
|
+
assert_equal "foo", text
|
112
|
+
end
|
113
|
+
|
114
|
+
should "return comment and text when feedback comment" do
|
115
|
+
text, comment = @answer.send :split_comment, "foo #bar"
|
116
|
+
assert_equal "foo", text
|
117
|
+
assert_equal "bar", comment
|
118
|
+
end
|
119
|
+
|
120
|
+
should "return full text when feedback comment is escaped" do
|
121
|
+
text, comment = @answer.send :split_comment, 'foo \\#bar'
|
122
|
+
assert_nil comment
|
123
|
+
assert_equal 'foo \\#bar', text
|
124
|
+
end
|
125
|
+
end
|
126
|
+
|
127
|
+
context "escape stripping" do
|
128
|
+
should "not remove escaped feedback comments" do
|
129
|
+
result = @answer.send :unescape, "foo \\#comment"
|
130
|
+
assert_equal "foo #comment", result
|
131
|
+
end
|
132
|
+
|
133
|
+
should "remove escapes from read string" do
|
134
|
+
@answer.parse('~ \\{\\}\\~\\=\\#foo', nil)
|
135
|
+
assert_equal "{}~=#foo", @answer.text
|
136
|
+
end
|
137
|
+
end
|
138
|
+
|
139
|
+
context "evaluating correctness" do
|
140
|
+
should "view equals as correct" do
|
141
|
+
result = @answer.send :split_correct, "= foo"
|
142
|
+
assert result[0]
|
143
|
+
assert_equal "foo", result[1]
|
144
|
+
end
|
145
|
+
|
146
|
+
should "view twiddle as incorrect" do
|
147
|
+
result = @answer.send :split_correct, "~ foo"
|
148
|
+
assert !result[0]
|
149
|
+
assert_equal "foo", result[1]
|
150
|
+
end
|
151
|
+
end
|
152
|
+
end
|