s0nspark-choice 0.1.4
Sign up to get free protection for your applications and to get access to all the features.
- data/LICENSE +19 -0
- data/README.rdoc +441 -0
- data/lib/choice.rb +155 -0
- data/lib/choice/lazyhash.rb +67 -0
- data/lib/choice/option.rb +90 -0
- data/lib/choice/parser.rb +227 -0
- data/lib/choice/version.rb +8 -0
- data/lib/choice/writer.rb +187 -0
- data/test/choice_test.rb +231 -0
- data/test/lazyhash_test.rb +77 -0
- data/test/option_test.rb +146 -0
- data/test/parser_test.rb +374 -0
- data/test/test_helper.rb +5 -0
- data/test/writer_test.rb +104 -0
- metadata +69 -0
@@ -0,0 +1,187 @@
|
|
1
|
+
module Choice
|
2
|
+
# This module writes to the screen. As of now, its only real use is writing
|
3
|
+
# the help screen.
|
4
|
+
module Writer #:nodoc: all
|
5
|
+
|
6
|
+
# Some constants used for printing and line widths
|
7
|
+
SHORT_LENGTH = 6
|
8
|
+
SHORT_BREAK_LENGTH = 2
|
9
|
+
LONG_LENGTH = 29
|
10
|
+
PRE_DESC_LENGTH = SHORT_LENGTH + SHORT_BREAK_LENGTH + LONG_LENGTH
|
11
|
+
|
12
|
+
|
13
|
+
# The main method. Takes a hash of arguments with the following possible
|
14
|
+
# keys, running them through the appropriate method:
|
15
|
+
# banner, header, options, footer
|
16
|
+
#
|
17
|
+
# Can also be told where to print (default STDOUT) and not to exit after
|
18
|
+
# printing the help screen, which it does by default.
|
19
|
+
def self.help(args, target = STDOUT, dont_exit = false)
|
20
|
+
# Set our printing target.
|
21
|
+
self.target = target
|
22
|
+
|
23
|
+
# The banner method needs to know about the passed options if it's going
|
24
|
+
# to do its magic. Only really needs :options if :banner is nil.
|
25
|
+
banner(args[:banner], args[:options])
|
26
|
+
|
27
|
+
# Run these three methods, passing in the appropriate hash element.
|
28
|
+
%w[header options footer].each do |meth|
|
29
|
+
send(meth, args[meth.to_sym])
|
30
|
+
end
|
31
|
+
|
32
|
+
# Exit. Unless you don't want to.
|
33
|
+
exit unless dont_exit
|
34
|
+
end
|
35
|
+
|
36
|
+
class <<self
|
37
|
+
private
|
38
|
+
|
39
|
+
# Print a passed banner or assemble the default banner, which is usage.
|
40
|
+
def banner(banner, options)
|
41
|
+
if banner
|
42
|
+
puts banner
|
43
|
+
else
|
44
|
+
# Usage needs to know about the defined options.
|
45
|
+
usage(options)
|
46
|
+
end
|
47
|
+
end
|
48
|
+
|
49
|
+
# Print our header, which is just lines after the banner and before the
|
50
|
+
# options block. Needs an array, prints each element as a line.
|
51
|
+
def header(header)
|
52
|
+
if header.is_a?(Array) and header.size > 0
|
53
|
+
header.each { |line| puts line }
|
54
|
+
end
|
55
|
+
end
|
56
|
+
|
57
|
+
# Print out the options block by going through each option and printing
|
58
|
+
# it as a line (or more). Expects an array.
|
59
|
+
def options(options)
|
60
|
+
# Do nothing if there's nothing to do.
|
61
|
+
return if options.nil? || !options.size
|
62
|
+
|
63
|
+
# If the option is a hash, run it through option_line. Otherwise
|
64
|
+
# just print it out as is.
|
65
|
+
options.each do |name, option|
|
66
|
+
if option.respond_to?(:to_h)
|
67
|
+
option_line(option.to_h)
|
68
|
+
else
|
69
|
+
puts name
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
# The heavy lifting: print a line for an option. Has intimate knowledge
|
75
|
+
# of what keys are expected.
|
76
|
+
def option_line(option)
|
77
|
+
# Expect a hash
|
78
|
+
return unless option.is_a?(Hash)
|
79
|
+
|
80
|
+
# Make this easier on us
|
81
|
+
short = option['short']
|
82
|
+
long = option['long']
|
83
|
+
line = ''
|
84
|
+
|
85
|
+
# Get the short part.
|
86
|
+
line << sprintf("%#{SHORT_LENGTH}s", short)
|
87
|
+
line << sprintf("%-#{SHORT_BREAK_LENGTH}s", (',' if short && long))
|
88
|
+
|
89
|
+
# Get the long part.
|
90
|
+
line << sprintf("%-#{LONG_LENGTH}s", long)
|
91
|
+
|
92
|
+
# Print what we have so far
|
93
|
+
print line
|
94
|
+
|
95
|
+
# If there's a desc, print it.
|
96
|
+
if option['desc']
|
97
|
+
# If the line is too long, spill over to the next line
|
98
|
+
if line.length > PRE_DESC_LENGTH
|
99
|
+
puts
|
100
|
+
print " " * PRE_DESC_LENGTH
|
101
|
+
end
|
102
|
+
|
103
|
+
puts option['desc'].shift
|
104
|
+
|
105
|
+
# If there is more than one desc line, print each one in succession
|
106
|
+
# as separate lines.
|
107
|
+
option['desc'].each do |desc|
|
108
|
+
puts ' '*37 + desc
|
109
|
+
end
|
110
|
+
|
111
|
+
else
|
112
|
+
# No desc, just print a newline.
|
113
|
+
puts
|
114
|
+
|
115
|
+
end
|
116
|
+
end
|
117
|
+
|
118
|
+
# Expects an array, prints each element as a line.
|
119
|
+
def footer(footer)
|
120
|
+
footer.each { |line| puts line } unless footer.nil?
|
121
|
+
end
|
122
|
+
|
123
|
+
# Prints the usage statement, e.g. Usage prog.rb [-abc]
|
124
|
+
# Expects an array.
|
125
|
+
def usage(options)
|
126
|
+
# Really we just need an enumerable.
|
127
|
+
return unless options.respond_to?(:each)
|
128
|
+
|
129
|
+
# Start off the options with a dash.
|
130
|
+
opts = '-'
|
131
|
+
|
132
|
+
# Figure out the option shorts.
|
133
|
+
options.dup.each do |option|
|
134
|
+
# We really need an array here.
|
135
|
+
next unless option.is_a?(Array)
|
136
|
+
|
137
|
+
# Grab the hash of the last element, which should be the second
|
138
|
+
# element.
|
139
|
+
option = option.last.to_h
|
140
|
+
|
141
|
+
# Add the short to the options string.
|
142
|
+
opts << option['short'].sub('-','') if option['short']
|
143
|
+
end
|
144
|
+
|
145
|
+
# Figure out if we actually got any options.
|
146
|
+
opts = if opts =~ /^-(.+)/
|
147
|
+
" [#{opts}]"
|
148
|
+
end.to_s
|
149
|
+
|
150
|
+
# Print it out, with our newly aquired options string.
|
151
|
+
puts "Usage: #{program}" << opts
|
152
|
+
end
|
153
|
+
|
154
|
+
# Figure out the name of this program based on what was run.
|
155
|
+
def program
|
156
|
+
(/(\/|\\)/ =~ $0) ? File.basename($0) : $0
|
157
|
+
end
|
158
|
+
|
159
|
+
# Set where we print.
|
160
|
+
def target=(target)
|
161
|
+
@@target = target
|
162
|
+
end
|
163
|
+
|
164
|
+
# Where do we print?
|
165
|
+
def target
|
166
|
+
@@target
|
167
|
+
end
|
168
|
+
|
169
|
+
public
|
170
|
+
# Fake puts
|
171
|
+
def puts(str = nil)
|
172
|
+
str = '' if str.nil?
|
173
|
+
print(str + "\n")
|
174
|
+
end
|
175
|
+
|
176
|
+
# Fake printf
|
177
|
+
def printf(format, *args)
|
178
|
+
print(sprintf(format, *args))
|
179
|
+
end
|
180
|
+
|
181
|
+
# Fake print -- just add to target, which may not be STDOUT.
|
182
|
+
def print(str)
|
183
|
+
target << str
|
184
|
+
end
|
185
|
+
end
|
186
|
+
end
|
187
|
+
end
|
data/test/choice_test.rb
ADDED
@@ -0,0 +1,231 @@
|
|
1
|
+
$VERBOSE = nil
|
2
|
+
|
3
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), 'test_helper')
|
4
|
+
|
5
|
+
require "choice"
|
6
|
+
|
7
|
+
class TestChoice < Test::Unit::TestCase
|
8
|
+
|
9
|
+
def setup
|
10
|
+
Choice.reset!
|
11
|
+
Choice.dont_exit_on_help = true
|
12
|
+
Choice.send(:class_variable_set, '@@choices', true)
|
13
|
+
end
|
14
|
+
|
15
|
+
def test_choices
|
16
|
+
Choice.options do
|
17
|
+
header "Tell me about yourself?"
|
18
|
+
header ""
|
19
|
+
option :band do
|
20
|
+
short "-b"
|
21
|
+
long "--band=BAND"
|
22
|
+
cast String
|
23
|
+
desc "Your favorite band."
|
24
|
+
validate /\w+/
|
25
|
+
end
|
26
|
+
option :animal do
|
27
|
+
short "-a"
|
28
|
+
long "--animal=ANIMAL"
|
29
|
+
cast String
|
30
|
+
desc "Your favorite animal."
|
31
|
+
end
|
32
|
+
footer ""
|
33
|
+
footer "--help This message"
|
34
|
+
end
|
35
|
+
|
36
|
+
band = 'LedZeppelin'
|
37
|
+
animal = 'Reindeer'
|
38
|
+
|
39
|
+
args = ['-b', band, "--animal=#{animal}"]
|
40
|
+
Choice.args = args
|
41
|
+
|
42
|
+
assert_equal band, Choice.choices['band']
|
43
|
+
assert_equal animal, Choice.choices[:animal]
|
44
|
+
assert_equal ["Tell me about yourself?", ""], Choice.header
|
45
|
+
assert_equal ["", "--help This message"], Choice.footer
|
46
|
+
end
|
47
|
+
|
48
|
+
def test_failed_parse
|
49
|
+
assert Hash.new, Choice.parse
|
50
|
+
end
|
51
|
+
|
52
|
+
HELP_STRING = ''
|
53
|
+
def test_help
|
54
|
+
Choice.output_to(HELP_STRING)
|
55
|
+
|
56
|
+
Choice.options do
|
57
|
+
banner "Usage: choice [-mu]"
|
58
|
+
header ""
|
59
|
+
option :meal do
|
60
|
+
short '-m'
|
61
|
+
desc 'Your favorite meal.'
|
62
|
+
end
|
63
|
+
|
64
|
+
separator ""
|
65
|
+
separator "And you eat it with..."
|
66
|
+
|
67
|
+
option :utencil do
|
68
|
+
short "-u"
|
69
|
+
long "--utencil[=UTENCIL]"
|
70
|
+
desc "Your favorite eating utencil."
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
Choice.args = ['-m', 'lunch', '--help']
|
75
|
+
|
76
|
+
help_string = <<-HELP
|
77
|
+
Usage: choice [-mu]
|
78
|
+
|
79
|
+
-m Your favorite meal.
|
80
|
+
|
81
|
+
And you eat it with...
|
82
|
+
-u, --utencil[=UTENCIL] Your favorite eating utencil.
|
83
|
+
HELP
|
84
|
+
|
85
|
+
assert_equal help_string, HELP_STRING
|
86
|
+
end
|
87
|
+
|
88
|
+
UNKNOWN_STRING = ''
|
89
|
+
def test_unknown_argument
|
90
|
+
Choice.output_to(UNKNOWN_STRING)
|
91
|
+
|
92
|
+
Choice.options do
|
93
|
+
banner "Usage: choice [-mu]"
|
94
|
+
header ""
|
95
|
+
option :meal do
|
96
|
+
short '-m'
|
97
|
+
desc 'Your favorite meal.'
|
98
|
+
end
|
99
|
+
|
100
|
+
separator ""
|
101
|
+
separator "And you eat it with..."
|
102
|
+
|
103
|
+
option :utencil do
|
104
|
+
short "-u"
|
105
|
+
long "--utencil[=UTENCIL]"
|
106
|
+
desc "Your favorite eating utencil."
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
Choice.args = ['-m', 'lunch', '--motorcycles']
|
111
|
+
|
112
|
+
help_string = <<-HELP
|
113
|
+
Usage: choice [-mu]
|
114
|
+
|
115
|
+
-m Your favorite meal.
|
116
|
+
|
117
|
+
And you eat it with...
|
118
|
+
-u, --utencil[=UTENCIL] Your favorite eating utencil.
|
119
|
+
HELP
|
120
|
+
|
121
|
+
assert_equal help_string, UNKNOWN_STRING
|
122
|
+
end
|
123
|
+
|
124
|
+
REQUIRED_STRING = ''
|
125
|
+
def test_required_argument
|
126
|
+
Choice.output_to(REQUIRED_STRING)
|
127
|
+
|
128
|
+
Choice.options do
|
129
|
+
banner "Usage: choice [-mu]"
|
130
|
+
header ""
|
131
|
+
option :meal, :required => true do
|
132
|
+
short '-m'
|
133
|
+
desc 'Your favorite meal.'
|
134
|
+
end
|
135
|
+
|
136
|
+
separator ""
|
137
|
+
separator "And you eat it with..."
|
138
|
+
|
139
|
+
option :utencil do
|
140
|
+
short "-u"
|
141
|
+
long "--utencil[=UTENCIL]"
|
142
|
+
desc "Your favorite eating utencil."
|
143
|
+
end
|
144
|
+
end
|
145
|
+
|
146
|
+
Choice.args = ['-u', 'spork']
|
147
|
+
|
148
|
+
help_string = <<-HELP
|
149
|
+
Usage: choice [-mu]
|
150
|
+
|
151
|
+
-m Your favorite meal.
|
152
|
+
|
153
|
+
And you eat it with...
|
154
|
+
-u, --utencil[=UTENCIL] Your favorite eating utencil.
|
155
|
+
HELP
|
156
|
+
|
157
|
+
assert_equal help_string, REQUIRED_STRING
|
158
|
+
end
|
159
|
+
|
160
|
+
def test_shorthand_choices
|
161
|
+
Choice.options do
|
162
|
+
header "Tell me about yourself?"
|
163
|
+
header ""
|
164
|
+
options :band => { :short => "-b", :long => "--band=BAND", :cast => String, :desc => ["Your favorite band.", "Something cool."],
|
165
|
+
:validate => /\w+/ },
|
166
|
+
:animal => { :short => "-a", :long => "--animal=ANIMAL", :cast => String, :desc => "Your favorite animal." }
|
167
|
+
|
168
|
+
footer ""
|
169
|
+
footer "--help This message"
|
170
|
+
end
|
171
|
+
|
172
|
+
band = 'LedZeppelin'
|
173
|
+
animal = 'Reindeer'
|
174
|
+
|
175
|
+
args = ['-b', band, "--animal=#{animal}"]
|
176
|
+
Choice.args = args
|
177
|
+
|
178
|
+
assert_equal band, Choice.choices['band']
|
179
|
+
assert_equal animal, Choice.choices[:animal]
|
180
|
+
assert_equal ["Tell me about yourself?", ""], Choice.header
|
181
|
+
assert_equal ["", "--help This message"], Choice.footer
|
182
|
+
end
|
183
|
+
|
184
|
+
def test_args_of
|
185
|
+
suits = %w[clubs diamonds spades hearts]
|
186
|
+
stringed_numerics = (1..13).to_a.map { |a| a.to_s }
|
187
|
+
valid_cards = stringed_numerics + %w[jack queen king ace]
|
188
|
+
cards = {}
|
189
|
+
stringed_numerics.each { |n| cards[n] = n }
|
190
|
+
cards.merge!('1' => 'ace', '11' => 'jack', '12' => 'queen', '13' => 'king')
|
191
|
+
|
192
|
+
Choice.options do
|
193
|
+
header "Gambling is fun again! Pick a card and a suit (or two), then see if you win!"
|
194
|
+
header ""
|
195
|
+
header "Options:"
|
196
|
+
|
197
|
+
option :suit, :required => true do
|
198
|
+
short '-s'
|
199
|
+
long '--suit *SUITS'
|
200
|
+
desc "The suit you wish to choose. Required. You can pass in more than one, even."
|
201
|
+
desc " Valid suits: #{suits * ' '}"
|
202
|
+
valid suits
|
203
|
+
end
|
204
|
+
|
205
|
+
separator ''
|
206
|
+
|
207
|
+
option :card, :required => true do
|
208
|
+
short '-c'
|
209
|
+
long '--card CARD'
|
210
|
+
desc "The card you wish to gamble on. Required. Only one, please."
|
211
|
+
desc " Valid cards: 1 - 13, jack, queen, king, ace"
|
212
|
+
valid valid_cards
|
213
|
+
cast String
|
214
|
+
end
|
215
|
+
|
216
|
+
#cheat! to test --option=
|
217
|
+
option :autowin do
|
218
|
+
short '-a'
|
219
|
+
long '--autowin=PLAYER'
|
220
|
+
desc 'The person who should automatically win every time'
|
221
|
+
desc 'Beware: raises the suspitions of other players'
|
222
|
+
end
|
223
|
+
end
|
224
|
+
|
225
|
+
args = ["-c", "king", "--suit", "clubs", "diamonds", "spades", "hearts", "--autowin", "Grant"]
|
226
|
+
Choice.args = args
|
227
|
+
assert_equal ["king"], Choice.args_of("-c")
|
228
|
+
assert_equal ["clubs", "diamonds", "spades", "hearts"], Choice.args_of("--suit")
|
229
|
+
assert_equal ["Grant"], Choice.args_of("--autowin")
|
230
|
+
end
|
231
|
+
end
|
@@ -0,0 +1,77 @@
|
|
1
|
+
|
2
|
+
require File.join(File.expand_path(File.dirname(__FILE__)), 'test_helper')
|
3
|
+
|
4
|
+
require 'choice/lazyhash'
|
5
|
+
|
6
|
+
class TestLazyHash < Test::Unit::TestCase
|
7
|
+
|
8
|
+
def test_symbol
|
9
|
+
name = 'Igor'
|
10
|
+
age = 41
|
11
|
+
|
12
|
+
hash = Choice::LazyHash.new
|
13
|
+
hash['name'] = name
|
14
|
+
hash[:age] = age
|
15
|
+
|
16
|
+
assert_equal name, hash[:name]
|
17
|
+
assert_equal age, hash[:age]
|
18
|
+
end
|
19
|
+
|
20
|
+
def test_string
|
21
|
+
name = "Frank Stein"
|
22
|
+
age = 30
|
23
|
+
|
24
|
+
hash = Choice::LazyHash.new
|
25
|
+
hash[:name] = name
|
26
|
+
hash['age'] = age
|
27
|
+
|
28
|
+
assert_equal name, hash['name']
|
29
|
+
assert_equal age, hash['age']
|
30
|
+
end
|
31
|
+
|
32
|
+
def test_store_and_fetch
|
33
|
+
name = 'Jimini Jeremiah'
|
34
|
+
job = 'Interior Decorator'
|
35
|
+
|
36
|
+
hash = Choice::LazyHash.new
|
37
|
+
hash.store('name', name)
|
38
|
+
hash.store(:job, job)
|
39
|
+
|
40
|
+
assert_equal name, hash.fetch(:name)
|
41
|
+
assert_equal job, hash.fetch('job')
|
42
|
+
end
|
43
|
+
|
44
|
+
def test_messages
|
45
|
+
star = 'Sol'
|
46
|
+
planet = 'Mars'
|
47
|
+
|
48
|
+
hash = Choice::LazyHash.new
|
49
|
+
hash.star = star
|
50
|
+
hash.planet = planet
|
51
|
+
|
52
|
+
assert_equal star, hash.star
|
53
|
+
assert_equal planet, hash.planet
|
54
|
+
end
|
55
|
+
|
56
|
+
def test_from_hash
|
57
|
+
state = 'Nebraska'
|
58
|
+
country = 'Mexico'
|
59
|
+
|
60
|
+
hash = { :state => state, :country => country }
|
61
|
+
lazy = Choice::LazyHash.new(hash)
|
62
|
+
|
63
|
+
assert_equal state, lazy['state']
|
64
|
+
assert_equal country, lazy[:country]
|
65
|
+
end
|
66
|
+
|
67
|
+
def test_to_lazyhash
|
68
|
+
hash = { :name => 'Jimmy', :age => 25 }
|
69
|
+
lazy = hash.to_lazyhash
|
70
|
+
|
71
|
+
assert_equal hash[:name], lazy.name
|
72
|
+
assert_equal hash[:name], lazy[:name]
|
73
|
+
assert_equal hash[:age], lazy.age
|
74
|
+
assert_equal hash[:age], lazy[:age]
|
75
|
+
end
|
76
|
+
|
77
|
+
end
|