s0nspark-choice 0.1.4
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/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
|