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.
- data/CHANGES.md +0 -15
- data/README.md +46 -206
- data/lib/slop.rb +399 -888
- data/lib/slop/commands.rb +154 -0
- data/lib/slop/option.rb +149 -0
- data/slop.gemspec +1 -1
- data/test/commands_test.rb +58 -119
- data/test/option_test.rb +65 -159
- data/test/slop_test.rb +174 -497
- metadata +7 -5
@@ -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
|
data/lib/slop/option.rb
ADDED
@@ -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
|
data/slop.gemspec
CHANGED
data/test/commands_test.rb
CHANGED
@@ -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
|
-
|
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
|
-
|
17
|
-
|
18
|
-
|
12
|
+
on 'version' do
|
13
|
+
add_callback(:empty) { 'version 1' }
|
14
|
+
end
|
15
|
+
end
|
19
16
|
end
|
20
17
|
|
21
|
-
test
|
22
|
-
|
23
|
-
|
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
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
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
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
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
|
42
|
-
|
43
|
-
|
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
|
50
|
-
|
51
|
-
|
52
|
-
|
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
|
59
|
-
|
60
|
-
|
61
|
-
|
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
|
74
|
-
|
75
|
-
|
76
|
-
|
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
|
91
|
-
|
92
|
-
|
93
|
-
|
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
|
106
|
-
|
107
|
-
|
108
|
-
|
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 '
|
119
|
-
|
120
|
-
|
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
|
-
|
124
|
-
|
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
|
-
|
80
|
+
items = %w( new --force )
|
81
|
+
assert_equal items, @commands.parse(items)
|
127
82
|
end
|
128
83
|
|
129
|
-
test
|
130
|
-
|
131
|
-
|
132
|
-
|
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
|