slop 3.6.0 → 4.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,111 @@
1
+ module Slop
2
+ class Parser
3
+
4
+ # Our Options instance.
5
+ attr_reader :options
6
+
7
+ # A Hash of configuration options.
8
+ attr_reader :config
9
+
10
+ # Returns an Array of String arguments that were not parsed.
11
+ attr_reader :arguments
12
+
13
+ def initialize(options, **config)
14
+ @options = options
15
+ @config = config
16
+ reset
17
+ end
18
+
19
+ # Reset the parser, useful to use the same instance to parse a second
20
+ # time without duplicating state.
21
+ def reset
22
+ @arguments = []
23
+ @options.each(&:reset)
24
+ self
25
+ end
26
+
27
+ # Traverse `strings` and process options one by one. Anything after
28
+ # `--` is ignored. If a flag includes a equals (=) it will be split
29
+ # so that `flag, argument = s.split('=')`.
30
+ #
31
+ # The `call` method will be executed immediately for each option found.
32
+ # Once all options have been executed, any found options will have
33
+ # the `finish` method called on them.
34
+ #
35
+ # Returns a Slop::Result.
36
+ def parse(strings)
37
+ pairs = strings.each_cons(2).to_a
38
+ # this ensures we still support the last string being a flag,
39
+ # otherwise it'll only be used as an argument.
40
+ pairs << [strings.last, nil]
41
+
42
+ @arguments = strings.dup
43
+
44
+ pairs.each do |flag, arg|
45
+ # ignore everything after '--', flag or not
46
+ break if !flag || flag == '--'
47
+
48
+ # support `foo=bar`
49
+ if flag.include?("=")
50
+ flag, arg = flag.split("=")
51
+ end
52
+
53
+ if opt = try_process(flag, arg)
54
+ # since the option was parsed, we remove it from our
55
+ # arguments (plus the arg if necessary)
56
+ arguments.delete(flag)
57
+ arguments.delete(arg) if opt.expects_argument?
58
+ end
59
+ end
60
+
61
+ Result.new(self).tap do |result|
62
+ used_options.each { |o| o.finish(result) }
63
+ end
64
+ end
65
+
66
+ # Returns an Array of Option instances that were used.
67
+ def used_options
68
+ options.select { |o| o.count > 0 }
69
+ end
70
+
71
+ # Returns an Array of Option instances that were not used.
72
+ def unused_options
73
+ options.to_a - used_options
74
+ end
75
+
76
+ private
77
+
78
+ # We've found an option, process and return it
79
+ def process(option, arg)
80
+ option.ensure_call(arg)
81
+ option
82
+ end
83
+
84
+ # Try and find an option to process
85
+ def try_process(flag, arg)
86
+ if option = matching_option(flag)
87
+ process(option, arg)
88
+ elsif flag =~ /\A-[^-]/ && flag.size > 2
89
+ # try and process as a set of grouped short flags. drop(1) removes
90
+ # the prefixed -, then we add them back to each flag separately.
91
+ flags = flag.split("").drop(1).map { |f| "-#{f}" }
92
+ last = flags.pop
93
+
94
+ flags.each { |f| try_process(f, nil) }
95
+ try_process(last, arg) # send the argument to the last flag
96
+ else
97
+ if flag.start_with?("-") && !suppress_errors?
98
+ raise UnknownOption, "unknown option `#{flag}'"
99
+ end
100
+ end
101
+ end
102
+
103
+ def suppress_errors?
104
+ config[:suppress_errors]
105
+ end
106
+
107
+ def matching_option(flag)
108
+ options.find { |o| o.flags.include?(flag) }
109
+ end
110
+ end
111
+ end
@@ -0,0 +1,79 @@
1
+ module Slop
2
+ # This class encapsulates a Parser and Options pair. The idea is that
3
+ # the Options class shouldn't have to deal with what happens when options
4
+ # are parsed, and the Parser shouldn't have to deal with the state of
5
+ # options once parsing is complete. This keeps the API really simple; A
6
+ # Parser parses, Options handles options, and this class handles the
7
+ # result of those actions. This class contains the important most used
8
+ # methods.
9
+ class Result
10
+ attr_reader :parser, :options
11
+
12
+ def initialize(parser)
13
+ @parser = parser
14
+ @options = parser.options
15
+ end
16
+
17
+ # Returns an options value, nil if the option does not exist.
18
+ def [](flag)
19
+ (o = option(flag)) && o.value
20
+ end
21
+ alias get []
22
+
23
+ # Returns an Option if it exists. Ignores any prefixed hyphens.
24
+ def option(flag)
25
+ cleaned = -> (f) { f.to_s.sub(/\A--?/, '') }
26
+ options.find do |o|
27
+ o.flags.any? { |f| cleaned.(f) == cleaned.(flag) }
28
+ end
29
+ end
30
+
31
+ def method_missing(name, *args, &block)
32
+ if respond_to_missing?(name)
33
+ (o = option(name.to_s.chomp("?"))) && used_options.include?(o)
34
+ else
35
+ super
36
+ end
37
+ end
38
+
39
+ def respond_to_missing?(name, include_private = false)
40
+ name.to_s.end_with?("?") || super
41
+ end
42
+
43
+ # Returns an Array of Option instances that were used.
44
+ def used_options
45
+ parser.used_options
46
+ end
47
+
48
+ # Returns an Array of Option instances that were not used.
49
+ def unused_options
50
+ parser.unused_options
51
+ end
52
+
53
+ # Example:
54
+ #
55
+ # opts = Slop.parse do |o|
56
+ # o.string '--host'
57
+ # o.int '-p'
58
+ # end
59
+ #
60
+ # # ruby run.rb connect --host 123 helo
61
+ # opts.arguments #=> ["connect", "helo"]
62
+ #
63
+ # Returns an Array of String arguments that were not parsed.
64
+ def arguments
65
+ parser.arguments
66
+ end
67
+ alias args arguments
68
+
69
+ # Returns a hash with option key => value.
70
+ def to_hash
71
+ Hash[options.reject(&:null?).map { |o| [o.key, o.value] }]
72
+ end
73
+ alias to_h to_hash
74
+
75
+ def to_s(**opts)
76
+ options.to_s(**opts)
77
+ end
78
+ end
79
+ end
@@ -0,0 +1,52 @@
1
+ module Slop
2
+ class StringOption < Option
3
+ def call(value)
4
+ value.to_s
5
+ end
6
+ end
7
+
8
+ class BoolOption < Option
9
+ def call(_value)
10
+ true
11
+ end
12
+
13
+ def default_value
14
+ config[:default] || false
15
+ end
16
+
17
+ def expects_argument?
18
+ false
19
+ end
20
+ end
21
+ BooleanOption = BoolOption
22
+
23
+ class IntegerOption < Option
24
+ def call(value)
25
+ value =~ /\A\d+\z/ && value.to_i
26
+ end
27
+ end
28
+ IntOption = IntegerOption
29
+
30
+ class ArrayOption < Option
31
+ def call(value)
32
+ @value ||= []
33
+ @value.concat value.split(delimiter)
34
+ end
35
+
36
+ def default_value
37
+ config[:default] || []
38
+ end
39
+
40
+ def delimiter
41
+ config[:delimiter] || ","
42
+ end
43
+ end
44
+
45
+ # an option that discards the return value
46
+ class NullOption < BoolOption
47
+ def null?
48
+ true
49
+ end
50
+ end
51
+
52
+ end
@@ -1,8 +1,11 @@
1
+ $:.unshift './lib'
2
+ require 'slop'
3
+
1
4
  Gem::Specification.new do |s|
2
5
  s.name = 'slop'
3
- s.version = '3.6.0'
6
+ s.version = Slop::VERSION
4
7
  s.summary = 'Simple Lightweight Option Parsing'
5
- s.description = 'A simple DSL for gathering options and parsing the command line'
8
+ s.description = 'A DSL for gathering options and parsing command line flags'
6
9
  s.author = 'Lee Jarvis'
7
10
  s.email = 'ljjarvis@gmail.com'
8
11
  s.homepage = 'http://github.com/leejarvis/slop'
@@ -10,7 +13,7 @@ Gem::Specification.new do |s|
10
13
  s.test_files = `git ls-files -- test/*`.split("\n")
11
14
  s.license = 'MIT'
12
15
 
13
- s.required_ruby_version = '>= 1.8.7'
16
+ s.required_ruby_version = '>= 2.0.0'
14
17
 
15
18
  s.add_development_dependency 'rake'
16
19
  s.add_development_dependency 'minitest', '~> 5.0.0'
@@ -0,0 +1,31 @@
1
+ require 'test_helper'
2
+
3
+ # All raised errors tested here
4
+
5
+ describe Slop::MissingArgument do
6
+ it "raises when an argument is missing" do
7
+ opts = Slop::Options.new
8
+ opts.string "-n", "--name"
9
+ assert_raises(Slop::MissingArgument) { opts.parse %w(--name) }
10
+ end
11
+
12
+ it "does not raise when errors are suppressed" do
13
+ opts = Slop::Options.new(suppress_errors: true)
14
+ opts.string "-n", "--name"
15
+ opts.parse %w(--name)
16
+ end
17
+ end
18
+
19
+ describe Slop::UnknownOption do
20
+ it "raises when an option is unknown" do
21
+ opts = Slop::Options.new
22
+ opts.string "-n", "--name"
23
+ assert_raises(Slop::UnknownOption) { opts.parse %w(--foo) }
24
+ end
25
+
26
+ it "does not raise when errors are suppressed" do
27
+ opts = Slop::Options.new(suppress_errors: true)
28
+ opts.string "-n", "--name"
29
+ opts.parse %w(--foo)
30
+ end
31
+ end
@@ -1,145 +1,24 @@
1
- require 'helper'
1
+ require 'test_helper'
2
2
 
3
- class OptionTest < TestCase
4
- def option(*args, &block)
5
- Slop.new.on(*args, &block)
3
+ describe Slop::Option do
4
+ def option(*args)
5
+ Slop::Option.new(*args)
6
6
  end
7
7
 
8
- def option_with_argument(*args, &block)
9
- options = args.shift
10
- slop = Slop.new
11
- option = slop.opt(*args)
12
- slop.parse(options)
13
- slop.options.find {|opt| opt.key == option.key }
8
+ describe "#flag" do
9
+ it "returns the flags joined by a comma" do
10
+ assert_equal "-f, --bar", option(%w(-f --bar), nil).flag
11
+ assert_equal "--bar", option(%w(--bar), nil).flag
12
+ end
14
13
  end
15
14
 
16
- def option_value(*args, &block)
17
- option_with_argument(*args, &block).value
18
- end
19
-
20
- test "expects_argument?" do
21
- assert option(:f=).expects_argument?
22
- assert option(:foo=).expects_argument?
23
- assert option(:foo, :argument => true).expects_argument?
24
- end
25
-
26
- test "accepts_optional_argument?" do
27
- refute option(:f=).accepts_optional_argument?
28
- assert option(:f=, :argument => :optional).accepts_optional_argument?
29
- assert option(:f, :optional_argument => true).accepts_optional_argument?
30
- end
31
-
32
- test "key" do
33
- assert_equal 'foo', option(:foo).key
34
- assert_equal 'foo', option(:f, :foo).key
35
- assert_equal 'f', option(:f).key
36
- end
37
-
38
- test "call" do
39
- foo = nil
40
- option(:f, :callback => proc { foo = "bar" }).call
41
- assert_equal "bar", foo
42
- option(:f) { foo = "baz" }.call
43
- assert_equal "baz", foo
44
- option(:f) { |o| assert_equal 1, o }.call(1)
45
- end
46
-
47
- # type casting
48
-
49
- test "proc/custom type cast" do
50
- assert_equal 1, option_value(%w'-f 1', :f=, :as => proc {|x| x.to_i })
51
- assert_equal "oof", option_value(%w'-f foo', :f=, :as => proc {|x| x.reverse })
52
- end
53
-
54
- test "integer type cast" do
55
- opts = Slop.new
56
- opts.on :f=, :as => Integer
57
- opts.parse %w'-f 1'
58
- assert_equal 1, opts[:f]
59
-
60
- opts = Slop.new(:strict => true) { on :r=, :as => Integer }
61
- assert_raises(Slop::InvalidArgumentError) { opts.parse %w/-r abc/ }
62
- end
63
-
64
- test "float type cast" do
65
- opts = Slop.new(:strict => true) { on :r=, :as => Float }
66
- assert_raises(Slop::InvalidArgumentError) { opts.parse %w/-r abc/ }
67
- end
68
-
69
- test "symbol type cast" do
70
- assert_equal :foo, option_value(%w'-f foo', :f=, :as => Symbol)
71
- end
72
-
73
- test "range type cast" do
74
- assert_equal((1..10), option_value(%w/-r 1..10/, :r=, :as => Range))
75
- assert_equal((1..10), option_value(%w/-r 1-10/, :r=, :as => Range))
76
- assert_equal((1..10), option_value(%w/-r 1,10/, :r=, :as => Range))
77
- assert_equal((1...10), option_value(%w/-r 1...10/, :r=, :as => Range))
78
- assert_equal((-1..10), option_value(%w/-r -1..10/, :r=, :as => Range))
79
- assert_equal((1..-10), option_value(%w/-r 1..-10/, :r=, :as => Range))
80
- assert_equal((1..1), option_value(%w/-r 1/, :r=, :as => Range))
81
- assert_equal((-1..10), option_value(%w/-r -1..10/, :r, :as => Range, :optional_argument => true))
82
-
83
- opts = Slop.new(:strict => true) { on :r=, :as => Range }
84
- assert_raises(Slop::InvalidArgumentError) { opts.parse %w/-r abc/ }
85
- end
86
-
87
- test "array type cast" do
88
- assert_equal %w/lee john bill/, option_value(%w/-p lee,john,bill/, :p=, :as => Array)
89
- assert_equal %w/lee john bill jeff jill/, option_value(%w/-p lee,john,bill -p jeff,jill/, :p=, :as => Array)
90
- assert_equal %w/lee john bill/, option_value(%w/-p lee:john:bill/, :p=, :as => Array, :delimiter => ':')
91
- assert_equal %w/lee john,bill/, option_value(%w/-p lee,john,bill/, :p=, :as => Array, :limit => 2)
92
- assert_equal %w/lee john:bill/, option_value(%w/-p lee:john:bill/, :p=, :as => Array, :limit => 2, :delimiter => ':')
93
- end
94
-
95
- test "regexp type cast" do
96
- assert_equal Regexp.new("foo"), option_value(%w/-p foo/, :p=, :as => Regexp)
97
- end
98
-
99
- test "adding custom types" do
100
- opts = Slop.new
101
- opt = opts.on :f=, :as => :reverse
102
- opt.types[:reverse] = proc { |v| v.reverse }
103
- opts.parse %w'-f bar'
104
- assert_equal 'rab', opt.value
105
- end
106
-
107
- test "count type" do
108
- assert_equal 3, option_value(%w/-c -c -c/, :c, :as => :count)
109
- assert_equal 0, option_value(%w/-a -b -z/, :c, :as => :count)
110
- assert_equal 3, option_value(%w/-vvv/, :v, :as => :count)
111
- end
112
-
113
- # end type casting tests
114
-
115
- test "using a default value as fallback" do
116
- opts = Slop.new
117
- opts.on :f, :argument => :optional, :default => 'foo'
118
- opts.parse %w'-f'
119
- assert_equal 'foo', opts[:f]
120
- end
121
-
122
- test "printing options" do
123
- slop = Slop.new
124
- slop.opt :n, :name=, 'Your name'
125
- slop.opt :age=, 'Your age'
126
- slop.opt :V, 'Display the version'
127
-
128
- assert_equal " -n, --name Your name", slop.fetch_option(:name).to_s
129
- assert_equal " --age Your age", slop.fetch_option(:age).to_s
130
- assert_equal " -V, Display the version", slop.fetch_option(:V).help
131
- end
132
-
133
- test "printing options that have defaults" do
134
- opts = Slop.new
135
- opts.on :n, :name=, 'Your name', :default => 'Lee'
136
-
137
- assert_equal " -n, --name Your name (default: Lee)", opts.fetch_option(:name).to_s
138
- end
15
+ describe "#key" do
16
+ it "uses the last flag and strips trailing hyphens" do
17
+ assert_equal :foo, option(%w(-f --foo), nil).key
18
+ end
139
19
 
140
- test "overwriting the help text" do
141
- slop = Slop.new
142
- slop.on :foo, :help => ' -f, --foo SOMETHING FOOEY'
143
- assert_equal ' -f, --foo SOMETHING FOOEY', slop.fetch_option(:foo).help
20
+ it "can be overridden" do
21
+ assert_equal :bar, option(%w(-f --foo), nil, key: "bar").key
22
+ end
144
23
  end
145
24
  end