choosy 0.2.5 → 0.3.0

Sign up to get free protection for your applications and to get access to all the features.
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