choosy 0.1.0 → 0.2.0
Sign up to get free protection for your applications and to get access to all the features.
- data/README.markdown +229 -221
- data/Rakefile +21 -3
- data/examples/bar.rb +44 -0
- data/examples/foo.rb +198 -0
- data/examples/superfoo.rb +125 -0
- data/lib/VERSION +1 -1
- data/lib/choosy/argument.rb +51 -0
- data/lib/choosy/base_command.rb +22 -7
- data/lib/choosy/command.rb +12 -4
- data/lib/choosy/dsl/argument_builder.rb +88 -0
- data/lib/choosy/dsl/base_command_builder.rb +71 -56
- data/lib/choosy/dsl/command_builder.rb +14 -2
- data/lib/choosy/dsl/option_builder.rb +43 -83
- data/lib/choosy/dsl/super_command_builder.rb +37 -9
- data/lib/choosy/option.rb +13 -11
- data/lib/choosy/parse_result.rb +8 -27
- data/lib/choosy/parser.rb +20 -16
- data/lib/choosy/printing/color.rb +39 -21
- data/lib/choosy/printing/erb_printer.rb +12 -3
- data/lib/choosy/printing/formatting_element.rb +17 -0
- data/lib/choosy/printing/help_printer.rb +204 -117
- data/lib/choosy/printing/terminal.rb +53 -0
- data/lib/choosy/super_command.rb +6 -6
- data/lib/choosy/super_parser.rb +26 -15
- data/lib/choosy/verifier.rb +61 -6
- data/spec/choosy/base_command_spec.rb +27 -2
- data/spec/choosy/command_spec.rb +31 -9
- data/spec/choosy/dsl/argument_builder_spec.rb +180 -0
- data/spec/choosy/dsl/base_command_builder_spec.rb +87 -44
- data/spec/choosy/dsl/commmand_builder_spec.rb +15 -4
- data/spec/choosy/dsl/option_builder_spec.rb +101 -191
- data/spec/choosy/dsl/super_command_builder_spec.rb +34 -9
- data/spec/choosy/parser_spec.rb +30 -8
- data/spec/choosy/printing/color_spec.rb +19 -5
- data/spec/choosy/printing/help_printer_spec.rb +152 -73
- data/spec/choosy/printing/terminal_spec.rb +27 -0
- data/spec/choosy/super_command_spec.rb +17 -17
- data/spec/choosy/super_parser_spec.rb +20 -10
- data/spec/choosy/verifier_spec.rb +137 -47
- data/spec/integration/command-A_spec.rb +6 -6
- data/spec/integration/command-B_spec.rb +45 -0
- data/spec/integration/supercommand-A_spec.rb +33 -27
- data/spec/integration/supercommand-B_spec.rb +32 -0
- data/spec/spec_helpers.rb +8 -5
- metadata +95 -54
data/Rakefile
CHANGED
@@ -14,9 +14,27 @@ desc "Default task"
|
|
14
14
|
task :default => [ :spec ]
|
15
15
|
|
16
16
|
desc "Build documentation"
|
17
|
-
task :doc => [ :rdoc ]
|
17
|
+
task :doc => [ :rdoc ] do
|
18
|
+
File.open 'docs/README.markdown', 'r' do |template|
|
19
|
+
File.open 'README.markdown', 'w' do |output|
|
20
|
+
template.each do |line|
|
21
|
+
if line =~ /^>>> (.*)/
|
22
|
+
puts $1
|
23
|
+
File.open $1, 'r' do |inserted|
|
24
|
+
inserted.each do |ins|
|
25
|
+
output.puts " #{ins}"
|
26
|
+
end
|
27
|
+
end
|
28
|
+
else
|
29
|
+
output.puts line
|
30
|
+
end
|
31
|
+
end
|
32
|
+
end
|
33
|
+
end
|
34
|
+
end
|
18
35
|
|
19
|
-
|
36
|
+
desc "Create RDocs: TODO"
|
37
|
+
task :rdoc
|
20
38
|
|
21
39
|
desc "Run the RSpec tests"
|
22
40
|
RSpec::Core::RakeTask.new :spec
|
@@ -47,7 +65,7 @@ task :clean do
|
|
47
65
|
end
|
48
66
|
|
49
67
|
desc "Deploys the gem to rubygems.org"
|
50
|
-
task :gem => :release do
|
68
|
+
task :gem => [:doc, :release] do
|
51
69
|
system("gem build #{PACKAGE_NAME}.gemspec")
|
52
70
|
system("gem push #{PACKAGE_NAME}-#{PACKAGE_VERSION}.gem")
|
53
71
|
end
|
data/examples/bar.rb
ADDED
@@ -0,0 +1,44 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# bar.rb
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift File.join(File.dirname(File.dirname(__FILE__)), 'lib')
|
5
|
+
require 'choosy'
|
6
|
+
|
7
|
+
# Create a new command
|
8
|
+
bar_cmd = Choosy::Command.new :bar do
|
9
|
+
executor do |args, options|
|
10
|
+
if options[:bold]
|
11
|
+
puts "BOLD!!!"
|
12
|
+
else
|
13
|
+
puts "plain"
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
summary "Displays when this is a subcommand"
|
18
|
+
para "Just prints 'bar'"
|
19
|
+
para "A truly unremarkable command"
|
20
|
+
|
21
|
+
header 'Option:'
|
22
|
+
boolean :bold, "Bolds something" do
|
23
|
+
negate 'un'
|
24
|
+
end
|
25
|
+
|
26
|
+
# Because there is no bar.arguments call,
|
27
|
+
# it is now an error if there are extra
|
28
|
+
# command line arguments to this command.
|
29
|
+
end
|
30
|
+
|
31
|
+
if __FILE__ == $0
|
32
|
+
args = ['--un-bold']
|
33
|
+
|
34
|
+
result = bar_cmd.parse!(args)
|
35
|
+
|
36
|
+
require 'pp'
|
37
|
+
pp result.options[:bold] # => false
|
38
|
+
pp result.args # => []
|
39
|
+
|
40
|
+
bar_cmd.execute!(args) # => 'plain'
|
41
|
+
|
42
|
+
args << 'should-throw-error'
|
43
|
+
bar_cmd.execute!(args)
|
44
|
+
end
|
data/examples/foo.rb
ADDED
@@ -0,0 +1,198 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# foo.rb
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift File.join(File.dirname(File.dirname(__FILE__)), 'lib')
|
5
|
+
require 'choosy'
|
6
|
+
|
7
|
+
FOO_VERSION = '1.0.1'
|
8
|
+
|
9
|
+
class FooExecutor
|
10
|
+
def execute!(args, options)
|
11
|
+
puts "BOLDED!!" if options[:bold]
|
12
|
+
options[:count].times do
|
13
|
+
puts "#{options[:prefix]}#{options[:words].push('foo').join(',')}#{options[:suffix]}"
|
14
|
+
end
|
15
|
+
puts "and #{args.join ' '}"
|
16
|
+
end
|
17
|
+
end
|
18
|
+
|
19
|
+
foo_cmd = Choosy::Command.new :foo do |foo|
|
20
|
+
# Add a class to do the execution when you call foo_cmd.execute!
|
21
|
+
# You can also use a proc that takes the options and the args, like:
|
22
|
+
# executor { |args, options| puts 'Hi!' }
|
23
|
+
executor FooExecutor.new
|
24
|
+
|
25
|
+
# When used as a subcommand, you need a summary for the help screen
|
26
|
+
summary "This is a nice command named 'foo'"
|
27
|
+
|
28
|
+
# You can add your custom printer by giving the
|
29
|
+
# full path to an ERB template file here.
|
30
|
+
# The default printer is :standard, but you can
|
31
|
+
# also use the builtin printer :erb, with the :tempates
|
32
|
+
# parameter to set the erb template you wish to use. The
|
33
|
+
# output can be colored or uncolored, though the
|
34
|
+
# default is colored.
|
35
|
+
printer :standard, :color => true, :header_styles => [:bold, :green]
|
36
|
+
|
37
|
+
para 'Prints out "foo" to the console'
|
38
|
+
para 'This is a long description of what foo is an how it works. This line will assuredly wrap the console at least once, since it it such a long line, but it will be wrapped automatically by the printer, above. If you want to, you can add write "printer :standard, :max_width => 80" to set the maximum column width that the printer will allow (not respected by ERB templates).'
|
39
|
+
|
40
|
+
header 'Required Options:' # Formatted according to the header_styles for the printer
|
41
|
+
|
42
|
+
# A shorthand for a common option type.
|
43
|
+
# It adds the '-p/--prefix PREFIX' infomation for you.
|
44
|
+
single :prefix, "A prefix for 'foo'" do
|
45
|
+
default '<'
|
46
|
+
required
|
47
|
+
end
|
48
|
+
|
49
|
+
# The long way to do the same thing as above, except with
|
50
|
+
# explicitly named dependencies
|
51
|
+
option :suffix => [:prefix] do
|
52
|
+
short '-s'
|
53
|
+
long '--suffix', 'SUFFIX'
|
54
|
+
desc 'A suffix for "foo"'
|
55
|
+
required
|
56
|
+
|
57
|
+
validate do |suffix, options|
|
58
|
+
if suffix == options[:prefix]
|
59
|
+
die "You can't matching prefixes and suffixes, you heathen!"
|
60
|
+
end
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# Just like the 'single' method above, except now it automatically
|
65
|
+
# requires/casts the argument to this flag into an integer. These commands
|
66
|
+
# also take an optional hash as the last argument, which can be used instead
|
67
|
+
# of a block.
|
68
|
+
integer :count, 'The number of times to repeat "foo"', :required => true
|
69
|
+
|
70
|
+
header 'Options:', :bold, :blue # Format this header differently, overrides 'header_styles'
|
71
|
+
|
72
|
+
option :words do
|
73
|
+
short '-w'
|
74
|
+
long '--words', 'WORDS+' # By default, the '+' at the end
|
75
|
+
# means that this takes multiple
|
76
|
+
# arguments. You put a '-' at
|
77
|
+
# the end of the argument list
|
78
|
+
# to stop parsing this option
|
79
|
+
# and allow for regular args.
|
80
|
+
desc "Other fun words to put in quotes"
|
81
|
+
|
82
|
+
# Sets the exact count of the number of arguments it accepts.
|
83
|
+
# also allowable are the single selectors :zero and :one.
|
84
|
+
# By default, the option 'WORDS+' sets the range to be
|
85
|
+
# {:at_least => 1, :at_most => 1000 }
|
86
|
+
count :at_least => 2, :at_most => 10
|
87
|
+
|
88
|
+
validate do |words, options|
|
89
|
+
words.each do |word|
|
90
|
+
if word !~ /\w+/
|
91
|
+
die "I can't print that: #{word}"
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
end
|
96
|
+
|
97
|
+
# Alternatively, we could have done the following:
|
98
|
+
strings :words, "Other fun words to put in quotes" do
|
99
|
+
count 2..10
|
100
|
+
default []
|
101
|
+
validate do |words, options|
|
102
|
+
words.each do |word|
|
103
|
+
if word !~ /\w+/
|
104
|
+
die "I can't print that: #{word}"
|
105
|
+
end
|
106
|
+
end
|
107
|
+
end
|
108
|
+
end
|
109
|
+
|
110
|
+
# Yet another shorthand notation for options, since they
|
111
|
+
# are boolean by default. Here, we add a negation to the
|
112
|
+
# long flag of the option, creating [-b|--bold|--un-bold] flags.
|
113
|
+
# By default, calling 'negate' in a block without an argument
|
114
|
+
# uses the '--no-' prefix instead.
|
115
|
+
boolean :bold, "Bold this option", :default => false, :negate => 'un'
|
116
|
+
|
117
|
+
# Tail options
|
118
|
+
|
119
|
+
# When any of the simpler notations are suffixed with a '_'
|
120
|
+
# character, the short option is always suppressed.
|
121
|
+
boolean_ :debug, "Prints out extra debugging output."
|
122
|
+
|
123
|
+
# The '_' characters are replaced with '-' in flags, so the
|
124
|
+
# following creates a '--[no-]color' flag.
|
125
|
+
boolean_ :color, "Turns on/off coloring in the output. Defalt is on." do
|
126
|
+
negate
|
127
|
+
default true
|
128
|
+
validate do
|
129
|
+
foo.command.alter do
|
130
|
+
printer :standard, :colored => false
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
# Adds the standard -h/--help option.
|
136
|
+
# Should skip the '-h' flag if already set.
|
137
|
+
help # Automatically adds the description if not passed an argument. You can supply your own
|
138
|
+
|
139
|
+
# Adds the --version option.
|
140
|
+
version "Foo: #{FOO_VERSION}"
|
141
|
+
|
142
|
+
# Now, add some validation for any addtional arguments
|
143
|
+
# that are left over after the parsing the options.
|
144
|
+
arguments do
|
145
|
+
metaname 'ARGS'
|
146
|
+
count 1..10
|
147
|
+
validate do |args, options|
|
148
|
+
if args.empty?
|
149
|
+
die "You have to pass in empty arguments that do nothing!"
|
150
|
+
end
|
151
|
+
if args.count == 10
|
152
|
+
die "Whoa there! You're going argument crazy!"
|
153
|
+
end
|
154
|
+
end
|
155
|
+
end
|
156
|
+
end
|
157
|
+
|
158
|
+
if __FILE__ == $0
|
159
|
+
# Parses and validates the options.
|
160
|
+
args = ['--prefix', '{',
|
161
|
+
'--suffix', '}',
|
162
|
+
'--words', 'high', 'there', 'you', '-',
|
163
|
+
# The '-' stops parsing this option, so that:
|
164
|
+
'handsom', 'devil',
|
165
|
+
'http://not.posting.here', # will be regular arguments
|
166
|
+
'-c', '3', # Count
|
167
|
+
'--', # Stops parsing all arguments
|
168
|
+
'-h', '--help', '-v', '--version' # Ignored
|
169
|
+
]
|
170
|
+
result = foo_cmd.parse!(args)
|
171
|
+
|
172
|
+
require 'pp'
|
173
|
+
pp result[:prefix] # => '{'
|
174
|
+
pp result[:suffix] # => '}'
|
175
|
+
pp result[:count] # => 3
|
176
|
+
pp result[:bold] # => false
|
177
|
+
pp result[:words] # => ['high', 'there', 'you']
|
178
|
+
pp result.args # => ['handsom', 'devil',
|
179
|
+
# 'http://not.posting.here',
|
180
|
+
# '-h', '--help', '-v', '--version']
|
181
|
+
pp result.options # => {:prefix => '{', :suffix => '}'
|
182
|
+
# :count => 3, :bold => false,
|
183
|
+
# :words => ['high', 'there', 'you'],
|
184
|
+
# :debug => false, :color => true}
|
185
|
+
|
186
|
+
# Now, call the command that does the actual work.
|
187
|
+
# This passes the foo_cmd.options and the foo_cmd.args
|
188
|
+
# as arguments to the executors 'execute!' method.
|
189
|
+
#
|
190
|
+
# This allows you to easily associate command classes with
|
191
|
+
# commands, without resorting to a hash or combining
|
192
|
+
# execution logic with command parsing logic.
|
193
|
+
foo_cmd.execute!(args) # {high,there,you,foo}
|
194
|
+
# {high,there,you,foo}
|
195
|
+
# {high,there,you,foo}
|
196
|
+
# and handsom devil http://not.posting.here -h --help -v --verbose
|
197
|
+
|
198
|
+
end
|
@@ -0,0 +1,125 @@
|
|
1
|
+
#!/usr/bin/env ruby -w
|
2
|
+
# superfoo.rb
|
3
|
+
|
4
|
+
$LOAD_PATH.unshift File.join(File.dirname(File.dirname(__FILE__)), 'lib')
|
5
|
+
$LOAD_PATH.unshift File.join(File.dirname(File.dirname(__FILE__)), 'examples')
|
6
|
+
require 'choosy'
|
7
|
+
require "foo"
|
8
|
+
require "bar"
|
9
|
+
|
10
|
+
SUPERFOO_VERSION = "1.0.1"
|
11
|
+
|
12
|
+
superfoo = Choosy::SuperCommand.new :superfoo do
|
13
|
+
summary "This is a superfoo command"
|
14
|
+
para "Say something, dammit!"
|
15
|
+
|
16
|
+
# You can also add commands after instantiation.
|
17
|
+
# Note that, when added, these commands have their
|
18
|
+
# -h/--help/--version flags suppressed, so you'll
|
19
|
+
# need to add those flags here.
|
20
|
+
command bar_cmd
|
21
|
+
command foo_cmd
|
22
|
+
|
23
|
+
# Creates a 'help' command, message optional
|
24
|
+
help "Prints this help message"
|
25
|
+
|
26
|
+
# Create some global options that are parsed
|
27
|
+
# defore result options
|
28
|
+
|
29
|
+
# Here, check that a YAML file exists, and attempt
|
30
|
+
# to load it's parsed contents into this option.
|
31
|
+
# There is also a 'file' type that checks to see
|
32
|
+
# if the file exists. With both 'file' and 'yaml',
|
33
|
+
# if the file is missing, the option fails with an
|
34
|
+
# error.
|
35
|
+
yaml :Config, "Configure your superfoo with a YAML configuration file." do
|
36
|
+
default File.join(ENV['HOME'], '.superfoo.yml')
|
37
|
+
end
|
38
|
+
|
39
|
+
# Adds a global --version flag.
|
40
|
+
version "#{SUPERFOO_VERSION}"
|
41
|
+
end
|
42
|
+
|
43
|
+
if __FILE__ == $0
|
44
|
+
args = ['foo',
|
45
|
+
'-c', '5',
|
46
|
+
'--config', '~/.superfoo',
|
47
|
+
'--prefix', '{',
|
48
|
+
'--suffix', '}',
|
49
|
+
'cruft',
|
50
|
+
'bar',
|
51
|
+
'--bold']
|
52
|
+
|
53
|
+
result = superfoo.parse!(args)
|
54
|
+
|
55
|
+
require 'pp'
|
56
|
+
pp result[:config] # => '~/.superfoo'
|
57
|
+
pp result.name # => :foo
|
58
|
+
pp result[:prefix] # => '{'
|
59
|
+
pp result[:suffix] # => '}'
|
60
|
+
pp result[:count] # => 2
|
61
|
+
pp result[:bold] # => true
|
62
|
+
pp result.options # => {:prefix => '{', :suffix => '}'
|
63
|
+
# :count => 2,
|
64
|
+
# :bold => true,
|
65
|
+
# :words => [],
|
66
|
+
# :config => '~/.superfoo' }
|
67
|
+
pp result.args # => ['cruft', 'bar']
|
68
|
+
|
69
|
+
# Now, we can call the result
|
70
|
+
superfoo.execute!(args) ## Calls superfoo.result.execute!
|
71
|
+
## Prints:
|
72
|
+
# BOLDED!!
|
73
|
+
# {foo}
|
74
|
+
# {foo}
|
75
|
+
# and cruft bar
|
76
|
+
|
77
|
+
# Instead of parsing the 'bar' parameter as an argument to
|
78
|
+
# the foo command, so that when the first argument that matches
|
79
|
+
# another command name is encountered, it stops parsing the
|
80
|
+
# current command and passes the rest of the arguments to the
|
81
|
+
# next command.
|
82
|
+
#
|
83
|
+
# In this case, we call the 'alter' method to use the DSL
|
84
|
+
# syntax again to alter this command.
|
85
|
+
#
|
86
|
+
# You can also set this inside a SuperChoosy.new {...}
|
87
|
+
# block.
|
88
|
+
superfoo.alter do
|
89
|
+
parsimonious
|
90
|
+
end
|
91
|
+
|
92
|
+
result = superfoo.parse!(args)
|
93
|
+
|
94
|
+
pp result.name # => :superfoo
|
95
|
+
pp result[:config] # => '~/.superfoo'
|
96
|
+
pp result.subresults[0].name # => :foo
|
97
|
+
pp result.subresults[0][:prefix] # => '{'
|
98
|
+
pp result.subresults[0][:suffix] # => '}'
|
99
|
+
pp result.subresults[0][:count] # => 2
|
100
|
+
pp result.subresults[0][:bold] # => true
|
101
|
+
pp result.subresults[0].options # => {:prefix => '{', :suffix => '}'
|
102
|
+
# :count => 2,
|
103
|
+
# :bold => false,
|
104
|
+
# :words => [],
|
105
|
+
# :config => '~/.superfoo' }
|
106
|
+
pp result.subresults[0].args # => ['cruft']
|
107
|
+
|
108
|
+
pp result.subresults[1].name # => :bar
|
109
|
+
pp result.subresults[1][:bold] # => true
|
110
|
+
pp result.subresults[1].options # => {:bold => true,
|
111
|
+
# :config => '~/.superfoo'}
|
112
|
+
pp result.subresults[1].args # => []
|
113
|
+
|
114
|
+
# Now, execute the results in order
|
115
|
+
superfoo.execute!(args) ## Same as:
|
116
|
+
# results.each do |subcommand|
|
117
|
+
# command.execute!
|
118
|
+
# end
|
119
|
+
## Prints:
|
120
|
+
# {foo}
|
121
|
+
# {foo}
|
122
|
+
# and cruft
|
123
|
+
# BOLDED BAR
|
124
|
+
end
|
125
|
+
|
data/lib/VERSION
CHANGED
@@ -1 +1 @@
|
|
1
|
-
0.
|
1
|
+
0.2.0
|
@@ -0,0 +1,51 @@
|
|
1
|
+
require 'choosy/errors'
|
2
|
+
|
3
|
+
module Choosy
|
4
|
+
class Argument
|
5
|
+
ZERO_ARITY = (0 .. 0)
|
6
|
+
ONE_ARITY = (1 .. 1)
|
7
|
+
MANY_ARITY = (1 .. 1000)
|
8
|
+
|
9
|
+
attr_accessor :metaname, :validation_step, :arity, :cast_to, :allowable_values
|
10
|
+
|
11
|
+
def initialize
|
12
|
+
@required = false
|
13
|
+
end
|
14
|
+
|
15
|
+
def required=(val)
|
16
|
+
@required = val
|
17
|
+
end
|
18
|
+
|
19
|
+
def required?
|
20
|
+
@required
|
21
|
+
end
|
22
|
+
|
23
|
+
def restricted?
|
24
|
+
!allowable_values.nil? && allowable_values.length > 0
|
25
|
+
end
|
26
|
+
|
27
|
+
def boolean?
|
28
|
+
@arity == ZERO_ARITY
|
29
|
+
end
|
30
|
+
|
31
|
+
def single?
|
32
|
+
@arity == ONE_ARITY
|
33
|
+
end
|
34
|
+
|
35
|
+
def multiple?
|
36
|
+
@arity == MANY_ARITY
|
37
|
+
end
|
38
|
+
|
39
|
+
def boolean!
|
40
|
+
@arity = ZERO_ARITY
|
41
|
+
end
|
42
|
+
|
43
|
+
def single!
|
44
|
+
@arity = ONE_ARITY
|
45
|
+
end
|
46
|
+
|
47
|
+
def multiple!
|
48
|
+
@arity = MANY_ARITY
|
49
|
+
end
|
50
|
+
end
|
51
|
+
end
|