gipper 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
Files changed (47) hide show
  1. data/.gitignore +7 -0
  2. data/PostInstall.txt +5 -0
  3. data/README.rdoc +49 -0
  4. data/Rakefile +69 -0
  5. data/VERSION.yml +4 -0
  6. data/docs/gift_reference.pdf +0 -0
  7. data/features/fixtures/matching.gift +9 -0
  8. data/features/fixtures/missing_word.gift +8 -0
  9. data/features/fixtures/multiple_choice.gift +41 -0
  10. data/features/fixtures/numerical.gift +10 -0
  11. data/features/fixtures/short_answer.gift +10 -0
  12. data/features/fixtures/super_gift.txt +169 -0
  13. data/features/fixtures/true_or_false.gift +19 -0
  14. data/features/gift_parsing.feature +10 -0
  15. data/features/parse_matching_questions.feature +10 -0
  16. data/features/parse_missing_word.feature +10 -0
  17. data/features/parse_multiple_choice_questions.feature +10 -0
  18. data/features/parse_numerical_questions.feature +10 -0
  19. data/features/parse_short_answer_questions.feature +10 -0
  20. data/features/parse_true_or_false_questions.feature +10 -0
  21. data/features/steps/gift_parsing_steps.rb +546 -0
  22. data/features/steps/parse_matching_steps.rb +30 -0
  23. data/features/steps/parse_missing_word_steps.rb +58 -0
  24. data/features/steps/parse_multiple_choice_steps.rb +107 -0
  25. data/features/steps/parse_numerical_steps.rb +35 -0
  26. data/features/steps/parse_short_answer_steps.rb +42 -0
  27. data/features/steps/parse_steps.rb +7 -0
  28. data/features/steps/parse_true_or_false_steps.rb +61 -0
  29. data/features/support/env.rb +10 -0
  30. data/lib/answer.rb +165 -0
  31. data/lib/answers.rb +99 -0
  32. data/lib/gipper.rb +15 -0
  33. data/lib/question.rb +53 -0
  34. data/lib/quiz.rb +31 -0
  35. data/lib/special_charater_handler.rb +8 -0
  36. data/script/txt2html +71 -0
  37. data/script/txt2html.cmd +1 -0
  38. data/tasks/rspec.rake +21 -0
  39. data/test/answer_test.rb +152 -0
  40. data/test/answers_test.rb +126 -0
  41. data/test/custom_assertions.rb +68 -0
  42. data/test/fixtures/begins_with_comment.txt +6 -0
  43. data/test/question_test.rb +86 -0
  44. data/test/quiz_test.rb +65 -0
  45. data/test/special_character_handler_test.rb +43 -0
  46. data/test/test_helper.rb +19 -0
  47. metadata +125 -0
@@ -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
@@ -0,0 +1,15 @@
1
+ begin
2
+ require 'oniguruma'
3
+ rescue LoadError
4
+ require 'rubygems'
5
+ require 'oniguruma'
6
+ end
7
+
8
+ require 'answer'
9
+ require 'answers'
10
+ require 'question'
11
+ require 'quiz'
12
+
13
+ module Gipper
14
+
15
+ end
@@ -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
+
@@ -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
@@ -0,0 +1,8 @@
1
+ module Gipper
2
+ module SpecialCharacterHandler
3
+ protected
4
+ def unescape text
5
+ text.gsub(/\\(~|=|#|\{|\})/, '\1') if !text.nil?
6
+ end
7
+ end
8
+ end
@@ -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)
@@ -0,0 +1 @@
1
+ @ruby script/txt2html %*
@@ -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
@@ -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