choosy 0.2.5 → 0.3.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 (58) hide show
  1. data/CHANGELOG.md +8 -0
  2. data/LICENSE +2 -0
  3. data/README.markdown +102 -54
  4. data/Rakefile +27 -5
  5. data/TODO.md +5 -0
  6. data/examples/bar.rb +9 -7
  7. data/examples/foo.rb +9 -7
  8. data/examples/superfoo.rb +48 -36
  9. data/lib/VERSION.yml +6 -0
  10. data/lib/choosy/argument.rb +5 -0
  11. data/lib/choosy/base_command.rb +11 -10
  12. data/lib/choosy/command.rb +17 -1
  13. data/lib/choosy/converter.rb +5 -1
  14. data/lib/choosy/dsl/argument_builder.rb +33 -21
  15. data/lib/choosy/dsl/base_builder.rb +26 -0
  16. data/lib/choosy/dsl/base_command_builder.rb +23 -30
  17. data/lib/choosy/dsl/command_builder.rb +8 -12
  18. data/lib/choosy/dsl/option_builder.rb +14 -45
  19. data/lib/choosy/dsl/super_command_builder.rb +17 -20
  20. data/lib/choosy/errors.rb +1 -0
  21. data/lib/choosy/option.rb +19 -0
  22. data/lib/choosy/printing/base_printer.rb +231 -0
  23. data/lib/choosy/printing/color.rb +8 -5
  24. data/lib/choosy/printing/erb_printer.rb +1 -1
  25. data/lib/choosy/printing/help_printer.rb +49 -163
  26. data/lib/choosy/printing/manpage.rb +235 -0
  27. data/lib/choosy/printing/manpage_printer.rb +95 -0
  28. data/lib/choosy/printing/terminal.rb +39 -8
  29. data/lib/choosy/printing.rb +1 -0
  30. data/lib/choosy/super_command.rb +13 -4
  31. data/lib/choosy/super_parser.rb +5 -1
  32. data/lib/choosy/verifier.rb +8 -0
  33. data/lib/choosy/version.rb +64 -5
  34. data/spec/choosy/argument_spec.rb +28 -0
  35. data/spec/choosy/base_command_spec.rb +7 -3
  36. data/spec/choosy/command_spec.rb +2 -2
  37. data/spec/choosy/converter_spec.rb +3 -2
  38. data/spec/choosy/dsl/argument_builder_spec.rb +19 -8
  39. data/spec/choosy/dsl/base_builder_spec.rb +43 -0
  40. data/spec/choosy/dsl/base_command_builder_spec.rb +7 -9
  41. data/spec/choosy/dsl/commmand_builder_spec.rb +9 -1
  42. data/spec/choosy/dsl/option_builder_spec.rb +1 -65
  43. data/spec/choosy/dsl/super_command_builder_spec.rb +19 -8
  44. data/spec/choosy/option_spec.rb +68 -0
  45. data/spec/choosy/printing/base_printer_spec.rb +155 -0
  46. data/spec/choosy/printing/color_spec.rb +4 -0
  47. data/spec/choosy/printing/help_printer_spec.rb +15 -109
  48. data/spec/choosy/printing/manpage_printer_spec.rb +95 -0
  49. data/spec/choosy/printing/manpage_spec.rb +206 -0
  50. data/spec/choosy/super_command_spec.rb +7 -0
  51. data/spec/choosy/super_parser_spec.rb +9 -0
  52. data/spec/choosy/verifier_spec.rb +31 -1
  53. data/spec/choosy/version.yml +5 -0
  54. data/spec/choosy/version_spec.rb +87 -0
  55. data/spec/integration/command-C_spec.rb +23 -0
  56. data/spec/integration/supercommand-C_spec.rb +45 -0
  57. metadata +81 -78
  58. data/lib/VERSION +0 -1
@@ -7,6 +7,18 @@ require 'choosy/verifier'
7
7
  module Choosy
8
8
  class Command < BaseCommand
9
9
  attr_accessor :executor, :arguments
10
+ attr_reader :parent
11
+
12
+ def initialize(name, supercommand=nil)
13
+ super(name)
14
+ self.parent = supercommand
15
+ end
16
+
17
+ def parent=(value)
18
+ @parent = value
19
+ return if @parent.nil?
20
+ raise Choosy::ConfigurationError.new("Parent must be a super command") unless Choosy::SuperCommand
21
+ end
10
22
 
11
23
  def execute!(args)
12
24
  raise Choosy::ConfigurationError.new("No executor given for: #{name}") unless executor
@@ -18,13 +30,17 @@ module Choosy
18
30
  end
19
31
  end
20
32
 
33
+ def subcommand?
34
+ !@parent.nil?
35
+ end
36
+
21
37
  protected
22
38
  def create_builder
23
39
  Choosy::DSL::CommandBuilder.new(self)
24
40
  end
25
41
 
26
42
  def handle_help(hc)
27
- puts printer.print!(self)
43
+ printer.print!(self)
28
44
  end
29
45
 
30
46
  def parse(args)
@@ -36,6 +36,10 @@ module Choosy
36
36
  end
37
37
 
38
38
  def self.convert(ty, value)
39
+ if ty.nil?
40
+ raise ArgumentError.new("Can't convert nil to a type.")
41
+ end
42
+
39
43
  if CONVERSIONS.has_key?(ty)
40
44
  send(ty, value)
41
45
  else
@@ -72,7 +76,7 @@ module Choosy
72
76
  end
73
77
 
74
78
  def self.file(value)
75
- if File.exist?(value)
79
+ if File.file?(value)
76
80
  File.new(value)
77
81
  else
78
82
  raise Choosy::ValidationError.new("Unable to locate file: '#{value}'")
@@ -1,34 +1,52 @@
1
1
  require 'choosy/errors'
2
2
  require 'choosy/argument'
3
3
  require 'choosy/converter'
4
+ require 'choosy/dsl/base_builder'
4
5
 
5
6
  module Choosy::DSL
6
7
  class ArgumentBuilder
8
+ include BaseBuilder
9
+
7
10
  def initialize
8
11
  @count_called = false
9
12
  end
10
13
 
11
- def argument
12
- @argument ||= Choosy::Argument.new
14
+ def entity
15
+ @entity ||= Choosy::Argument.new
13
16
  end
14
-
17
+
15
18
  def required(value=nil)
16
- argument.required = if value.nil? || value == true
19
+ entity.required = if value.nil? || value == true
17
20
  true
18
21
  else
19
22
  false
20
23
  end
21
24
  end
22
25
 
26
+ def only(*args)
27
+ if args.nil? || args.empty?
28
+ raise Choosy::ConfigurationError.new("'only' requires at least one argument")
29
+ end
30
+
31
+ entity.allowable_values = args
32
+ if args.length > 0 && entity.cast_to.nil?
33
+ if args[0].is_a?(Symbol)
34
+ cast :symbol
35
+ else
36
+ cast :string
37
+ end
38
+ end
39
+ end
40
+
23
41
  def metaname(meta)
24
42
  return if meta.nil?
25
- argument.metaname = meta
43
+ entity.metaname = meta
26
44
  return if @count_called
27
45
 
28
46
  if meta =~ /\+$/
29
- argument.multiple!
47
+ entity.multiple!
30
48
  else
31
- argument.single!
49
+ entity.single!
32
50
  end
33
51
  end
34
52
 
@@ -44,40 +62,34 @@ module Choosy::DSL
44
62
  raise Choosy::ConfigurationError.new("The upper bound (#{upper_bound}) is less than the lower bound (#{lower_bound}).")
45
63
  end
46
64
 
47
- argument.arity = (lower_bound .. upper_bound)
65
+ entity.arity = (lower_bound .. upper_bound)
48
66
  elsif restriction.is_a?(Range)
49
- argument.arity = restriction
67
+ entity.arity = restriction
50
68
  elsif restriction == :zero || restriction == :none
51
- argument.boolean!
69
+ entity.boolean!
52
70
  elsif restriction == :once
53
- argument.single!
71
+ entity.single!
54
72
  else
55
73
  check_count(restriction)
56
- argument.arity = (restriction .. restriction)
74
+ entity.arity = (restriction .. restriction)
57
75
  end
58
76
  end
59
77
 
60
78
  def cast(ty)
61
- argument.cast_to = Choosy::Converter.for(ty)
62
- if argument.cast_to.nil?
79
+ entity.cast_to = Choosy::Converter.for(ty)
80
+ if entity.cast_to.nil?
63
81
  raise Choosy::ConfigurationError.new("Unknown conversion cast: #{ty}")
64
82
  end
65
83
  end
66
84
 
67
85
  def validate(&block)
68
- argument.validation_step = block
86
+ entity.validation_step = block
69
87
  end
70
88
 
71
89
  def die(msg)
72
90
  raise Choosy::ValidationError.new("argument error: #{msg}")
73
91
  end
74
92
 
75
- def finalize!
76
- if argument.arity.nil?
77
- argument.boolean!
78
- end
79
- end
80
-
81
93
  protected
82
94
  def check_count(count)
83
95
  if !count.is_a?(Integer)
@@ -0,0 +1,26 @@
1
+ require 'choosy/errors'
2
+
3
+ module Choosy::DSL
4
+ # Must have entity attribute
5
+ module BaseBuilder
6
+ # Adapted from: http://www.dcmanges.com/blog/ruby-dsls-instance-eval-with-delegation
7
+ def evaluate!(&block)
8
+ if block_given?
9
+ @self_before_instance_eval = eval "self", block.binding
10
+ instance_eval(&block)
11
+ end
12
+ @self_before_instance_eval = nil
13
+
14
+ entity.finalize!
15
+ entity
16
+ end
17
+
18
+ def method_missing(method, *args, &block)
19
+ if @self_before_instance_eval
20
+ @self_before_instance_eval.send(method, *args, &block)
21
+ else
22
+ raise NoMethodError.new("undefined method '#{method}' for #{self.class.name}")
23
+ end
24
+ end
25
+ end
26
+ end
@@ -1,27 +1,33 @@
1
1
  require 'choosy/errors'
2
2
  require 'choosy/dsl/option_builder'
3
+ require 'choosy/dsl/base_builder'
4
+ require 'choosy/printing/manpage_printer'
3
5
  require 'choosy/printing/erb_printer'
4
6
  require 'choosy/printing/formatting_element'
5
7
 
6
8
  module Choosy::DSL
7
9
  class BaseCommandBuilder
8
- attr_reader :command
10
+ include BaseBuilder
11
+
12
+ attr_reader :entity
9
13
 
10
14
  def initialize(command)
11
- @command = command
15
+ @entity = command
12
16
  end
13
17
 
14
18
  # Generic setup
15
19
 
16
20
  def summary(msg)
17
- @command.summary = msg
21
+ @entity.summary = msg
18
22
  end
19
23
 
20
24
  def printer(kind, options={})
21
- @command.printer = if kind == :standard
25
+ @entity.printer = if kind == :standard
22
26
  Choosy::Printing::HelpPrinter.new(options)
23
27
  elsif kind == :erb
24
28
  Choosy::Printing::ERBPrinter.new(options)
29
+ elsif kind == :manpage
30
+ Choosy::Printing::ManpagePrinter.new(options)
25
31
  elsif kind.respond_to?(:print!)
26
32
  kind
27
33
  else
@@ -32,11 +38,11 @@ module Choosy::DSL
32
38
  # Formatting
33
39
 
34
40
  def header(msg, *styles)
35
- @command.listing << Choosy::Printing::FormattingElement.new(:header, msg, styles)
41
+ @entity.listing << Choosy::Printing::FormattingElement.new(:header, msg, styles)
36
42
  end
37
43
 
38
44
  def para(msg=nil, *styles)
39
- @command.listing << Choosy::Printing::FormattingElement.new(:para, msg, styles)
45
+ @entity.listing << Choosy::Printing::FormattingElement.new(:para, msg, styles)
40
46
  end
41
47
 
42
48
  # Options
@@ -64,10 +70,7 @@ module Choosy::DSL
64
70
  raise Choosy::ConfigurationError.new("No configuration block was given") if !block_given?
65
71
  end
66
72
 
67
- if block_given?
68
- builder.instance_eval(&block)
69
- end
70
- finalize_option_builder builder
73
+ evaluate_option_builder!(builder, &block)
71
74
  end
72
75
 
73
76
  def self.create_conversions
@@ -123,30 +126,23 @@ module Choosy::DSL
123
126
  v = OptionBuilder.new(OptionBuilder::VERSION)
124
127
  v.long '--version'
125
128
  v.desc "The version number"
129
+ # TODO: research how this should be refactored, used with manpage
130
+ # v.default msg
131
+ v.cast :boolean
126
132
 
127
133
  v.validate do
128
134
  raise Choosy::VersionCalled.new(msg)
129
135
  end
130
136
 
131
- if block_given?
132
- v.instance_eval(&block)
133
- end
134
- finalize_option_builder v
135
- end
136
-
137
- def finalize!
138
- if @command.printer.nil?
139
- printer :standard
140
- end
137
+ evaluate_option_builder!(v, &block)
141
138
  end
142
139
 
143
140
  protected
144
- def finalize_option_builder(option_builder)
145
- option_builder.finalize!
146
- @command.option_builders[option_builder.option.name] = option_builder
147
- @command.listing << option_builder.option
148
-
149
- option_builder.option
141
+ def evaluate_option_builder!(builder, &block)
142
+ builder.evaluate!(&block)
143
+ @entity.listing << builder.entity
144
+ @entity.option_builders[builder.entity.name] = builder
145
+ builder.entity
150
146
  end
151
147
 
152
148
  private
@@ -170,10 +166,7 @@ module Choosy::DSL
170
166
  end
171
167
  builder.from_hash config if config
172
168
 
173
- if block_given?
174
- builder.instance_eval(&block)
175
- end
176
- finalize_option_builder builder
169
+ evaluate_option_builder!(builder, &block)
177
170
  end
178
171
 
179
172
  def format_meta(name, count)
@@ -9,7 +9,7 @@ module Choosy::DSL
9
9
  def executor(exec=nil, &block)
10
10
  if exec.nil?
11
11
  if block_given?
12
- @command.executor = block
12
+ @entity.executor = block
13
13
  else
14
14
  raise Choosy::ConfigurationError.new("The executor was nil")
15
15
  end
@@ -17,11 +17,11 @@ module Choosy::DSL
17
17
  if !exec.respond_to?(:execute!)
18
18
  raise Choosy::ConfigurationError.new("Execution class doesn't implement 'execute!'")
19
19
  end
20
- @command.executor = exec
20
+ @entity.executor = exec
21
21
  end
22
22
  end
23
23
 
24
- def help(msg=nil)
24
+ def help(msg=nil, &block)
25
25
  h = OptionBuilder.new(OptionBuilder::HELP)
26
26
  h.short '-h'
27
27
  h.long '--help'
@@ -32,24 +32,20 @@ module Choosy::DSL
32
32
  raise Choosy::HelpCalled.new(@name)
33
33
  end
34
34
 
35
- finalize_option_builder h
35
+ evaluate_option_builder!(h, &block)
36
36
  end
37
37
 
38
38
  def arguments(&block)
39
39
  builder = ArgumentBuilder.new
40
40
  # Set multiple by default
41
- builder.argument.multiple!
42
-
43
- if block_given?
44
- builder.instance_eval(&block)
45
- end
41
+ builder.entity.multiple!
42
+ builder.evaluate!(&block)
46
43
 
47
- builder.finalize!
48
- if builder.argument.metaname.nil?
44
+ if builder.entity.metaname.nil?
49
45
  builder.metaname 'ARGS+'
50
46
  end
51
47
 
52
- command.arguments = builder.argument
48
+ entity.arguments = builder.entity
53
49
  end
54
50
  end
55
51
  end
@@ -13,19 +13,17 @@ module Choosy::DSL
13
13
  @name = name
14
14
  end
15
15
 
16
- def option
17
- @argument ||= Choosy::Option.new(@name)
16
+ def entity
17
+ @entity ||= Choosy::Option.new(@name)
18
18
  end
19
19
 
20
- alias :argument :option
21
-
22
20
  def short(flag, meta=nil)
23
- option.short_flag = flag
21
+ entity.short_flag = flag
24
22
  metaname(meta)
25
23
  end
26
24
 
27
25
  def long(flag, meta=nil)
28
- option.long_flag = flag
26
+ entity.long_flag = flag
29
27
  metaname(meta)
30
28
  end
31
29
 
@@ -36,37 +34,33 @@ module Choosy::DSL
36
34
  end
37
35
 
38
36
  def desc(description)
39
- option.description = description
37
+ entity.description = description
40
38
  end
41
39
 
42
40
  def default(value)
43
- option.default_value = value
41
+ entity.default_value = value
44
42
  end
45
43
 
46
44
  def depends_on(*args)
47
45
  if args.length == 1 && args[0].is_a?(Array)
48
- option.dependent_options = args[0]
46
+ entity.dependent_options = args[0]
49
47
  else
50
- option.dependent_options = args
48
+ entity.dependent_options = args
51
49
  end
52
50
  end
53
51
 
54
- def only(*args)
55
- option.allowable_values = args
56
- end
57
-
58
52
  def negate(prefix=nil)
59
53
  prefix ||= 'no'
60
- option.negation = prefix
54
+ entity.negation = prefix
61
55
  end
62
56
 
63
57
  def die(msg)
64
- flag_fmt = if option.short_flag && option.long_flag
65
- "#{option.short_flag}/#{option.long_flag}"
58
+ flag_fmt = if entity.short_flag && entity.long_flag
59
+ "#{entity.short_flag}/#{entity.long_flag}"
66
60
  end
67
- flag_fmt ||= option.short_flag || option.long_flag
68
- flag_meta = if option.metaname
69
- " #{option.metaname}"
61
+ flag_fmt ||= entity.short_flag || entity.long_flag
62
+ flag_meta = if entity.metaname
63
+ " #{entity.metaname}"
70
64
  end
71
65
  raise Choosy::ValidationError.new("#{flag_fmt}#{flag_meta}: #{msg}")
72
66
  end
@@ -86,30 +80,5 @@ module Choosy::DSL
86
80
  end
87
81
  end
88
82
  end
89
-
90
- def finalize!
91
- super
92
-
93
- if option.cast_to.nil?
94
- if option.boolean?
95
- option.cast_to = :boolean
96
- else
97
- option.cast_to = :string
98
- end
99
- end
100
-
101
- if option.boolean?
102
- if option.restricted?
103
- raise Choosy::ConfigurationError.new("Options cannot be both boolean and restricted to certain arguments: #{option.name}")
104
- elsif option.negated? && option.long_flag.nil?
105
- raise Choosy::ConfigurationError.new("The long flag is required for negation: #{option.name}")
106
- end
107
- option.default_value ||= false
108
- else
109
- if option.negated?
110
- raise Choosy::ConfigurationError.new("Unable to negate a non-boolean option: #{option.name}")
111
- end
112
- end
113
- end
114
83
  end
115
84
  end
@@ -9,26 +9,28 @@ module Choosy::DSL
9
9
 
10
10
  def command(cmd, &block)
11
11
  subcommand = if cmd.is_a?(Choosy::Command)
12
+ cmd.parent = entity
12
13
  cmd
13
14
  else
14
- Choosy::Command.new(cmd)
15
+ Choosy::Command.new(cmd, entity)
15
16
  end
16
17
 
17
- if block_given?
18
- subcommand.builder.instance_eval(&block)
19
- end
20
- finalize_subcommand(subcommand)
18
+ evaluate_command_builder!(subcommand.builder, &block)
21
19
  end
22
20
 
23
21
  def parsimonious
24
- @command.parsimonious = true
22
+ @entity.parsimonious = true
25
23
  end
26
24
 
27
25
  def metaname(meta)
28
- @command.metaname = meta
26
+ @entity.metaname = meta
27
+ end
28
+
29
+ def default(cmd)
30
+ @entity.default_command = cmd
29
31
  end
30
32
 
31
- def help(msg=nil)
33
+ def help(msg=nil, &block)
32
34
  msg ||= "Show the info for a command, or this message"
33
35
  help_command = Choosy::Command.new HELP do |help|
34
36
  help.summary msg
@@ -50,20 +52,15 @@ module Choosy::DSL
50
52
  end
51
53
  end
52
54
  end
53
- finalize_subcommand(help_command)
54
- end
55
-
56
- def finalize!
57
- super
58
- @command.metaname ||= 'COMMAND'
55
+ evaluate_command_builder!(help_command.builder, &block)
59
56
  end
60
57
 
61
- private
62
- def finalize_subcommand(subcommand)
63
- subcommand.builder.finalize!
64
- @command.command_builders[subcommand.name] = subcommand.builder
65
- @command.listing << subcommand
66
- subcommand
58
+ protected
59
+ def evaluate_command_builder!(builder, &block)
60
+ builder.evaluate!(&block)
61
+ @entity.listing << builder.entity
62
+ @entity.command_builders[builder.entity.name] = builder
63
+ builder.entity
67
64
  end
68
65
  end
69
66
  end
data/lib/choosy/errors.rb CHANGED
@@ -8,4 +8,5 @@ module Choosy
8
8
  ConversionError = Class.new(Choosy::Error)
9
9
  ParseError = Class.new(Choosy::Error)
10
10
  SuperParseError = Class.new(Choosy::Error)
11
+ VersionError = Class.new(Choosy::Error)
11
12
  end
data/lib/choosy/option.rb CHANGED
@@ -1,3 +1,4 @@
1
+ require 'choosy/errors'
1
2
  require 'choosy/argument'
2
3
 
3
4
  module Choosy
@@ -20,5 +21,23 @@ module Choosy
20
21
  def negated
21
22
  @negated ||= long_flag.gsub(/^--/, "--#{negation}-")
22
23
  end
24
+
25
+ def finalize!
26
+ @arity ||= ZERO_ARITY
27
+ @cast_to ||= boolean? ? :boolean : :string
28
+
29
+ if boolean?
30
+ if restricted?
31
+ raise Choosy::ConfigurationError.new("Options cannot be both boolean and restricted to certain arguments: #{@name}")
32
+ elsif negated? && @long_flag.nil?
33
+ raise Choosy::ConfigurationError.new("The long flag is required for negation: #{@name}")
34
+ end
35
+ @default_value ||= false
36
+ else
37
+ if negated?
38
+ raise Choosy::ConfigurationError.new("Unable to negate a non-boolean option: #{@name}")
39
+ end
40
+ end
41
+ end
23
42
  end
24
43
  end