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.
- data/lib/pickaxe.rb +3 -7
- data/lib/pickaxe/cli.rb +13 -5
- data/lib/pickaxe/color.rb +7 -3
- data/lib/pickaxe/errors.rb +60 -0
- data/lib/pickaxe/extensions.rb +9 -4
- data/lib/pickaxe/main.rb +8 -4
- data/lib/pickaxe/test.rb +45 -81
- metadata +5 -4
data/lib/pickaxe.rb
CHANGED
|
@@ -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.
|
|
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'
|
data/lib/pickaxe/cli.rb
CHANGED
|
@@ -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
|
|
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.
|
|
53
|
-
exit(e.status_code)
|
|
60
|
+
rescue Pickaxe::PickaxeError, OptionParser::InvalidOption => e
|
|
61
|
+
$stderr.puts(("! " + e.to_s).color(:red))
|
|
54
62
|
end
|
data/lib/pickaxe/color.rb
CHANGED
|
@@ -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
|
-
|
|
51
|
-
|
|
52
|
-
|
|
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
|
data/lib/pickaxe/extensions.rb
CHANGED
|
@@ -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!(
|
|
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)
|
data/lib/pickaxe/main.rb
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
module Pickaxe
|
|
2
2
|
class Main
|
|
3
|
-
class NoTests < PickaxeError;
|
|
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]
|
data/lib/pickaxe/test.rb
CHANGED
|
@@ -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.
|
|
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
|
-
|
|
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
|
-
|
|
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(
|
|
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(
|
|
125
|
+
answers = answers.collect {|answer| Answer.parse(answer) }
|
|
183
126
|
Question.new(file, m[1], content, answers).tap do |q|
|
|
184
|
-
raise NoCorrectAnswer.new(
|
|
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.
|
|
190
|
-
"
|
|
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(
|
|
192
|
+
def self.parse(lines)
|
|
229
193
|
m = RE.match(lines.shift)
|
|
230
|
-
Answer.new(m[4].strip + " " + lines.
|
|
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:
|
|
4
|
+
hash: 15
|
|
5
5
|
prerelease: false
|
|
6
6
|
segments:
|
|
7
7
|
- 0
|
|
8
8
|
- 5
|
|
9
|
-
-
|
|
10
|
-
version: 0.5.
|
|
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-
|
|
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
|