slop 2.4.4 → 3.0.0.rc1

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