choosy 0.1.0 → 0.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (45) hide show
  1. data/README.markdown +229 -221
  2. data/Rakefile +21 -3
  3. data/examples/bar.rb +44 -0
  4. data/examples/foo.rb +198 -0
  5. data/examples/superfoo.rb +125 -0
  6. data/lib/VERSION +1 -1
  7. data/lib/choosy/argument.rb +51 -0
  8. data/lib/choosy/base_command.rb +22 -7
  9. data/lib/choosy/command.rb +12 -4
  10. data/lib/choosy/dsl/argument_builder.rb +88 -0
  11. data/lib/choosy/dsl/base_command_builder.rb +71 -56
  12. data/lib/choosy/dsl/command_builder.rb +14 -2
  13. data/lib/choosy/dsl/option_builder.rb +43 -83
  14. data/lib/choosy/dsl/super_command_builder.rb +37 -9
  15. data/lib/choosy/option.rb +13 -11
  16. data/lib/choosy/parse_result.rb +8 -27
  17. data/lib/choosy/parser.rb +20 -16
  18. data/lib/choosy/printing/color.rb +39 -21
  19. data/lib/choosy/printing/erb_printer.rb +12 -3
  20. data/lib/choosy/printing/formatting_element.rb +17 -0
  21. data/lib/choosy/printing/help_printer.rb +204 -117
  22. data/lib/choosy/printing/terminal.rb +53 -0
  23. data/lib/choosy/super_command.rb +6 -6
  24. data/lib/choosy/super_parser.rb +26 -15
  25. data/lib/choosy/verifier.rb +61 -6
  26. data/spec/choosy/base_command_spec.rb +27 -2
  27. data/spec/choosy/command_spec.rb +31 -9
  28. data/spec/choosy/dsl/argument_builder_spec.rb +180 -0
  29. data/spec/choosy/dsl/base_command_builder_spec.rb +87 -44
  30. data/spec/choosy/dsl/commmand_builder_spec.rb +15 -4
  31. data/spec/choosy/dsl/option_builder_spec.rb +101 -191
  32. data/spec/choosy/dsl/super_command_builder_spec.rb +34 -9
  33. data/spec/choosy/parser_spec.rb +30 -8
  34. data/spec/choosy/printing/color_spec.rb +19 -5
  35. data/spec/choosy/printing/help_printer_spec.rb +152 -73
  36. data/spec/choosy/printing/terminal_spec.rb +27 -0
  37. data/spec/choosy/super_command_spec.rb +17 -17
  38. data/spec/choosy/super_parser_spec.rb +20 -10
  39. data/spec/choosy/verifier_spec.rb +137 -47
  40. data/spec/integration/command-A_spec.rb +6 -6
  41. data/spec/integration/command-B_spec.rb +45 -0
  42. data/spec/integration/supercommand-A_spec.rb +33 -27
  43. data/spec/integration/supercommand-B_spec.rb +32 -0
  44. data/spec/spec_helpers.rb +8 -5
  45. 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
- #task :rdoc => SOURCE_FILES
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
@@ -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
@@ -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
+
@@ -1 +1 @@
1
- 0.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