gift-parser 0.1.0
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 +2 -0
- data/LICENCE +23 -0
- data/README +18 -0
- data/RakeFile +39 -0
- data/VERSION +1 -0
- data/gift-parser.gemspec +57 -0
- data/github.com +0 -0
- data/lib/gift.rb +267 -0
- data/lib/gift_parser.rb +3242 -0
- data/src/gift_parser.treetop +207 -0
- data/test/GIFT-examples.rb +400 -0
- data/test/GIFT-examples.txt +27 -0
- data/test/gift_semantic_test.rb +179 -0
- data/test/gift_syntax_test.rb +243 -0
- data/test/gift_test.rb +55 -0
- metadata +79 -0
@@ -0,0 +1,27 @@
|
|
1
|
+
// true/false
|
2
|
+
::Q1:: 1+1=2 {T}
|
3
|
+
|
4
|
+
// multiple choice with specified feedback for right and wrong answers
|
5
|
+
::Q2:: What's between orange and green in the spectrum?
|
6
|
+
{ =yellow # right; good! ~red # wrong, it's yellow ~blue # wrong, it's yellow }
|
7
|
+
|
8
|
+
// fill-in-the-blank
|
9
|
+
::Q3:: Two plus {=two =2} equals four.
|
10
|
+
|
11
|
+
// matching
|
12
|
+
::Q4:: Which animal eats which food? { =cat -> cat food =dog -> dog food }
|
13
|
+
|
14
|
+
// math range question
|
15
|
+
::Q5:: What is a number from 1 to 5? {#3:2}
|
16
|
+
|
17
|
+
// math range specified with interval end points
|
18
|
+
::Q6:: What is a number from 1 to 5? {#1..5}
|
19
|
+
|
20
|
+
// multiple numeric answers with partial credit and feedback
|
21
|
+
::Q7:: When was Ulysses S. Grant born? {#
|
22
|
+
=1822:0 # Correct! Full credit.
|
23
|
+
=%50%1822:2 # He was born in 1822. Half credit for being close.
|
24
|
+
}
|
25
|
+
|
26
|
+
// essay
|
27
|
+
::Q8:: How are you? {}
|
@@ -0,0 +1,179 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Tests for GIFT parser
|
3
|
+
|
4
|
+
# This only tests that the input is parsed.
|
5
|
+
# so far does not test output.
|
6
|
+
|
7
|
+
require 'pp'
|
8
|
+
require 'test/unit'
|
9
|
+
require 'test/unit/ui/console/testrunner'
|
10
|
+
|
11
|
+
require File.expand_path('../../lib/gift', __FILE__)
|
12
|
+
|
13
|
+
class GiftSemanticTest < Test::Unit::TestCase
|
14
|
+
|
15
|
+
attr_accessor :parser
|
16
|
+
|
17
|
+
def setup
|
18
|
+
@parser = GiftParser.new
|
19
|
+
end
|
20
|
+
|
21
|
+
def assert_can_parse(string)
|
22
|
+
string << "\n\n" # Insert a blank line at end of string
|
23
|
+
assert !@parser.parse(string).nil?, "Failed on:\n#{string}\n Reason:\n#{@parser.failure_reason.inspect}\n"
|
24
|
+
end
|
25
|
+
|
26
|
+
def assert_cannot_parse(string)
|
27
|
+
string << "\n\n" # Insert a blank line at end of string
|
28
|
+
p = @parser.parse(string)
|
29
|
+
assert p.nil?, "Failed on:\n#{string}\n Reason:\nThis should not be correctly parsed, but was.\nParser Output:\n#{p.inspect}"
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_description_question
|
33
|
+
q = @parser.parse("This is a description.\n\n").questions[0]
|
34
|
+
assert q.class == Gift::DescriptionQuestion
|
35
|
+
assert q.text == "This is a description."
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_essay_question
|
39
|
+
q = @parser.parse("Write an essay on the toic of your choice{ }\n\n").questions[0]
|
40
|
+
assert q.class == Gift::EssayQuestion
|
41
|
+
assert q.text == "Write an essay on the toic of your choice"
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_true_false_question_true
|
45
|
+
q = @parser.parse("Is the sky blue?{T}\n\n").questions[0]
|
46
|
+
assert q.class == Gift::TrueFalseQuestion
|
47
|
+
assert q.text == "Is the sky blue?"
|
48
|
+
assert q.answers == [{:value => true, :correct => true, :feedback => nil}]
|
49
|
+
end
|
50
|
+
|
51
|
+
def test_true_false_question_false
|
52
|
+
q = @parser.parse("Is the sky green?{F}\n\n").questions[0]
|
53
|
+
assert q.text == "Is the sky green?"
|
54
|
+
assert q.answers == [{:value => false, :correct => true, :feedback => nil}]
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_tf_with_feedback
|
58
|
+
q = @parser.parse("Is the sky green?{F#No It's blue.}\n\n").questions[0]
|
59
|
+
assert q.text == "Is the sky green?"
|
60
|
+
assert q.answers == [{:value => false, :correct => true, :feedback => "No It's blue."}]
|
61
|
+
end
|
62
|
+
|
63
|
+
def test_multiple_choice_question
|
64
|
+
q = @parser.parse("What color is the sky?{=blue ~green ~red}\n\n").questions[0]
|
65
|
+
assert q.class == Gift::MultipleChoiceQuestion
|
66
|
+
assert q.text == "What color is the sky?"
|
67
|
+
assert q.answers == [{:value => "blue", :correct => true, :feedback => nil},
|
68
|
+
{:value => "green", :correct => false, :feedback => nil},
|
69
|
+
{:value => "red", :correct => false, :feedback => nil}]
|
70
|
+
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_multiple_choice_question_with_feedback
|
74
|
+
q = @parser.parse("What color is the sky?{=blue#Correct ~green#Wrong! ~red}\n\n").questions[0]
|
75
|
+
assert q.answers == [{:value => "blue", :correct => true, :feedback => "Correct"},
|
76
|
+
{:value => "green", :correct => false, :feedback => "Wrong!"},
|
77
|
+
{:value => "red", :correct => false, :feedback => nil}]
|
78
|
+
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_multiple_choice_question_with_weights
|
82
|
+
q = @parser.parse("What color is the sky?{=blue#Correct ~%50%green#Wrong! ~red}\n\n").questions[0]
|
83
|
+
assert q.answers == [{:value => "blue", :correct => true, :feedback => "Correct"},
|
84
|
+
{:value => "green", :correct => false, :feedback => "Wrong!", :weight => 50.0},
|
85
|
+
{:value => "red", :correct => false, :feedback => nil}]
|
86
|
+
end
|
87
|
+
|
88
|
+
def test_short_answer_question
|
89
|
+
q = @parser.parse("Who's buried in Grant's tomb?{=Grant =Ulysses S. Grant =Ulysses Grant}\n\n").questions[0]
|
90
|
+
assert q.class == Gift::ShortAnswerQuestion
|
91
|
+
assert q.text == "Who's buried in Grant's tomb?"
|
92
|
+
assert q.answers == [{:feedback => nil, :value => "Grant", :correct => true},
|
93
|
+
{:feedback => nil, :value => "Ulysses S. Grant", :correct => true},
|
94
|
+
{:feedback => nil, :value => "Ulysses Grant" , :correct => true}]
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_short_answer_question_with_weights
|
98
|
+
q = @parser.parse("Name three colors of the rainbow?{=%33.3%red =%33.3%orange =%33.3%yellow =%33.3%green =%33.3%blue =%33.3%indigo =%33.3%violet }\n\n").questions[0]
|
99
|
+
q.answers.each do |a|
|
100
|
+
assert a[:weight] == 33.3
|
101
|
+
end
|
102
|
+
end
|
103
|
+
|
104
|
+
def test_numeric_question
|
105
|
+
q = @parser.parse("What is 3 + 4?{#7}\n\n").questions[0]
|
106
|
+
assert q.class == Gift::NumericQuestion
|
107
|
+
assert q.answers == [{:maximum => 7.0, :minimum => 7.0}]
|
108
|
+
end
|
109
|
+
|
110
|
+
def test_numeric_with_tolerance
|
111
|
+
q = @parser.parse("What is the value of PI?{#3.1:0.5}\n\n").questions[0]
|
112
|
+
assert q.answers == [{:maximum => 3.6, :minimum => 2.6}]
|
113
|
+
end
|
114
|
+
|
115
|
+
def test_numeric_range
|
116
|
+
q = @parser.parse("What is a number from 1 to 5? {#1..5}\n\n").questions[0]
|
117
|
+
assert q.answers == [{:maximum => 1.0, :minimum => 5.0}]
|
118
|
+
end
|
119
|
+
|
120
|
+
def test_numeric_multiple_answers
|
121
|
+
q = @parser.parse("What is the value of PI?{#3.1415 =%50%3.1 =%25%3 }\n\n").questions[0]
|
122
|
+
assert q.answers == [{:maximum => 3.1415, :minimum => 3.1415},
|
123
|
+
{:maximum => 3.1, :minimum => 3.1},
|
124
|
+
{:maximum => 3.0, :minimum => 3.0}]
|
125
|
+
end
|
126
|
+
|
127
|
+
def test_numeric_neagitve_numbers
|
128
|
+
q = @parser.parse("Calculate 2 - 6.{#-4.0}\n\n").questions[0]
|
129
|
+
assert q.answers == [{:maximum => -4.0, :minimum => -4.0}]
|
130
|
+
end
|
131
|
+
|
132
|
+
def test_matching_question
|
133
|
+
q = @parser.parse("Match the names. { =Charlie -> Chaplin =Groucho -> Marx =Buster -> Keaton =Stan -> Laurel }\n\n").questions[0]
|
134
|
+
assert q.class == Gift::MatchQuestion
|
135
|
+
assert q.text == "Match the names."
|
136
|
+
assert q.answers == {'Charlie' => 'Chaplin', 'Groucho' => 'Marx', 'Buster' => 'Keaton', 'Stan' => 'Laurel'}
|
137
|
+
end
|
138
|
+
|
139
|
+
def test_fill_in_question
|
140
|
+
q = @parser.parse("There were {=three} little pigs.\n\n").questions[0]
|
141
|
+
assert q.class == Gift::FillInQuestion
|
142
|
+
assert q.text == "There were %% little pigs."
|
143
|
+
assert q.answers == [{:value => 'three', :correct => true, :feedback => nil}]
|
144
|
+
end
|
145
|
+
|
146
|
+
def test_title
|
147
|
+
q = @parser.parse("::Essay One:: Write an essay on the toic of your choice{ }\n\n").questions[0]
|
148
|
+
assert q.title == "Essay One"
|
149
|
+
end
|
150
|
+
|
151
|
+
def test_title_from_question
|
152
|
+
q = @parser.parse("Write an essay on the toic of your choice{ }\n\n").questions[0]
|
153
|
+
assert q.title == q.text
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_comment
|
157
|
+
q = @parser.parse("//Here's an easy one\nWrite an essay on the toic of your choice{ }\n\n").questions[0]
|
158
|
+
assert q.comment == "Here's an easy one"
|
159
|
+
end
|
160
|
+
|
161
|
+
def test_command
|
162
|
+
q = @parser.parse("$COMMAND=1\n\nQuestion{}\n\n")
|
163
|
+
assert q.commands == ["COMMAND=1"]
|
164
|
+
end
|
165
|
+
|
166
|
+
def test_category_setting
|
167
|
+
q = @parser.parse("$CATEGORY: food \n\nIs apple a food?{T}\n\n$CATEGORY: drink \n\nIs water drinkable?{T}\n\n" )
|
168
|
+
assert q.questions[0].category == "food"
|
169
|
+
assert q.questions[1].category == "drink"
|
170
|
+
end
|
171
|
+
|
172
|
+
def test_markup_type
|
173
|
+
q = @parser.parse("[textile] This *essay* is marked up in textile.{}\n\n").questions[0]
|
174
|
+
assert q.markup == "textile"
|
175
|
+
end
|
176
|
+
|
177
|
+
end
|
178
|
+
|
179
|
+
Test::Unit::UI::Console::TestRunner.run(GiftSemanticTest)
|
@@ -0,0 +1,243 @@
|
|
1
|
+
#!/usr/bin/env ruby
|
2
|
+
# Tests for GIFT parser
|
3
|
+
|
4
|
+
# This only tests that the input is parsed.
|
5
|
+
# so far does not test output.
|
6
|
+
|
7
|
+
require 'pp'
|
8
|
+
require 'test/unit'
|
9
|
+
require 'test/unit/ui/console/testrunner'
|
10
|
+
|
11
|
+
require File.expand_path('../../lib/gift', __FILE__)
|
12
|
+
require File.expand_path('../GIFT-examples.rb', __FILE__)
|
13
|
+
|
14
|
+
|
15
|
+
class GiftSyntaxTest < Test::Unit::TestCase
|
16
|
+
|
17
|
+
attr_accessor :parser
|
18
|
+
|
19
|
+
def setup
|
20
|
+
@parser = GiftParser.new
|
21
|
+
end
|
22
|
+
|
23
|
+
def assert_can_parse(string)
|
24
|
+
string << "\n\n" # Insert a blank line at end of string
|
25
|
+
assert !@parser.parse(string).nil?, "Failed on:\n#{string}\n Reason:\n#{@parser.failure_reason.inspect}\n"
|
26
|
+
end
|
27
|
+
|
28
|
+
def assert_cannot_parse(string)
|
29
|
+
string << "\n\n"
|
30
|
+
p = @parser.parse(string)
|
31
|
+
assert p.nil?, "Failed on:\n#{string}\n Reason:\nThis should not be correctly parsed, but was.\nParser Output:\n#{p.inspect}"
|
32
|
+
end
|
33
|
+
|
34
|
+
def test_essay_question
|
35
|
+
assert_can_parse("Write an essay about something.{}")
|
36
|
+
end
|
37
|
+
|
38
|
+
def test_description_question
|
39
|
+
assert_can_parse("This is simply a description")
|
40
|
+
end
|
41
|
+
|
42
|
+
def test_true_false_questions
|
43
|
+
assert_can_parse("The sky is blue.{T}")
|
44
|
+
assert_can_parse("The sky is blue.{TRUE}")
|
45
|
+
assert_can_parse("The sky is green.{F}")
|
46
|
+
assert_can_parse("The sky is blue.{FALSE}")
|
47
|
+
end
|
48
|
+
|
49
|
+
def test_feedback
|
50
|
+
assert_can_parse("Grant is buried in Grant's tomb.{FALSE#No one is buried in Grant's tomb.}")
|
51
|
+
end
|
52
|
+
|
53
|
+
def test_multiple_choice_question
|
54
|
+
assert_can_parse("What color is the sky?{ = Blue ~Green ~Red }")
|
55
|
+
end
|
56
|
+
|
57
|
+
def test_multiple_choice_question_with_feedback
|
58
|
+
assert_can_parse('What color is the sky?{ = Blue#Right ~Green ~Red#Very wrong}')
|
59
|
+
end
|
60
|
+
|
61
|
+
def test_multiple_choice_on_multiple_lines
|
62
|
+
test_text= <<EOS
|
63
|
+
What color is the sky?{
|
64
|
+
= Blue#Right
|
65
|
+
~Green
|
66
|
+
~Red
|
67
|
+
#Very wrong
|
68
|
+
}
|
69
|
+
EOS
|
70
|
+
assert_can_parse(test_text)
|
71
|
+
end
|
72
|
+
|
73
|
+
def test_multiple_choice_with_weights
|
74
|
+
assert_can_parse('Which of these are primary colors?{ ~%33%Blue ~%33%Yellow ~Beige ~%33%Red}')
|
75
|
+
end
|
76
|
+
|
77
|
+
def test_numeric_question
|
78
|
+
assert_can_parse("How many pounds in a kilogram?{#2.2:0.1}")
|
79
|
+
end
|
80
|
+
|
81
|
+
def test_numeric_question_with_multiple_answer
|
82
|
+
assert_can_parse("How many pounds in a kilogram?{# =2.2:0.1 =%50%2 }")
|
83
|
+
end
|
84
|
+
|
85
|
+
def test_numeric_range_question
|
86
|
+
assert_can_parse("::Q5:: What is a number from 1 to 5? {#1..5}")
|
87
|
+
end
|
88
|
+
|
89
|
+
def test_numeric_negative_number
|
90
|
+
assert_can_parse("What is 6 - 10?{#-4}")
|
91
|
+
end
|
92
|
+
|
93
|
+
def test_short_answer
|
94
|
+
assert_can_parse("Who's buried in Grant's tomb?{=Grant =Ulysses S. Grant =Ulysses Grant}")
|
95
|
+
end
|
96
|
+
|
97
|
+
def test_matching_question
|
98
|
+
test_text= <<EOS
|
99
|
+
Match the following countries with their corresponding capitals. {
|
100
|
+
=Canada -> Ottawa
|
101
|
+
=Italy -> Rome
|
102
|
+
=Japan -> Tokyo
|
103
|
+
=India -> New Delhi
|
104
|
+
}
|
105
|
+
EOS
|
106
|
+
assert_can_parse(test_text)
|
107
|
+
end
|
108
|
+
|
109
|
+
def test_fill_in_question
|
110
|
+
assert_can_parse("Little {~blue =red ~green } riding hood.\n")
|
111
|
+
assert_can_parse("Two plus two equals {=four =4}.\n")
|
112
|
+
end
|
113
|
+
|
114
|
+
def test_question_with_title
|
115
|
+
assert_can_parse('::Colors 1:: Which of these are primary colors?{ ~%33%Blue ~%33%Yellow ~Beige ~%33%Red}')
|
116
|
+
end
|
117
|
+
|
118
|
+
def test_question_with_comment
|
119
|
+
assert_can_parse("//This is an easy one\n Which of these are primary colors?{ ~%33%Blue ~%33%Yellow ~Beige ~%33%Red}")
|
120
|
+
end
|
121
|
+
|
122
|
+
def test_multiline_comments
|
123
|
+
assert_can_parse("//This is an easy one\n//With more than one line of comment\nWhich of these are primary colors?{ ~%33%Blue ~%33%Yellow ~Beige ~%33%Red}")
|
124
|
+
end
|
125
|
+
|
126
|
+
def test_that_questions_must_be_separated
|
127
|
+
test_text = <<EOS
|
128
|
+
Who's buried in Grant's tomb?{=Grant =Ulysses S. Grant =Ulysses Grant}
|
129
|
+
Which of these are primary colors?{ ~%33%Blue ~%33%Yellow ~Beige ~%33%Red}
|
130
|
+
EOS
|
131
|
+
assert_cannot_parse(test_text)
|
132
|
+
|
133
|
+
test_text = <<EOS
|
134
|
+
Who's buried in Grant's tomb?{=Grant =Ulysses S. Grant =Ulysses Grant}
|
135
|
+
|
136
|
+
Which of these are primary colors?{ ~%33%Blue ~%33%Yellow ~Beige ~%33%Red}
|
137
|
+
EOS
|
138
|
+
assert_can_parse(test_text)
|
139
|
+
end
|
140
|
+
|
141
|
+
def test_escape_left_bracket
|
142
|
+
assert_can_parse('Can a \{ be escaped?{=Yes ~No}')
|
143
|
+
assert_can_parse('Can a \{ be escaped?{=Yes ~No\{ }')
|
144
|
+
|
145
|
+
end
|
146
|
+
|
147
|
+
def test_escape_right_bracket
|
148
|
+
assert_can_parse('Can a \} be escaped?{=Yes \} can be escaped. ~No}')
|
149
|
+
|
150
|
+
end
|
151
|
+
|
152
|
+
def test_escape_tilde
|
153
|
+
assert_can_parse('Can a \~ be escaped?{=Yes ~No \~ can\'t}')
|
154
|
+
end
|
155
|
+
|
156
|
+
def test_escape_hash
|
157
|
+
assert_can_parse('Can a \# be escaped?{=Yes \# can ~No}')
|
158
|
+
end
|
159
|
+
|
160
|
+
def test_escape_equals
|
161
|
+
assert_can_parse('Can a \= be escaped?{=Yes ~No}')
|
162
|
+
end
|
163
|
+
|
164
|
+
def test_escape_colon
|
165
|
+
assert_can_parse('Can a \: be escaped?{=Yes \: can be escaped. ~No}')
|
166
|
+
end
|
167
|
+
|
168
|
+
def test_escapes_in_title
|
169
|
+
assert_can_parse('::\{This is in\: Brackets\}::Who\'s buried in Grant\'s tomb?{=Grant =Ulysses S. Grant =Ulysses Grant}')
|
170
|
+
end
|
171
|
+
|
172
|
+
def test_escapes_in_comment
|
173
|
+
assert_can_parse("//Escapes in comments are redundant \\: since they end in a \n Question?{}")
|
174
|
+
end
|
175
|
+
|
176
|
+
def test_crlf_support
|
177
|
+
assert_can_parse("Can we have DOS style line breaks?{\r\n=yes \r\n~no}\r\n \r\n And is it seen as a line_break?{TRUE}")
|
178
|
+
end
|
179
|
+
|
180
|
+
def test_comment_line_break
|
181
|
+
assert_can_parse("//Comment\nWho's buried in Grant's tomb?{=Grant =Ulysses S. Grant =Ulysses Grant}")
|
182
|
+
assert_can_parse("//Comment\r\nWho's buried in Grant's tomb?{=Grant =Ulysses S. Grant =Ulysses Grant}")
|
183
|
+
end
|
184
|
+
|
185
|
+
def test_dealing_with_blank_lines_at_top_of_file
|
186
|
+
assert_can_parse("\n \n//Comment\nWho's buried in Grant's tomb?{=Grant =Ulysses S. Grant =Ulysses Grant}")
|
187
|
+
end
|
188
|
+
|
189
|
+
def test_dealing_with_blank_lines_at_end_of_file
|
190
|
+
assert_can_parse("//Comment\nWho's buried in Grant's tomb?{=Grant =Ulysses S. Grant =Ulysses Grant}\n \n \n ")
|
191
|
+
end
|
192
|
+
|
193
|
+
def test_title_line_breaks
|
194
|
+
test_text= <<EOS
|
195
|
+
::Title::
|
196
|
+
Match the following countries with their corresponding capitals. {
|
197
|
+
=Canada -> Ottawa
|
198
|
+
=Italy -> Rome
|
199
|
+
=Japan -> Tokyo
|
200
|
+
=India -> New Delhi
|
201
|
+
}
|
202
|
+
|
203
|
+
EOS
|
204
|
+
assert_can_parse(test_text)
|
205
|
+
end
|
206
|
+
|
207
|
+
|
208
|
+
def test_single_short_answer
|
209
|
+
test_text = <<EOS
|
210
|
+
// ===Short Answer===
|
211
|
+
What is your favorite color?{=blue}
|
212
|
+
EOS
|
213
|
+
assert_can_parse(test_text)
|
214
|
+
end
|
215
|
+
|
216
|
+
def test_multiple_short_answer
|
217
|
+
test_text = <<EOS
|
218
|
+
::Multiple Short Answer::
|
219
|
+
What are your four favorite colors?{
|
220
|
+
=%25%Blue
|
221
|
+
=%25% Red
|
222
|
+
=%25% Green
|
223
|
+
=%25% pink
|
224
|
+
=%25% azure
|
225
|
+
=%25% gold
|
226
|
+
}
|
227
|
+
EOS
|
228
|
+
assert_can_parse(test_text)
|
229
|
+
end
|
230
|
+
|
231
|
+
def test_moodle_examples
|
232
|
+
GiftExamples.examples.each do |key, value|
|
233
|
+
assert_can_parse value
|
234
|
+
end
|
235
|
+
end
|
236
|
+
|
237
|
+
def test_can_have_commands
|
238
|
+
assert_can_parse("$COMMAND=1\n\nQuestion text{}")
|
239
|
+
end
|
240
|
+
end
|
241
|
+
|
242
|
+
Test::Unit::UI::Console::TestRunner.run(GiftSyntaxTest)
|
243
|
+
|