choosy 0.1.0 → 0.2.0

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.
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