Pickaxe 0.5.1 → 0.5.2

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