rake-commander 0.1.2
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.
- checksums.yaml +7 -0
- data/.gitignore +20 -0
- data/.rspec +3 -0
- data/.rubocop.yml +74 -0
- data/.yardopts +10 -0
- data/CHANGELOG.md +14 -0
- data/Gemfile +6 -0
- data/README.md +28 -0
- data/Rakefile +32 -0
- data/examples/basic.rb +30 -0
- data/lib/rake-commander/base/class_auto_loader.rb +112 -0
- data/lib/rake-commander/base/class_helpers.rb +168 -0
- data/lib/rake-commander/base.rb +29 -0
- data/lib/rake-commander/custom.rb +3 -0
- data/lib/rake-commander/option.rb +153 -0
- data/lib/rake-commander/options/arguments.rb +116 -0
- data/lib/rake-commander/options/error.rb +18 -0
- data/lib/rake-commander/options/error_rely.rb +58 -0
- data/lib/rake-commander/options/name.rb +160 -0
- data/lib/rake-commander/options/set.rb +16 -0
- data/lib/rake-commander/options.rb +171 -0
- data/lib/rake-commander/rake_context/wrapper.rb +39 -0
- data/lib/rake-commander/rake_task.rb +157 -0
- data/lib/rake-commander/version.rb +3 -0
- data/lib/rake-commander.rb +9 -0
- data/rake-commander.gemspec +29 -0
- metadata +169 -0
@@ -0,0 +1,153 @@
|
|
1
|
+
class RakeCommander
|
2
|
+
@option_struct ||= Struct.new(:short, :name)
|
3
|
+
class Option < @option_struct
|
4
|
+
extend RakeCommander::Base::ClassHelpers
|
5
|
+
extend RakeCommander::Options::Name
|
6
|
+
|
7
|
+
attr_accessor :desc, :default
|
8
|
+
attr_writer :type_coertion, :required
|
9
|
+
attr_reader :name_full
|
10
|
+
|
11
|
+
def initialize(short, name, *args, **kargs, &block)
|
12
|
+
raise ArgumentError, "A short of one letter should be provided. Given: #{short}" unless self.class.valid_short?(short)
|
13
|
+
raise ArgumentError, "A name should be provided. Given: #{name}" unless self.class.valid_name?(name)
|
14
|
+
|
15
|
+
@name_full = name
|
16
|
+
super(short, name)
|
17
|
+
@default = kargs[:default] if kargs.key?(:default)
|
18
|
+
@desc = kargs[:desc] if kargs.key?(:desc)
|
19
|
+
@required = kargs[:required] if kargs.key?(:required)
|
20
|
+
@other_args = args
|
21
|
+
@original_block = block
|
22
|
+
yield(self) if block_given?
|
23
|
+
configure_other
|
24
|
+
end
|
25
|
+
|
26
|
+
def required?
|
27
|
+
!!@required
|
28
|
+
end
|
29
|
+
|
30
|
+
# @return [Symbol]
|
31
|
+
def short
|
32
|
+
self.class.short_sym(super)
|
33
|
+
end
|
34
|
+
|
35
|
+
# @return [String]
|
36
|
+
def short_hyphen
|
37
|
+
self.class.short_hyphen(short)
|
38
|
+
end
|
39
|
+
|
40
|
+
# @return [Symbol]
|
41
|
+
def name
|
42
|
+
self.class.name_word_sym(super)
|
43
|
+
end
|
44
|
+
|
45
|
+
# @return [String]
|
46
|
+
def name_hyphen
|
47
|
+
self.class.name_hyphen(name_full)
|
48
|
+
end
|
49
|
+
|
50
|
+
# @param [Boolean] whether this option allows an argument
|
51
|
+
def argument?
|
52
|
+
self.class.name_argument?(name_full)
|
53
|
+
end
|
54
|
+
|
55
|
+
# @param [String, Nil] the argument, may it exist
|
56
|
+
def argument
|
57
|
+
return nil unless argument?
|
58
|
+
self.class.name_argument(name_full)
|
59
|
+
end
|
60
|
+
|
61
|
+
# @param [Boolean] If there was an argument, whether it is required
|
62
|
+
def argument_required?
|
63
|
+
self.class.argument_required?(argument)
|
64
|
+
end
|
65
|
+
|
66
|
+
# @return [Class, NilClass]
|
67
|
+
def type_coertion
|
68
|
+
@type_coertion || (default? && default.class)
|
69
|
+
end
|
70
|
+
|
71
|
+
# @return [Boolean]
|
72
|
+
def default?
|
73
|
+
instance_variable_defined?(:@default)
|
74
|
+
end
|
75
|
+
|
76
|
+
# Adds this option's switch to the `OptionParser`
|
77
|
+
# @note it allows to add a `middleware` block that will be called at `parse` runtime
|
78
|
+
def add_switch(opts_parser, where: :base, &middleware)
|
79
|
+
raise "Expecting OptionParser. Given: #{opts_parser.class}" unless opts_parser.is_a?(OptionParser)
|
80
|
+
case where
|
81
|
+
when :head, :top
|
82
|
+
opts_parser.on_head(*switch_args, &option_block(&middleware))
|
83
|
+
when :tail, :end
|
84
|
+
opts_parser.on_tail(*switch_args, &option_block(&middleware))
|
85
|
+
else # :base
|
86
|
+
opts_parser.on(*switch_args, &option_block(&middleware))
|
87
|
+
end
|
88
|
+
opts_parser
|
89
|
+
end
|
90
|
+
|
91
|
+
# @return [Array<Variant>]
|
92
|
+
def switch_args
|
93
|
+
configure_other
|
94
|
+
args = [short_hyphen, name_hyphen]
|
95
|
+
if str = switch_desc
|
96
|
+
args << str
|
97
|
+
end
|
98
|
+
args << type_coertion if type_coertion
|
99
|
+
args
|
100
|
+
end
|
101
|
+
|
102
|
+
private
|
103
|
+
|
104
|
+
# Called on parse runtime
|
105
|
+
def option_block(&middleware)
|
106
|
+
block_extra_args = [default, short, name]
|
107
|
+
proc do |value|
|
108
|
+
args = block_extra_args.dup.unshift(value)
|
109
|
+
@original_block&.call(*args)
|
110
|
+
middleware&.call(*args)
|
111
|
+
end
|
112
|
+
end
|
113
|
+
|
114
|
+
def switch_desc
|
115
|
+
val = "#{desc}#{default_desc}"
|
116
|
+
return nil if val.empty?
|
117
|
+
val
|
118
|
+
end
|
119
|
+
|
120
|
+
def default_desc
|
121
|
+
return nil unless default?
|
122
|
+
str = "Default: '#{default}'"
|
123
|
+
if desc && !desc.downcase.include?('default')
|
124
|
+
str = desc.end_with?('.') ? " #{str}" : ". #{str}"
|
125
|
+
end
|
126
|
+
str
|
127
|
+
end
|
128
|
+
|
129
|
+
def other_args(*args)
|
130
|
+
@other_args ||= []
|
131
|
+
if args.empty?
|
132
|
+
@other_args
|
133
|
+
else
|
134
|
+
@other_args.push(*args)
|
135
|
+
end
|
136
|
+
end
|
137
|
+
|
138
|
+
# It consumes `other_args`, to prevent direct overrides to be overriden by it.
|
139
|
+
def configure_other
|
140
|
+
if type = other_args.find {|arg| arg.is_a?(Class)}
|
141
|
+
self.type_coertion = type
|
142
|
+
other_args.delete(type)
|
143
|
+
end
|
144
|
+
if value = other_args.find {|arg| arg.is_a?(String)}
|
145
|
+
self.desc = value
|
146
|
+
other_args.dup.each do |val|
|
147
|
+
other_args.delete(val) if val.is_a?(String)
|
148
|
+
end
|
149
|
+
end
|
150
|
+
nil
|
151
|
+
end
|
152
|
+
end
|
153
|
+
end
|
@@ -0,0 +1,116 @@
|
|
1
|
+
class RakeCommander
|
2
|
+
module Options
|
3
|
+
# Offers helpers to treat `ARGV`
|
4
|
+
module Arguments
|
5
|
+
include RakeCommander::Options::Name
|
6
|
+
|
7
|
+
# Options with arguments should not take another option as value.
|
8
|
+
# `OptionParser` can do this even if the the argument is optional.
|
9
|
+
# This method re-arranges the arguments based on options that receive parameters,
|
10
|
+
# provided that an option is not taken as a value of a previous option that accepts arguments.
|
11
|
+
# If an option with argument is missing the argument, but has a `default` value,
|
12
|
+
# that `default` value will be inserted after the option in the array
|
13
|
+
# to prevent the `OptionParser::MissingArgument` error to stop the parsing process.
|
14
|
+
# @note
|
15
|
+
# 1. Any word or letter with _hypen_ -`` or _double hypen_ `--` is interpreted as option(s)
|
16
|
+
# 2. To overcome this limitation, you may enclose in double quotes and argument with
|
17
|
+
# that start (i,e, `"--argument"`).
|
18
|
+
# @example
|
19
|
+
# 1. `-abc ARGUMENT` where only `c` receives the argument becomes `-ab -c ARGUMENT`
|
20
|
+
# 3. `-abc ARGUMENT` where `b` and `c` are argument receivers becomes `-a -b nil -c ARGUMENT`
|
21
|
+
# 2. `-acb ARGUMENT` where only `c` receives the argument becomes `-a -c nil -b ARGUMENT`
|
22
|
+
# 4. `-c --some-option ARGUMENT` where both options receive argument, becomes `-c nil --some-option ARGUMENT`
|
23
|
+
# 5. `-c --some-option -d ARGUMENT` where both options receive argument, becomes `-c nil --some-option nil -d ARGUMENT`
|
24
|
+
# 6. `-cd ARGUMENT` where `c` default is `"yeah"`, becomes `-c yeah -d ARGUMENT`
|
25
|
+
# @param argv [Array<String>]
|
26
|
+
# @param options [Hash] the defined `RakeCommander::Option` to re-arrange `argv` with.
|
27
|
+
# @return [Array<String>] the re-arranged `argv`
|
28
|
+
def pre_parse_arguments(argv = ARGV, options)
|
29
|
+
pre_parsed = explicit_argument_options(argv, options)
|
30
|
+
compact_short = ''
|
31
|
+
pre_parsed.each_with_object([]) do |(opt_ref, args), out|
|
32
|
+
next out.push(*args) unless opt_ref.is_a?(Symbol)
|
33
|
+
is_short = opt_ref.to_s.length == 1
|
34
|
+
next compact_short << opt_ref.to_s if is_short && args.empty?
|
35
|
+
out.push("-#{compact_short}") unless compact_short.empty?
|
36
|
+
compact_short = ''
|
37
|
+
opt_str = is_short ? "-#{opt_ref}" : name_hyphen(opt_ref)
|
38
|
+
out.push(opt_str, *args)
|
39
|
+
end.tap do |out|
|
40
|
+
out.push("-#{compact_short}") unless compact_short.empty?
|
41
|
+
end
|
42
|
+
end
|
43
|
+
|
44
|
+
private
|
45
|
+
|
46
|
+
# @example the output is actually a Hash, keyed by the Symbol of the option (short or name)
|
47
|
+
# 1. `-abc ARGUMENT` where only `c` receives the argument becomes `:a :b :c ARGUMENT`
|
48
|
+
# 3. `-abc ARGUMENT` where `b` and `c` are argument receivers becomes `:a :b nil :c ARGUMENT`
|
49
|
+
# 2. `-acb ARGUMENT` where only `c` receives the argument becomes `:a :c nil :b ARGUMENT`
|
50
|
+
# 4. `-c --some-option ARGUMENT` where both options receive argument, becomes `:c nil :some_option ARGUMENT`
|
51
|
+
# 5. `-c --some-option -d ARGUMENT` where first two options receive argument, becomes `:c nil :some_option nil :d ARGUMENT`
|
52
|
+
# 6. `-cd ARGUMENT` where `c` default is `"yeah"`, becomes `:c yeah :d ARGUMENT`
|
53
|
+
# @return [Hash<Symbol, Array>]
|
54
|
+
def explicit_argument_options(argv, options)
|
55
|
+
decoupled = decluster_shorts_n_names_to_sym(argv)
|
56
|
+
grouped = group_symbols_with_strings(decoupled)
|
57
|
+
normalized = insert_missing_argument_to_groups(grouped, options)
|
58
|
+
normalized.each_with_object({}) do |group, pre_parsed|
|
59
|
+
opt_ref = group.first.is_a?(Symbol)? group.shift : nil
|
60
|
+
pre_parsed[opt_ref] = group
|
61
|
+
end
|
62
|
+
end
|
63
|
+
|
64
|
+
# It adds the missing argument to options that expect it.
|
65
|
+
# @note it uses `default` if present, and `nil` otherwise.
|
66
|
+
# @param groups [@see #pair_symbols_with_strings]
|
67
|
+
def insert_missing_argument_to_groups(groups, options)
|
68
|
+
groups.each do |group|
|
69
|
+
args = group.dup
|
70
|
+
opt_ref = args.shift
|
71
|
+
next unless args.empty?
|
72
|
+
next unless opt_ref.is_a?(Symbol)
|
73
|
+
next unless opt = options[opt_ref]
|
74
|
+
next unless opt.argument?
|
75
|
+
next group.push(opt.default) if opt.default?
|
76
|
+
group.push(nil)
|
77
|
+
end
|
78
|
+
end
|
79
|
+
|
80
|
+
# @return [Array<Array>] where the first element of each `Array` is a symbol
|
81
|
+
# followed by one or more `String`.
|
82
|
+
def group_symbols_with_strings(argv)
|
83
|
+
[].tap do |out|
|
84
|
+
curr_ary = nil
|
85
|
+
argv.each do |arg|
|
86
|
+
if arg.is_a?(Symbol)
|
87
|
+
out << (curr_ary = [arg])
|
88
|
+
else # must be `String`
|
89
|
+
out << (curr_ary = []) unless curr_ary
|
90
|
+
curr_ary << arg
|
91
|
+
end
|
92
|
+
end
|
93
|
+
end
|
94
|
+
end
|
95
|
+
|
96
|
+
# It splits `argv` compacted shorts into their `Symbol` version.
|
97
|
+
# Symbolizes option `names` (long version).
|
98
|
+
# @return [Array<String, Symbol>] where symbols are options and strings arguments.
|
99
|
+
def decluster_shorts_n_names_to_sym(argv)
|
100
|
+
argv.each_with_object([]) do |arg, out|
|
101
|
+
if single_hyphen?(arg) # short option(s)
|
102
|
+
options = arg.match(SINGLE_HYPHEN_REGEX)[:options]
|
103
|
+
options.split('').each do |short|
|
104
|
+
out << short_sym(short)
|
105
|
+
end
|
106
|
+
elsif double_hyphen?(arg) # name option
|
107
|
+
name = arg.match(DOUBLE_HYPHEN_REGEX)[:option]
|
108
|
+
out << name_sym(name)
|
109
|
+
else # argument
|
110
|
+
out << arg
|
111
|
+
end
|
112
|
+
end
|
113
|
+
end
|
114
|
+
end
|
115
|
+
end
|
116
|
+
end
|
@@ -0,0 +1,18 @@
|
|
1
|
+
require_relative 'error_rely'
|
2
|
+
class RakeCommander
|
3
|
+
module Options
|
4
|
+
class MissingOption < RakeCommander::Options::ErrorRely
|
5
|
+
def initialize(value)
|
6
|
+
super("missing required option: #{to_description(value)}")
|
7
|
+
end
|
8
|
+
end
|
9
|
+
|
10
|
+
class MissingArgument < RakeCommander::Options::ErrorRely
|
11
|
+
OPTION_REGEX = /missing (?:required|) argument: (?<option>.+)/i.freeze
|
12
|
+
end
|
13
|
+
|
14
|
+
class InvalidArgument < RakeCommander::Options::ErrorRely
|
15
|
+
OPTION_REGEX = /invalid argument: (?<option>.+)/i.freeze
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
@@ -0,0 +1,58 @@
|
|
1
|
+
class RakeCommander
|
2
|
+
module Options
|
3
|
+
# Relies between OptionParser and RakeCommander errors
|
4
|
+
class ErrorRely < StandardError
|
5
|
+
extend RakeCommander::Options::Name
|
6
|
+
|
7
|
+
OPTION_REGEX = /(?:argument|option): (?<option>.+)/i.freeze
|
8
|
+
|
9
|
+
def initialize(value)
|
10
|
+
case value
|
11
|
+
when OptionParser::MissingArgument, OptionParser::InvalidArgument
|
12
|
+
super(value.message)
|
13
|
+
when String
|
14
|
+
super(value)
|
15
|
+
else
|
16
|
+
raise ArgumentError, "Expecting String or OptionParser error. Given: #{value.class}"
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
def name?
|
21
|
+
option_sym.to_s.length > 1
|
22
|
+
end
|
23
|
+
|
24
|
+
def short?
|
25
|
+
option_sym.to_s.length == 1
|
26
|
+
end
|
27
|
+
|
28
|
+
def option_sym
|
29
|
+
return @option_sym if @option_sym
|
30
|
+
return nil unless match = message.match(self.class::OPTION_REGEX)
|
31
|
+
option = match[:option]
|
32
|
+
@option_sym = \
|
33
|
+
if option.length > 1
|
34
|
+
self.class.name_word_sym(option)
|
35
|
+
else
|
36
|
+
self.class.short_sym(option)
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def to_description(value)
|
43
|
+
case value
|
44
|
+
when Hash
|
45
|
+
to_description(value.values.uniq)
|
46
|
+
when Array
|
47
|
+
value.map do |v|
|
48
|
+
to_description(v)
|
49
|
+
end.join(', ')
|
50
|
+
when RakeCommander::Option
|
51
|
+
"#{value.name_hyphen} (#{value.short_hyphen})"
|
52
|
+
else
|
53
|
+
value
|
54
|
+
end
|
55
|
+
end
|
56
|
+
end
|
57
|
+
end
|
58
|
+
end
|
@@ -0,0 +1,160 @@
|
|
1
|
+
class RakeCommander
|
2
|
+
module Options
|
3
|
+
module Name
|
4
|
+
# Substitions
|
5
|
+
HYPHEN_START_REGEX = /^-+/.freeze
|
6
|
+
HYPEN_REGEX = /-+/.freeze
|
7
|
+
UNDERSCORE_REGEX = /_+/.freeze
|
8
|
+
SPACE_REGEX = /\s+/.freeze
|
9
|
+
# Checkers
|
10
|
+
OPTIONAL_REGEX = /\[\w+\]$/.freeze
|
11
|
+
SINGLE_HYPHEN_REGEX = /^-(?<options>[^- ][^ ]*)/.freeze
|
12
|
+
DOUBLE_HYPHEN_REGEX = /^--(?<option>[^- ][^ ]*)/.freeze
|
13
|
+
|
14
|
+
# @return [Boolean]
|
15
|
+
def single_hyphen?(value)
|
16
|
+
return false unless value.respond_to?(:to_s)
|
17
|
+
!!value.to_s.match(SINGLE_HYPHEN_REGEX)
|
18
|
+
end
|
19
|
+
|
20
|
+
# @return [Boolean]
|
21
|
+
def double_hyphen?(value)
|
22
|
+
return false unless value.respond_to?(:to_s)
|
23
|
+
!!value.to_s.match(DOUBLE_HYPHEN_REGEX)
|
24
|
+
end
|
25
|
+
|
26
|
+
# @param strict [Boolean] whether hyphen is required when declaring an option `short`
|
27
|
+
# @return [Boolean]
|
28
|
+
def valid_short?(value, strict: false)
|
29
|
+
return false unless value.respond_to?(:to_s) && !value.to_s.empty?
|
30
|
+
return false unless !strict || single_hypen(value)
|
31
|
+
short_sym(value).to_s.length == 1
|
32
|
+
end
|
33
|
+
|
34
|
+
# @param strict [Boolean] whether hyphen is required when declaring an option `name`
|
35
|
+
# @return [Boolean]
|
36
|
+
def valid_name?(value, strict: false)
|
37
|
+
return false unless value.respond_to?(:to_s) && !value.to_s.empty?
|
38
|
+
return false unless !strict || double_hyphen?(value)
|
39
|
+
name_sym(value).to_s.length > 1
|
40
|
+
end
|
41
|
+
|
42
|
+
# @param strict [Boolean] whether hyphen is required when declaring an option `short`
|
43
|
+
# @return [Boolean] whether `value` is an hyphened option `short`
|
44
|
+
def short_hyphen?(value, strict: false)
|
45
|
+
short?(value, strict: strict) && single_hypen(value)
|
46
|
+
end
|
47
|
+
|
48
|
+
# @param strict [Boolean] whether hyphen is required when declaring an option `name`
|
49
|
+
# @return [Boolean] whether `value` is an hyphened option `name`
|
50
|
+
def name_hyphen?(value, strict: false)
|
51
|
+
name?(value, strict: strict) && double_hyphen?(value)
|
52
|
+
end
|
53
|
+
|
54
|
+
# Converter
|
55
|
+
# @example
|
56
|
+
# * `"-d"` becomes `:d`
|
57
|
+
# @return [Symbol, NilClass]
|
58
|
+
def short_sym(value)
|
59
|
+
return nil unless value
|
60
|
+
value = value.to_s.gsub(HYPHEN_START_REGEX, '')
|
61
|
+
return nil unless value = value.chars.first
|
62
|
+
value.to_sym
|
63
|
+
end
|
64
|
+
|
65
|
+
# Converter.
|
66
|
+
# @example
|
67
|
+
# * `"--there-we-go ARGUMENT"` becomes `:"there_we_go ARGUMENT"`
|
68
|
+
# @note
|
69
|
+
# 1. It removes the double hyphen start (`--`)
|
70
|
+
# 2. Replaces any intermediate hyphen by underscore `_`
|
71
|
+
# 3. Replaces any multi-spacing by single space ` `
|
72
|
+
# @return [Symbol, NilClass]
|
73
|
+
def name_sym(value)
|
74
|
+
return nil unless value
|
75
|
+
value = value.to_s.gsub(HYPHEN_START_REGEX, '')
|
76
|
+
value = value.gsub(HYPEN_REGEX, '_')
|
77
|
+
value = value.gsub(SPACE_REGEX, ' ')
|
78
|
+
return nil if value.empty?
|
79
|
+
value.to_sym
|
80
|
+
end
|
81
|
+
|
82
|
+
# It's like `#name_sym` but it only gets the option name.
|
83
|
+
# @example
|
84
|
+
# * `"--there-we-go ARGUMENT"` becomes `:there_we_go`
|
85
|
+
# @see #name_sym
|
86
|
+
# @return [Symbol, NilClass]
|
87
|
+
def name_word_sym(value)
|
88
|
+
return nil unless value = name_sym(value)
|
89
|
+
return nil unless value = name_words(value).first
|
90
|
+
value.to_sym
|
91
|
+
end
|
92
|
+
|
93
|
+
# @return [String, NilClass] it returns the hyphened (`-`) version of a short `value`
|
94
|
+
def short_hyphen(value)
|
95
|
+
return nil unless value = short_sym(value)
|
96
|
+
"-#{value}"
|
97
|
+
end
|
98
|
+
|
99
|
+
# Gets the actual name of the option. First word.
|
100
|
+
# @example
|
101
|
+
# * `"--there-we-go ARGUMENT"` becomes `"--there-we-go"`
|
102
|
+
# * `"there-we-go"` becomes `"--there-we-go"`
|
103
|
+
# * `:there_we_go` becomes `"--there-we-go"`
|
104
|
+
# @return [String, NilClass] option `name` alone double hypened (`--`)
|
105
|
+
def name_hyphen(value)
|
106
|
+
return nil unless value = name_sym(value)
|
107
|
+
value = value.to_s.gsub(UNDERSCORE_REGEX, '-')
|
108
|
+
return nil if value.empty?
|
109
|
+
"--#{value}"
|
110
|
+
end
|
111
|
+
|
112
|
+
# @example
|
113
|
+
# * `"--there-we-go ARGUMENT"` returns `"ARGUMENT"`
|
114
|
+
# @return [String, NilClass] the argument of `value`, if present
|
115
|
+
def name_argument(value)
|
116
|
+
name_words(value)[1]
|
117
|
+
end
|
118
|
+
|
119
|
+
# @example
|
120
|
+
# * `"--there-we-go ARGUMENT"` returns `true`
|
121
|
+
# * `"--time"` returns `false`
|
122
|
+
# @return [String, NilClass] whether `value` is a name with argument
|
123
|
+
def name_argument?(value)
|
124
|
+
!!name_argument(value)
|
125
|
+
end
|
126
|
+
|
127
|
+
# @example
|
128
|
+
# * `"--there-we-go [ARGUMENT]"` returns `false`
|
129
|
+
# * `"--folder FOLDER"` returns `true`
|
130
|
+
# * `"--time"` returns `false`
|
131
|
+
# @return [Boolean] `true` if `value` does NOT end with `[String]`
|
132
|
+
def argument_required?(value)
|
133
|
+
return false unless value
|
134
|
+
!argument_optional?(value)
|
135
|
+
end
|
136
|
+
|
137
|
+
# @example
|
138
|
+
# * `"--there-we-go [ARGUMENT]"` returns `true`
|
139
|
+
# * `"--folder FOLDER"` returns `false`
|
140
|
+
# * `"--time"` returns `true`
|
141
|
+
# @note when there is NO argument it evaluates `true`
|
142
|
+
# @return [Boolean] `true` if `value` ends with `[String]`
|
143
|
+
def argument_optional?(value)
|
144
|
+
return true unless value
|
145
|
+
!!value.match(OPTIONAL_REGEX)
|
146
|
+
end
|
147
|
+
|
148
|
+
private
|
149
|
+
|
150
|
+
# @example
|
151
|
+
# * `"--there-we-go [ARGUMENT]"` returns `["there-we-go","[ARGUMENT]"]`
|
152
|
+
# @return [Array<String>] the words of `value` without hyphen start
|
153
|
+
def name_words(value)
|
154
|
+
return nil unless value
|
155
|
+
value = value.to_s.gsub(HYPHEN_START_REGEX, '')
|
156
|
+
value.to_s.split(SPACE_REGEX)
|
157
|
+
end
|
158
|
+
end
|
159
|
+
end
|
160
|
+
end
|
@@ -0,0 +1,171 @@
|
|
1
|
+
require_relative 'options/name'
|
2
|
+
require_relative 'options/arguments'
|
3
|
+
require_relative 'option'
|
4
|
+
|
5
|
+
class RakeCommander
|
6
|
+
module Options
|
7
|
+
class << self
|
8
|
+
def included(base)
|
9
|
+
super(base)
|
10
|
+
base.extend RakeCommander::Base::ClassHelpers
|
11
|
+
base.extend ClassMethods
|
12
|
+
base.inheritable_attrs :banner, :options_hash, :results_with_all_defaults
|
13
|
+
base.extend RakeCommander::Options::Arguments
|
14
|
+
end
|
15
|
+
end
|
16
|
+
|
17
|
+
module ClassMethods
|
18
|
+
def options_hash
|
19
|
+
@options_hash ||= {}
|
20
|
+
end
|
21
|
+
|
22
|
+
def options
|
23
|
+
options_hash.values.uniq
|
24
|
+
end
|
25
|
+
|
26
|
+
def options?
|
27
|
+
!options.empty?
|
28
|
+
end
|
29
|
+
|
30
|
+
def clear_options!
|
31
|
+
@options_hash = {}
|
32
|
+
end
|
33
|
+
|
34
|
+
# Allows to use a set of options
|
35
|
+
# @param options [Enumerable<RakeCommander::Option>]
|
36
|
+
def use_options(options)
|
37
|
+
options = options.values if options.is_a?(Hash)
|
38
|
+
options.each do |opt|
|
39
|
+
add_to_options(opt)
|
40
|
+
end
|
41
|
+
self
|
42
|
+
end
|
43
|
+
|
44
|
+
# Defines a new option
|
45
|
+
# @note
|
46
|
+
# - It will override with a Warning options with same `short` or `name`
|
47
|
+
def option(*args, **kargs, &block)
|
48
|
+
opt = RakeCommander::Option.new(*args, **kargs, &block)
|
49
|
+
add_to_options(opt)
|
50
|
+
self
|
51
|
+
end
|
52
|
+
|
53
|
+
# Overrides the auto-generated banner
|
54
|
+
def banner(desc = :not_used)
|
55
|
+
return @banner = desc unless desc == :not_used
|
56
|
+
return @banner if @banner
|
57
|
+
return task_options_banner if respond_to?(:task_options_banner, true)
|
58
|
+
end
|
59
|
+
|
60
|
+
# @return [Boolean] whether results should include options defined
|
61
|
+
# with a default, regarless if they are invoked
|
62
|
+
def results_with_all_defaults(value = nil)
|
63
|
+
if value.nil?
|
64
|
+
@results_with_all_defaults || false
|
65
|
+
else
|
66
|
+
@results_with_all_defaults = !!value
|
67
|
+
end
|
68
|
+
end
|
69
|
+
|
70
|
+
# It builds the `OptionParser` injecting the `middleware` block.
|
71
|
+
# @return [Hash] with `short` option as `key` and final value as `value`.
|
72
|
+
def parse_options(argv = ARGV, leftovers: [], &middleware)
|
73
|
+
left_overs = []
|
74
|
+
options_parser_with_results(middleware) do |options_parser|
|
75
|
+
argv = pre_parse_arguments(argv, options_hash)
|
76
|
+
leftovers.push(*options_parser.parse(argv))
|
77
|
+
rescue OptionParser::MissingArgument => e
|
78
|
+
raise RakeCommander::Options::MissingArgument, e, cause: nil
|
79
|
+
rescue OptionParser::InvalidArgument => e
|
80
|
+
error = RakeCommander::Options::InvalidArgument
|
81
|
+
error = error.new(e)
|
82
|
+
if (opt = options_hash[error.option_sym]) && opt.argument_required?
|
83
|
+
msg = "missing required argument: #{opt.name_hyphen} (#{opt.short_hyphen})"
|
84
|
+
raise RakeCommander::Options::MissingArgument, msg, cause: nil
|
85
|
+
else
|
86
|
+
raise error, e, cause: nil
|
87
|
+
end
|
88
|
+
end.tap do |results|
|
89
|
+
check_required_presence!(results)
|
90
|
+
end
|
91
|
+
end
|
92
|
+
|
93
|
+
protected
|
94
|
+
|
95
|
+
# @return [OptionParser] the built options parser.
|
96
|
+
def options_parser(&middleware)
|
97
|
+
new_options_parser do |opts|
|
98
|
+
opts.banner = banner if banner
|
99
|
+
options.each {|opt| opt.add_switch(opts, &middleware)}
|
100
|
+
end
|
101
|
+
end
|
102
|
+
|
103
|
+
def new_options_parser(&block)
|
104
|
+
require 'optparse'
|
105
|
+
OptionParser.new(&block)
|
106
|
+
end
|
107
|
+
|
108
|
+
private
|
109
|
+
|
110
|
+
# Expects a block that should do the final call to `#parse`
|
111
|
+
def options_parser_with_results(middleware)
|
112
|
+
result_defaults.tap do |result|
|
113
|
+
results_collector = proc do |value, default, short, name|
|
114
|
+
middleware&.call(value, default, short, name)
|
115
|
+
result[short] = value.nil?? default : value
|
116
|
+
end
|
117
|
+
options_parser = options_parser(&results_collector)
|
118
|
+
yield(options_parser)
|
119
|
+
end
|
120
|
+
end
|
121
|
+
|
122
|
+
# Based on `required` options, it sets the `default`
|
123
|
+
def result_defaults
|
124
|
+
{}.tap do |res_def|
|
125
|
+
options.select do |opt|
|
126
|
+
(results_with_all_defaults && opt.default?) \
|
127
|
+
|| (opt.required? && opt.default?)
|
128
|
+
end.each do |opt|
|
129
|
+
res_def[opt.short] = opt.default
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
|
134
|
+
# It throws an exception if any of the required options
|
135
|
+
# is missing in results
|
136
|
+
def check_required_presence!(results)
|
137
|
+
missing = options.select do |opt|
|
138
|
+
opt.required?
|
139
|
+
end.reject do |opt|
|
140
|
+
results.key?(opt.short) || results.key?(opt.name)
|
141
|
+
end
|
142
|
+
raise RakeCommander::Options::MissingOption.new(missing) unless missing.empty?
|
143
|
+
end
|
144
|
+
|
145
|
+
def add_to_options(opt)
|
146
|
+
if prev = options_hash[opt.short]
|
147
|
+
puts "Warning: Overriding option with short '#{prev.short}' ('#{prev.name}')"
|
148
|
+
options_hash.delete(prev.short)
|
149
|
+
options_hash.delete(prev.name)
|
150
|
+
end
|
151
|
+
if prev = options_hash[opt.name]
|
152
|
+
puts "Warning: Overriding option with name '#{prev.name}' ('#{prev.short}')"
|
153
|
+
options_hash.delete(prev.short)
|
154
|
+
options_hash.delete(prev.name)
|
155
|
+
end
|
156
|
+
options_hash[opt.name] = options_hash[opt.short] = opt
|
157
|
+
end
|
158
|
+
end
|
159
|
+
|
160
|
+
def options(argv = ARGV)
|
161
|
+
@options ||= self.class.parse_options(argv, leftovers: self.options_leftovers)
|
162
|
+
end
|
163
|
+
|
164
|
+
def options_leftovers
|
165
|
+
@options_leftovers ||= []
|
166
|
+
end
|
167
|
+
end
|
168
|
+
end
|
169
|
+
|
170
|
+
require_relative 'options/error'
|
171
|
+
require_relative 'options/set'
|