Pickaxe 0.5.1 → 0.5.2
Sign up to get free protection for your applications and to get access to all the features.
- 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
|