Pickaxe 0.5.1 → 0.5.2

Sign up to get free protection for your applications and to get access to all the features.
@@ -5,19 +5,15 @@ require "active_support/all"
5
5
  $LOAD_PATH.unshift(File.expand_path(File.dirname(__FILE__)))
6
6
 
7
7
  module Pickaxe
8
- VERSION = "0.5.1"
8
+ VERSION = "0.5.2"
9
9
 
10
- class PickaxeError < StandardError
11
- attr_reader :status_code
12
- def self.status_code(code = nil)
13
- define_method(:status_code) { code }
14
- end
15
- end
10
+ class PickaxeError < StandardError; end
16
11
 
17
12
  autoload :Shell, 'pickaxe/shell'
18
13
  autoload :Color, 'pickaxe/color'
19
14
  autoload :Main, 'pickaxe/main'
20
15
  autoload :Test, 'pickaxe/test'
16
+ autoload :Errors, 'pickaxe/errors'
21
17
  end
22
18
 
23
19
  require 'pickaxe/extensions'
@@ -1,7 +1,7 @@
1
1
  require 'optparse'
2
2
 
3
3
  options = { :extension => "txt" }
4
- OptionParser.new do |opts|
4
+ parser = OptionParser.new do |opts|
5
5
  opts.banner = <<END_OF_BANNER
6
6
  Usage:
7
7
  #{$0.split("/").last} path [, path ...]
@@ -19,6 +19,10 @@ END_OF_BANNER
19
19
  options[:sorted] = true
20
20
  end
21
21
 
22
+ opts.on("--sorted-answers", "Do not shuffle answers") do |v|
23
+ options[:sorted_answers] = true
24
+ end
25
+
22
26
  opts.on("--select [NUMBER]", "Select certain number of questions") do |v|
23
27
  options[:select] = Integer(v)
24
28
  end
@@ -35,6 +39,10 @@ END_OF_BANNER
35
39
  options[:syntax_check] = true
36
40
  end
37
41
 
42
+ opts.on("--no-color", "Turn off colors") do |v|
43
+ options[:no_colors] = true
44
+ end
45
+
38
46
  opts.on_tail("--version", "Show version") do
39
47
  puts "pickaxe version #{Pickaxe::VERSION}"
40
48
  exit
@@ -44,11 +52,11 @@ END_OF_BANNER
44
52
  puts opts
45
53
  exit
46
54
  end
47
- end.parse!
55
+ end
48
56
 
49
57
  begin
58
+ parser.parse!
50
59
  Pickaxe::Main.new(ARGV, options)
51
- rescue Pickaxe::PickaxeError => e
52
- $stderr.puts(("! " + e.message).color(:red))
53
- exit(e.status_code)
60
+ rescue Pickaxe::PickaxeError, OptionParser::InvalidOption => e
61
+ $stderr.puts(("! " + e.to_s).color(:red))
54
62
  end
@@ -47,9 +47,13 @@ module Pickaxe
47
47
  # of the returned String.
48
48
  #
49
49
  def self.set_color(string, color, bold=false)
50
- color = self.const_get(color.to_s.upcase) if color.is_a?(Symbol)
51
- bold = bold ? BOLD : ""
52
- "#{bold}#{color}#{string}#{CLEAR}"
50
+ unless (Main.options || {})[:no_colors]
51
+ color = self.const_get(color.to_s.upcase) if color.is_a?(Symbol)
52
+ bold = bold ? BOLD : ""
53
+ "#{bold}#{color}#{string}#{CLEAR}"
54
+ else
55
+ "#{CLEAR}#{string}"
56
+ end
53
57
  end
54
58
  end
55
59
  end
@@ -0,0 +1,60 @@
1
+ module Pickaxe
2
+ module Errors
3
+ class PathError < PickaxeError
4
+ def initialize(file_or_directory)
5
+ super("file or directory '#{file_or_directory}' does not exist")
6
+ end
7
+ end
8
+
9
+ class TestSyntaxError < PickaxeError
10
+ def initialize(line, message)
11
+ super(" line #{line}: #{message}")
12
+ end
13
+ end
14
+
15
+ class MissingContent < TestSyntaxError
16
+ def initialize(line)
17
+ super(line, "no content (check blank lines nearby)")
18
+ end
19
+ end
20
+
21
+ class MissingAnswers < TestSyntaxError
22
+ def initialize(question)
23
+ super(question.index,
24
+ BadQuestion.message(question, "has no answers"))
25
+ end
26
+ end
27
+
28
+ class BadAnswer < TestSyntaxError
29
+ def initialize(line)
30
+ super(line.index,
31
+ "'#{line.truncate(20)}' starts with weird characters")
32
+ end
33
+ end
34
+
35
+ class BadQuestion < TestSyntaxError
36
+ def self.message(question, m)
37
+ "question '#{question.truncate(20)}' #{m}"
38
+ end
39
+
40
+ def initialize(question)
41
+ super(question.index,
42
+ "'#{question.truncate(20)}' does not look like question")
43
+ end
44
+ end
45
+
46
+ class NoCorrectAnswer < TestSyntaxError
47
+ def initialize(question)
48
+ super(question.content.first.index,
49
+ BadQuestion.message(question.content.first, "has no correct answers"))
50
+ end
51
+ end
52
+
53
+ class NotUniqueAnswerIndices < TestSyntaxError
54
+ def initialize(question)
55
+ super(question.content.first.index,
56
+ BadQuestion.message(question.content.first, "answer indices are not unique"))
57
+ end
58
+ end
59
+ end
60
+ end
@@ -4,15 +4,20 @@ class String
4
4
  unless args.blank?
5
5
  options[:line_width] = args[0] || Pickaxe::Shell.dynamic_width || 80
6
6
  end
7
- options.reverse_merge!(:line_width => Pickaxe::Shell.dynamic_width || 80)
7
+ options.reverse_merge!({
8
+ :line_width => (Pickaxe::Shell.dynamic_width || 80),
9
+ :indent => 0
10
+ })
11
+
12
+ options[:line_width] -= options[:indent]
8
13
 
9
14
  self.split("\n").collect do |line|
10
15
  if line.length > options[:line_width]
11
- line.gsub(/(.{1,#{options[:line_width]}})(\s+|$)/, "\\1\n").strip
16
+ line.gsub(/(.{1,#{options[:line_width]}})(\s+|$)/, "\\1\n#{" " * options[:indent]}").strip
12
17
  else
13
- line
18
+ line.strip
14
19
  end
15
- end * "\n"
20
+ end * "\n#{" " * options[:indent]}"
16
21
  end
17
22
 
18
23
  def color(name, bold=false)
@@ -1,6 +1,6 @@
1
1
  module Pickaxe
2
2
  class Main
3
- class NoTests < PickaxeError; status_code(1) ; end
3
+ class NoTests < PickaxeError; end
4
4
 
5
5
  cattr_accessor :options
6
6
 
@@ -19,9 +19,7 @@ END_OF_TEST
19
19
  Main.options = options
20
20
  @test = Test.new(*paths)
21
21
  return if options[:syntax_check]
22
-
23
- puts "! Hit Control-D to end test.\n\n"
24
-
22
+
25
23
  @logger = Logger.new(File.open('answers.log',
26
24
  File::WRONLY|File::TRUNC|File::CREAT))
27
25
  @logger.formatter = lambda { |s, t, p, msg| msg.to_s + "\n" }
@@ -31,7 +29,13 @@ END_OF_TEST
31
29
  @answers = Hash.new([])
32
30
  @started_at = Time.now
33
31
  @current_index = 0
32
+
33
+ if @questions_length == 0
34
+ $stderr.puts "! No questions in test!".color(:red)
35
+ return
36
+ end
34
37
 
38
+ puts "! Hit Control-D to end test.\n\n".color(:green)
35
39
  info = Main.options[:full_test] ? 1 : 0
36
40
  while @current_index < @questions.length + info do
37
41
  @question = @questions[@current_index]
@@ -1,55 +1,4 @@
1
1
  module Pickaxe
2
-
3
- class PathError < PickaxeError
4
- def initialize(file_or_directory)
5
- super("file or directory '#{file_or_directory}' does not exist")
6
- end
7
- end
8
-
9
- class TestSyntaxError < PickaxeError
10
- def initialize(file, line, message)
11
- super("#{file}: line #{line}: #{message}")
12
- end
13
- end
14
-
15
- class MissingContent < TestSyntaxError
16
- def initialize(file, line)
17
- super(file, line, "no content (check blank lines nearby)")
18
- end
19
- end
20
-
21
- class MissingAnswers < TestSyntaxError
22
- def initialize(file, question)
23
- super(file, question.index,
24
- BadQuestion.message(question, "has no answers"))
25
- end
26
- end
27
-
28
- class BadAnswer < TestSyntaxError
29
- def initialize(file, line)
30
- super(file, line.index,
31
- "'#{line.truncate(20)}' starts with weird characters")
32
- end
33
- end
34
-
35
- class BadQuestion < TestSyntaxError
36
- def self.message(question, m)
37
- "question '#{question.truncate(20)}' #{m}"
38
- end
39
-
40
- def initialize(file, question)
41
- super(file, question.index,
42
- "'#{question.truncate(20)}' does not look like question")
43
- end
44
- end
45
-
46
- class NoCorrectAnswer < TestSyntaxError
47
- def initialize(file, question)
48
- super(file, question.content.first.index,
49
- BadQuestion.message(question.content.first, "has no correct answers"))
50
- end
51
- end
52
-
53
2
  class TestLine < String
54
3
  attr_accessor :index
55
4
  def initialize(itself, index)
@@ -58,20 +7,9 @@ module Pickaxe
58
7
  end
59
8
  end
60
9
 
61
- #
62
- # Test is a file in which questions are separated by a blank line.
63
- # Each question has content (lines until answer), and answers.
64
- # Answers are listed one per line which starts with optional >> (means answer
65
- # is correct), followed by index in parenthesis (index) and followed by text.
66
- #
67
- # Example:
68
- #
69
- # 1. To be or not to be?
70
- # (a) To be.
71
- # (b) Not to be.
72
- # >> (c) I do not know.
73
- #
74
10
  class Test
11
+ include Pickaxe::Errors
12
+
75
13
  attr_reader :questions
76
14
 
77
15
  include Enumerable
@@ -93,7 +31,7 @@ module Pickaxe
93
31
  end.flatten.collect { |f| f.squeeze('/') }
94
32
 
95
33
  @questions = []
96
- @files.each do |file|
34
+ @files.inject(nil) do |last, file|
97
35
  File.open(file) do |f|
98
36
  lines = f.readlines.collect(&:strip).each_with_index.collect do |l, i|
99
37
  TestLine.new(l, i)
@@ -107,15 +45,20 @@ module Pickaxe
107
45
  if Main.options[:strict]
108
46
  raise e
109
47
  else
110
- $stderr.puts(e.message.color(:red))
48
+ if last != file
49
+ $stderr.puts unless last.nil?
50
+ $stderr.puts("#{file}:".color(:red))
51
+ last = file
52
+ end
53
+ $stderr.puts(e.message.color(:red).word_wrap(:indent => 2))
111
54
  end
112
55
  end
113
56
  end
57
+ file
114
58
  end
115
59
  end
116
60
  end
117
61
 
118
- # Yields questions randomly
119
62
  def each(&block)
120
63
  shuffled_questions.each(&block)
121
64
  end
@@ -155,6 +98,8 @@ module Pickaxe
155
98
  end
156
99
 
157
100
  class Question < Struct.new(:file, :index, :content, :answers)
101
+ include Pickaxe::Errors
102
+
158
103
  RE = /^\s*(\d+)\.?\s*(.+)$/u
159
104
 
160
105
  def self.parse(file, answers)
@@ -163,31 +108,46 @@ module Pickaxe
163
108
  content << answers.shift
164
109
  end
165
110
 
166
- raise MissingAnswers.new(file, answers.first.index) if content.blank?
167
- unless m = RE.match(content.first)
168
- raise BadQuestion.new(file, content.first)
169
- end
170
- raise MissingAnswers.new(file, content.first) if answers.blank?
111
+ raise MissingAnswers.new(file, answers.first.index) if content.blank?
112
+ raise BadQuestion.new(content.first) unless m = RE.match(content.first)
113
+ raise MissingAnswers.new(content.first) if answers.blank?
171
114
 
172
115
  answers = answers.inject([]) do |joined, line|
173
116
  if Answer::RE.match(line)
174
117
  joined << [line]
175
118
  else
176
- raise BadAnswer.new(file, line)unless Answer::LINE_RE.match(line)
119
+ raise BadAnswer.new(line) unless Answer::LINE_RE.match(line)
177
120
  joined.last << line
178
121
  end
179
122
  joined
180
123
  end
181
124
 
182
- answers = answers.collect {|answer| Answer.parse(file, answer) }
125
+ answers = answers.collect {|answer| Answer.parse(answer) }
183
126
  Question.new(file, m[1], content, answers).tap do |q|
184
- raise NoCorrectAnswer.new(file, q) if q.correct_answers.blank?
127
+ raise NoCorrectAnswer.new(q) if q.correct_answers.blank?
128
+ raise NotUniqueAnswerIndices.new(q) unless q.answer_indices.uniq!.nil?
129
+ q.content = q.content.join(" ").gsub("\\n", "\n")
185
130
  end
186
131
  end
187
132
 
133
+ def shuffled_answers
134
+ if @shuffled_answers.nil?
135
+ unless Main.options[:sorted_answers]
136
+ @shuffled_answers = self.answers.shuffle
137
+ answer_indices.sort.each_with_index do |index, order|
138
+ @shuffled_answers[order].index = index
139
+ end
140
+ else
141
+ @shuffled_answers = self.answers
142
+ end
143
+ end
144
+
145
+ @shuffled_answers
146
+ end
147
+
188
148
  def answered(indices)
189
- content = self.content.collect(&:word_wrap).join("\n")
190
- "#{content}\n\n" + self.answers.collect do |answer|
149
+ content = self.content.word_wrap(:indent => index.to_s.length+2)
150
+ content + "\n\n" + self.shuffled_answers.collect do |answer|
191
151
  selected = indices.include?(answer.index)
192
152
  line = (selected ? ">> " : " ") + answer.to_s
193
153
 
@@ -212,6 +172,10 @@ module Pickaxe
212
172
  answers.select(&:correctness).collect(&:index).sort
213
173
  end
214
174
 
175
+ def answer_indices
176
+ shuffled_answers.collect(&:index)
177
+ end
178
+
215
179
  def check?(given)
216
180
  if correct?(given)
217
181
  "Correct!".color(:green)
@@ -223,16 +187,16 @@ module Pickaxe
223
187
 
224
188
  class Answer < Struct.new(:content, :index, :correctness)
225
189
  RE = /^\s*(>+)?\s*(\?+)?\s*\(?(\w)\)\s*(.+)$/u
226
- LINE_RE = /^\s*([[:alpha:]]|\w+)/u
190
+ LINE_RE = /^\s*\\?\s*([[:alpha:]]|\w+)/u
227
191
 
228
- def self.parse(file, lines)
192
+ def self.parse(lines)
229
193
  m = RE.match(lines.shift)
230
- Answer.new(m[4].strip + " " + lines.collect(&:strip).join(" "),
194
+ Answer.new(m[4].strip + " " + lines.map(&:strip).join(" ").gsub("\\n", "\n"),
231
195
  m[3].strip, !m[1].nil?)
232
196
  end
233
197
 
234
198
  def to_s
235
- "(#{self.index}) #{self.content}".word_wrap
199
+ "(#{self.index}) #{self.content}".word_wrap(:indent => 7)
236
200
  end
237
201
  end
238
202
  end
metadata CHANGED
@@ -1,13 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: Pickaxe
3
3
  version: !ruby/object:Gem::Version
4
- hash: 9
4
+ hash: 15
5
5
  prerelease: false
6
6
  segments:
7
7
  - 0
8
8
  - 5
9
- - 1
10
- version: 0.5.1
9
+ - 2
10
+ version: 0.5.2
11
11
  platform: ruby
12
12
  authors:
13
13
  - Dawid Fatyga
@@ -15,7 +15,7 @@ autorequire:
15
15
  bindir: bin
16
16
  cert_chain: []
17
17
 
18
- date: 2010-12-02 00:00:00 +01:00
18
+ date: 2010-12-03 00:00:00 +01:00
19
19
  default_executable: bin/pickaxe
20
20
  dependencies:
21
21
  - !ruby/object:Gem::Dependency
@@ -87,6 +87,7 @@ files:
87
87
  - lib/pickaxe/color.rb
88
88
  - lib/pickaxe/extensions.rb
89
89
  - lib/pickaxe/shell.rb
90
+ - lib/pickaxe/errors.rb
90
91
  - lib/pickaxe/main.rb
91
92
  - bin/drill
92
93
  - bin/pickaxe