gipper 0.0.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- 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
|