slop 2.4.4 → 3.0.0.rc1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,154 @@
1
+ class Slop
2
+ class Commands
3
+ include Enumerable
4
+
5
+ attr_reader :config, :commands
6
+ attr_writer :banner
7
+
8
+ # Create a new instance of Slop::Commands and optionally build
9
+ # Slop instances via a block. Any configuration options used in
10
+ # this method will be the default configuration options sent to
11
+ # each Slop object created.
12
+ #
13
+ # config - An optional configuration Hash.
14
+ # block - Optional block used to define commands.
15
+ def initialize(config = {}, &block)
16
+ @config = config
17
+ @commands = {}
18
+ @banner = nil
19
+
20
+ if block_given?
21
+ block.arity == 1 ? yield(self) : instance_eval(&block)
22
+ end
23
+ end
24
+
25
+ # Optionally set the banner for this command help output.
26
+ #
27
+ # banner - The String text to set the banner.
28
+ #
29
+ # Returns the String banner if one is set.
30
+ def banner(banner = nil)
31
+ @banner = banner if banner
32
+ @banner
33
+ end
34
+
35
+ # Add a Slop instance for a specific command.
36
+ #
37
+ # command - A String or Symbol key used to identify this command.
38
+ # config - A Hash of configuration options to pass to Slop.
39
+ # block - An optional block used to pass options to Slop.
40
+ #
41
+ # Returns the newly created Slop instance mapped to command.
42
+ def on(command, config = {}, &block)
43
+ commands[command] = Slop.new(@config.merge(config), &block)
44
+ end
45
+
46
+ # Add a Slop instance used when no other commands exist.
47
+ #
48
+ # config - A Hash of configuration options to pass to Slop.
49
+ # block - An optional block used to pass options to Slop.
50
+ #
51
+ # Returns the newly created Slop instance mapped to default.
52
+ def default(config = {}, &block)
53
+ commands['default'] = Slop.new(@config.merge(config), &block)
54
+ end
55
+
56
+ # Add a global Slop instance.
57
+ #
58
+ # config - A Hash of configuration options to pass to Slop.
59
+ # block - An optional block used to pass options to Slop.
60
+ #
61
+ # Returns the newly created Slop instance mapped to global.
62
+ def global(config = {}, &block)
63
+ commands['global'] = Slop.new(@config.merge(config), &block)
64
+ end
65
+
66
+ # Fetch the instance of Slop tied to a command.
67
+ #
68
+ # key - The String or Symbol key used to locate this command.
69
+ #
70
+ # Returns the Slop instance if this key is found, nil otherwise.
71
+ def [](key)
72
+ commands[key.to_s]
73
+ end
74
+ alias get []
75
+
76
+ # Parse a list of items.
77
+ #
78
+ # items - The Array of items to parse.
79
+ #
80
+ # Returns the original Array of items.
81
+ def parse(items = ARGV)
82
+ parse_items(items)
83
+ end
84
+
85
+ # Enumerable interface.
86
+ def each(&block)
87
+ @commands.each(&block)
88
+ end
89
+
90
+ # Parse a list of items, removing any options or option arguments found.
91
+ #
92
+ # items - The Array of items to parse.
93
+ #
94
+ # Returns the original Array of items with options removed.
95
+ def parse!(items = ARGV)
96
+ parse_items(items, true)
97
+ end
98
+
99
+ # Returns a nested Hash with Slop options and values. See Slop#to_hash.
100
+ def to_hash
101
+ Hash[commands.map { |k, v| [k.to_sym, v.to_hash] }]
102
+ end
103
+
104
+ # Returns the help String.
105
+ def to_s
106
+ defaults = commands.delete('default')
107
+ globals = commands.delete('global')
108
+ helps = commands.reject { |_, v| v.options.none? }
109
+ helps.merge!('Global options' => globals.to_s) if globals
110
+ helps.merge!('Other options' => defaults.to_s) if defaults
111
+ banner = @banner ? "#{@banner}\n" : ""
112
+ banner + helps.map { |key, opts| " #{key}\n#{opts}" }.join("\n\n")
113
+ end
114
+ alias help to_s
115
+
116
+ # Returns the inspection String.
117
+ def inspect
118
+ "#<Slop::Commands #{config.inspect} #{commands.values.map(&:inspect)}>"
119
+ end
120
+
121
+ private
122
+
123
+ # Parse a list of items.
124
+ #
125
+ # items - The Array of items to parse.
126
+ # bang - When true, #parse! will be called instead of #parse.
127
+ #
128
+ # Returns the Array of items (with options removed if bang == true).
129
+ def parse_items(items, bang = false)
130
+ if opts = commands[items[0].to_s]
131
+ items.shift
132
+ bang ? opts.parse!(items) : opts.parse(items)
133
+ execute_global_opts(items, bang)
134
+ else
135
+ if opts = commands['default']
136
+ bang ? opts.parse!(items) : opts.parse(items)
137
+ else
138
+ if config[:strict] && items[0]
139
+ raise InvalidCommandError, "Unknown command `#{items[0]}`"
140
+ end
141
+ end
142
+ execute_global_opts(items, bang)
143
+ end
144
+ items
145
+ end
146
+
147
+ def execute_global_opts(items, bang)
148
+ if global_opts = commands['global']
149
+ bang ? global_opts.parse!(items) : global_opts.parse(items)
150
+ end
151
+ end
152
+
153
+ end
154
+ end
@@ -0,0 +1,149 @@
1
+ class Slop
2
+ class Option
3
+
4
+ # The default Hash of configuration options this class uses.
5
+ DEFAULT_OPTIONS = {
6
+ :argument => false,
7
+ :optional_argument => false,
8
+ :tail => false,
9
+ :default => nil,
10
+ :callback => nil,
11
+ :delimiter => ',',
12
+ :limit => 0,
13
+ :match => nil,
14
+ :optional => true,
15
+ :required => false,
16
+ :as => String,
17
+ :autocreated => false
18
+ }
19
+
20
+ attr_reader :short, :long, :description, :config, :types
21
+ attr_accessor :count
22
+ attr_writer :value
23
+
24
+ # Incapsulate internal option information, mainly used to store
25
+ # option specific configuration data, most of the meat of this
26
+ # class is found in the #value method.
27
+ #
28
+ # slop - The instance of Slop tied to this Option.
29
+ # short - The String or Symbol short flag.
30
+ # long - The String or Symbol long flag.
31
+ # description - The String description text.
32
+ # config - A Hash of configuration options.
33
+ # block - An optional block used as a callback.
34
+ def initialize(slop, short, long, description, config = {}, &block)
35
+ @slop = slop
36
+ @short = short
37
+ @long = long
38
+ @description = description
39
+ @config = DEFAULT_OPTIONS.merge(config)
40
+ @count = 0
41
+ @callback = block_given? ? block : config[:callback]
42
+ @value = nil
43
+
44
+ @types = {
45
+ :string => proc { |v| v.to_s },
46
+ :symbol => proc { |v| v.to_sym },
47
+ :integer => proc { |v| v.to_s.to_i },
48
+ :float => proc { |v| v.to_f },
49
+ :array => proc { |v| v.split(@config[:delimiter], @config[:limit]) },
50
+ :range => proc { |v| value_to_range(v) }
51
+ }
52
+
53
+ if long && long.size > @slop.config[:longest_flag]
54
+ @slop.config[:longest_flag] = long.size
55
+ end
56
+
57
+ @config.each_key do |key|
58
+ self.class.send(:define_method, "#{key}?") { !!@config[key] }
59
+ end
60
+ end
61
+
62
+ # Returns true if this option expects an argument.
63
+ def expects_argument?
64
+ config[:argument] && config[:argument] != :optional
65
+ end
66
+
67
+ # Returns true if this option accepts an optional argument.
68
+ def accepts_optional_argument?
69
+ config[:optional_argument] || config[:argument] == :optional
70
+ end
71
+
72
+ # Returns the String flag of this option. Preferring the long flag.
73
+ def key
74
+ long || short
75
+ end
76
+
77
+ # Call this options callback if one exists, and it responds to call().
78
+ #
79
+ # Returns nothing.
80
+ def call(*objects)
81
+ @callback.call(*objects) if @callback.respond_to?(:call)
82
+ end
83
+
84
+ # Fetch the argument value for this option.
85
+ #
86
+ # Returns the Object once any type conversions have taken place.
87
+ def value
88
+ value = @value || config[:default]
89
+ return if value.nil?
90
+
91
+ type = config[:as]
92
+ if type.respond_to?(:call)
93
+ type.call(value)
94
+ else
95
+ if callable = types[type.to_s.downcase.to_sym]
96
+ callable.call(value)
97
+ else
98
+ value
99
+ end
100
+ end
101
+ end
102
+
103
+ # Returns the help String for this option.
104
+ def to_s
105
+ return config[:help] if config[:help].respond_to?(:to_str)
106
+
107
+ out = " "
108
+ out += short ? "-#{short}, " : ' ' * 4
109
+
110
+ if long
111
+ out += "--#{long}"
112
+ size = long.size
113
+ diff = @slop.config[:longest_flag] - size
114
+ out += " " * (diff + 6)
115
+ else
116
+ out += " " * (@slop.config[:longest_flag] + 8)
117
+ end
118
+
119
+ "#{out}#{description}"
120
+ end
121
+ alias help to_s
122
+
123
+ # Returns the String inspection text.
124
+ def inspect
125
+ "#<Slop::Option [-#{short} | --#{long}" +
126
+ "#{'=' if expects_argument?}#{'=?' if accepts_optional_argument?}]" +
127
+ " (#{description}) #{config.inspect}"
128
+ end
129
+
130
+ private
131
+
132
+ # Convert an object to a Range if possible. If this method is passed
133
+ # what does *not* look like a Range, but looks like an Integer of some
134
+ # sort, it will call #to_i on the Object and return the Integer
135
+ # representation.
136
+ #
137
+ # value - The Object we want to convert to a range.
138
+ #
139
+ # Returns the Range value if one could be found, else the original object.
140
+ def value_to_range(value)
141
+ if value.to_s =~ /\A(?:\-?\d+|(-?\d+?)(\.\.\.?|-|,)(-?\d+))\z/
142
+ $1 ? Range.new($1.to_i, $3.to_i, $2 == '...') : value.to_i
143
+ else
144
+ value
145
+ end
146
+ end
147
+
148
+ end
149
+ end
@@ -1,6 +1,6 @@
1
1
  Gem::Specification.new do |s|
2
2
  s.name = 'slop'
3
- s.version = '2.4.4'
3
+ s.version = '3.0.0.rc1'
4
4
  s.summary = 'Option gathering made easy'
5
5
  s.description = 'A simple DSL for gathering options and parsing the command line'
6
6
  s.author = 'Lee Jarvis'
@@ -1,151 +1,90 @@
1
1
  require 'helper'
2
2
 
3
3
  class CommandsTest < TestCase
4
- test 'creating commands' do
5
- slop = Slop.new do
6
- command :foo do on :f, :foo, 'foo option' end
7
- command :bar do on :f, :foo; on :b, :bar, true end
8
- end
9
-
10
- slop.commands.each_value do |command|
11
- assert_kind_of Slop, command
12
- end
13
4
 
14
- assert 'foo option', slop.commands[:foo].options[:foo].description
5
+ def setup
6
+ @commands = Slop::Commands.new do
7
+ on 'new' do
8
+ on '--force', 'Force creation'
9
+ on '--outdir=', 'Output directory'
10
+ end
15
11
 
16
- slop.parse %w/bar --bar baz/
17
- assert 'baz', slop.commands[:bar][:bar]
18
- assert_nil slop.commands['bar']
12
+ on 'version' do
13
+ add_callback(:empty) { 'version 1' }
14
+ end
15
+ end
19
16
  end
20
17
 
21
- test 'repeating existing commands' do
22
- slop = Slop.new
23
- assert slop.command :foo
24
- assert_raises(ArgumentError) { slop.command :foo }
18
+ test "it nests instances of Slop" do
19
+ assert_empty Slop::Commands.new.commands
20
+ @commands.commands.each_value { |k| assert_kind_of Slop, k }
25
21
  end
26
22
 
27
- test 'commands inheriting options' do
28
- slop = Slop.new :strict do
29
- command :foo do end
30
- end
31
- assert slop.commands[:foo].instance_variable_get(:@strict)
23
+ test "accessing Slop instances via get/[]" do
24
+ assert_kind_of Slop, @commands['new']
25
+ assert_kind_of Slop, @commands[:new]
26
+ assert_nil @commands[:unknown]
27
+ assert_equal 'Force creation', @commands[:new].fetch_option(:force).description
32
28
  end
33
29
 
34
- test 'commands setting options' do
35
- slop = Slop.new :strict => false do
36
- command :foo, :strict => true do end
37
- end
38
- assert slop.commands[:foo].instance_variable_get(:@strict)
30
+ test "to_hash" do
31
+ assert_equal({
32
+ :new => { :force => nil, :outdir => nil },
33
+ :version => {}
34
+ }, @commands.to_hash)
39
35
  end
40
36
 
41
- test 'inception' do
42
- slop = Slop.new do
43
- command(:foo) { command(:bar) { command(:baz) { on :f, 'D:' } } }
44
- end
45
- desc = slop.commands[:foo].commands[:bar].commands[:baz].options[:f].description
46
- assert_equal 'D:', desc
37
+ test "raising on unknown commands with :strict => true" do
38
+ cmds = Slop::Commands.new(:strict => true)
39
+ assert_raises(Slop::InvalidCommandError) { cmds.parse %w( abc ) }
47
40
  end
48
41
 
49
- test 'commands with banners' do
50
- slop = Slop.new do
51
- command(:foo, :banner => 'bar') { }
52
- command(:bar) { banner 'bar' }
53
- end
54
- assert_equal 'bar', slop.commands[:foo].banner
55
- assert_equal 'bar', slop.commands[:bar].banner
42
+ test "adding global options" do
43
+ cmds = Slop::Commands.new { global { on '--verbose' } }
44
+ cmds.parse %w( --verbose )
45
+ assert cmds[:global].verbose?
56
46
  end
57
47
 
58
- test 'executing on_empty on separate commands' do
59
- incmd = inslop = false
60
- slop = Slop.new do
61
- command(:foo) { on(:bar) {}; on_empty { incmd = true }}
62
- on_empty { inslop = true }
63
- end
64
- slop.parse %w//
65
- assert inslop
66
- refute incmd
67
- inslop = false
68
- slop.parse %w/foo/
69
- assert incmd
70
- refute inslop
48
+ test "global options are always executed" do
49
+ @commands.global { on 'foo=' }
50
+ @commands.parse %w( new --force --foo bar )
51
+ assert_equal 'bar', @commands[:global][:foo]
71
52
  end
72
53
 
73
- test 'executing blocks' do
74
- foo = bar = nil
75
- slop = Slop.new
76
- slop.command :foo do
77
- on :v, :verbose
78
- execute { |o, a| foo = o.verbose? }
79
- end
80
- slop.command :bar do
81
- on :v, :verbose
82
- execute { |o, a| bar = o.verbose? }
83
- end
84
- slop.parse %w[ foo --verbose ]
85
-
86
- assert foo
87
- refute bar
54
+ test "default options are only executed when there's nothing else" do
55
+ @commands.default { on 'foo=' }
56
+ @commands.parse %w( new --force --foo bar )
57
+ assert_nil @commands[:default][:foo]
88
58
  end
89
59
 
90
- test 'executing blocks and command arguments' do
91
- opts = args = nil
92
- slop = Slop.new
93
- slop.command :foo do
94
- execute do |o, a|
95
- opts = o
96
- args = a
97
- end
98
- end
99
- slop.parse %w[ foo bar baz ]
100
-
101
- assert_equal %w[ bar baz ], args
102
- assert_kind_of Slop, opts
60
+ test "adding default options" do
61
+ cmds = Slop::Commands.new { default { on '--verbose' } }
62
+ cmds.parse %w( --verbose )
63
+ assert cmds[:default].verbose?
103
64
  end
104
65
 
105
- test 'executing nested commands' do
106
- args = nil
107
- slop = Slop.new
108
- slop.command :foo do
109
- command :bar do
110
- execute { |o, a| args = a }
111
- end
112
- end
113
- slop.parse %w[ foo bar baz ]
114
-
115
- assert_equal %w[ baz ], args
66
+ test "on/global and default all return newly created slop instances" do
67
+ assert_kind_of Slop, @commands.on('foo')
68
+ assert_kind_of Slop, @commands.default
69
+ assert_kind_of Slop, @commands.global
116
70
  end
117
71
 
118
- test 'aliases' do
119
- slop = Slop.new
120
- slop.command :foo, :alias => :bar
121
- slop.command :baz, :aliases => [:lorem, :ipsum]
72
+ test "parse does nothing when there's nothing to parse" do
73
+ assert @commands.parse []
74
+ end
122
75
 
123
- assert_equal [:bar], slop.commands[:foo].aliases
124
- assert_equal [:lorem, :ipsum], slop.commands[:baz].aliases
76
+ test "parse returns the original array of items" do
77
+ items = %w( foo bar baz )
78
+ assert_equal items, @commands.parse(items)
125
79
 
126
- assert_raises(ArgumentError) { slop.command :lorem }
80
+ items = %w( new --force )
81
+ assert_equal items, @commands.parse(items)
127
82
  end
128
83
 
129
- test 'command completion' do
130
- foo = bar = bara = nil
131
- slop = Slop.new
132
- slop.command(:foo) { execute { foo = true } }
133
- slop.parse %w[ fo ]
134
- assert foo
135
-
136
- slop.command(:bar) { execute { bar = true } }
137
- slop.command(:bara) { execute { bara = true } }
138
- slop.parse %w[ bar ]
139
- assert bar
140
- refute bara
84
+ test "parse! removes options/arguments" do
85
+ items = %w( new --outdir foo )
86
+ @commands.parse!(items)
87
+ assert_equal [], items
141
88
  end
142
89
 
143
- test 'ambiguous command completion' do
144
- io = StringIO.new
145
- slop = Slop.new(:io => io)
146
- slop.command :bar
147
- slop.command :baz
148
- slop.parse %w[ ba ]
149
- assert_equal "Command 'ba' is ambiguous:\n bar, baz\n", io.string
150
- end
151
90
  end