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.
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