gift-parser 0.1.0
Sign up to get free protection for your applications and to get access to all the features.
- 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
|
+
|