slop 3.6.0 → 4.0.0

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,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