toys-core 0.7.0 → 0.8.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.
Files changed (59) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +98 -0
  3. data/LICENSE.md +16 -24
  4. data/README.md +307 -59
  5. data/docs/guide.md +44 -4
  6. data/lib/toys-core.rb +58 -49
  7. data/lib/toys/acceptor.rb +672 -0
  8. data/lib/toys/alias.rb +106 -0
  9. data/lib/toys/arg_parser.rb +624 -0
  10. data/lib/toys/cli.rb +422 -181
  11. data/lib/toys/compat.rb +83 -0
  12. data/lib/toys/completion.rb +442 -0
  13. data/lib/toys/context.rb +354 -0
  14. data/lib/toys/core_version.rb +18 -26
  15. data/lib/toys/dsl/flag.rb +213 -56
  16. data/lib/toys/dsl/flag_group.rb +237 -51
  17. data/lib/toys/dsl/positional_arg.rb +210 -0
  18. data/lib/toys/dsl/tool.rb +968 -317
  19. data/lib/toys/errors.rb +46 -28
  20. data/lib/toys/flag.rb +821 -0
  21. data/lib/toys/flag_group.rb +282 -0
  22. data/lib/toys/input_file.rb +18 -26
  23. data/lib/toys/loader.rb +110 -100
  24. data/lib/toys/middleware.rb +24 -31
  25. data/lib/toys/mixin.rb +90 -59
  26. data/lib/toys/module_lookup.rb +125 -0
  27. data/lib/toys/positional_arg.rb +184 -0
  28. data/lib/toys/source_info.rb +192 -0
  29. data/lib/toys/standard_middleware/add_verbosity_flags.rb +38 -43
  30. data/lib/toys/standard_middleware/handle_usage_errors.rb +39 -40
  31. data/lib/toys/standard_middleware/set_default_descriptions.rb +111 -89
  32. data/lib/toys/standard_middleware/show_help.rb +130 -113
  33. data/lib/toys/standard_middleware/show_root_version.rb +29 -35
  34. data/lib/toys/standard_mixins/exec.rb +116 -78
  35. data/lib/toys/standard_mixins/fileutils.rb +16 -24
  36. data/lib/toys/standard_mixins/gems.rb +29 -30
  37. data/lib/toys/standard_mixins/highline.rb +34 -41
  38. data/lib/toys/standard_mixins/terminal.rb +72 -26
  39. data/lib/toys/template.rb +51 -35
  40. data/lib/toys/tool.rb +1161 -206
  41. data/lib/toys/utils/completion_engine.rb +171 -0
  42. data/lib/toys/utils/exec.rb +279 -182
  43. data/lib/toys/utils/gems.rb +58 -49
  44. data/lib/toys/utils/help_text.rb +117 -111
  45. data/lib/toys/utils/terminal.rb +69 -62
  46. data/lib/toys/wrappable_string.rb +162 -0
  47. metadata +24 -22
  48. data/lib/toys/definition/acceptor.rb +0 -191
  49. data/lib/toys/definition/alias.rb +0 -112
  50. data/lib/toys/definition/arg.rb +0 -140
  51. data/lib/toys/definition/flag.rb +0 -370
  52. data/lib/toys/definition/flag_group.rb +0 -205
  53. data/lib/toys/definition/source_info.rb +0 -190
  54. data/lib/toys/definition/tool.rb +0 -842
  55. data/lib/toys/dsl/arg.rb +0 -132
  56. data/lib/toys/runner.rb +0 -188
  57. data/lib/toys/standard_middleware.rb +0 -47
  58. data/lib/toys/utils/module_lookup.rb +0 -135
  59. data/lib/toys/utils/wrappable_string.rb +0 -165
@@ -1,191 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright 2018 Daniel Azuma
4
- #
5
- # All rights reserved.
6
- #
7
- # Redistribution and use in source and binary forms, with or without
8
- # modification, are permitted provided that the following conditions are met:
9
- #
10
- # * Redistributions of source code must retain the above copyright notice,
11
- # this list of conditions and the following disclaimer.
12
- # * Redistributions in binary form must reproduce the above copyright notice,
13
- # this list of conditions and the following disclaimer in the documentation
14
- # and/or other materials provided with the distribution.
15
- # * Neither the name of the copyright holder, nor the names of any other
16
- # contributors to this software, may be used to endorse or promote products
17
- # derived from this software without specific prior written permission.
18
- #
19
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
- # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
- # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
- # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
- # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
- # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
- # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
- # POSSIBILITY OF SUCH DAMAGE.
30
- ;
31
-
32
- module Toys
33
- module Definition
34
- ##
35
- # An Acceptor validates and converts arguments. It is designed to be
36
- # compatible with the OptionParser accept mechanism.
37
- #
38
- # First, an acceptor validates an argument via the {#match} method. This
39
- # method should determine whether the argument is valid, and return
40
- # information that will help with conversion of the argument.
41
- #
42
- # Second, an acceptor converts the argument from the input string to its
43
- # final form via the {#convert} method.
44
- #
45
- # Finally, an acceptor has a name that may appear in help text for flags
46
- # and arguments that use it.
47
- #
48
- class Acceptor
49
- ##
50
- # Create a base acceptor.
51
- #
52
- # The base acceptor does not do any validation (i.e. it accepts all
53
- # arguments). You may subclass this object and override the {#match}
54
- # method to change this behavior.
55
- #
56
- # The base acceptor lets you provide a converter as a proc. The proc
57
- # should take one or more arguments, the first of which is the entire
58
- # argument string, and the others of which are any additional values
59
- # returned from validation. The converter should return the final
60
- # converted value of the argument.
61
- #
62
- # The converter may be provided either as a proc in the `converter`
63
- # parameter, or as a block. If neither is provided, the base acceptor
64
- # performs no conversion and uses the argument string.
65
- #
66
- # @param [String] name A visible name for the acceptor, shown in help.
67
- # @param [Proc] converter A converter function. May also be given as a
68
- # block.
69
- #
70
- def initialize(name, converter = nil, &block)
71
- @name = name.to_s
72
- @converter = converter || block
73
- end
74
-
75
- ##
76
- # Name of the acceptor
77
- # @return [String]
78
- #
79
- attr_reader :name
80
- alias to_s name
81
-
82
- ##
83
- # Validate the given input.
84
- #
85
- # You may override this method to specify a validation function. For a
86
- # valid input, the function must return either the original argument
87
- # string, or an array of which the first element is the original argument
88
- # string, and the remaining elements may comprise additional information.
89
- # All returned information is then passed to the conversion function.
90
- # Note that a MatchInfo object is a legitimate return value since it
91
- # duck-types the appropriate array.
92
- #
93
- # For an invalid input, you should return a falsy value.
94
- #
95
- # The default implementation simply returns the original argument string,
96
- # indicating all inputs are valid.
97
- #
98
- # @param [String] str Input argument string
99
- # @return [String,Array]
100
- #
101
- def match(str)
102
- str
103
- end
104
-
105
- ##
106
- # Convert the given input. Uses the converter provided to this object's
107
- # constructor. Subclasses may also override this method.
108
- #
109
- # @param [String] str Original argument string
110
- # @param [Object...] extra Zero or more additional arguments comprising
111
- # additional elements returned from the match function.
112
- # @return [Object] The converted argument as it should be stored in the
113
- # context data.
114
- #
115
- def convert(str, *extra)
116
- @converter ? @converter.call(str, *extra) : str
117
- end
118
- end
119
-
120
- ##
121
- # An acceptor that uses a regex to validate input.
122
- #
123
- class PatternAcceptor < Acceptor
124
- ##
125
- # Create a pattern acceptor.
126
- #
127
- # You must provide a regular expression as a validator. You may also
128
- # provide a converter proc. See {Toys::Definition::Acceptor} for details
129
- # on the converter.
130
- #
131
- # @param [String] name A visible name for the acceptor, shown in help.
132
- # @param [Regexp] regex Regular expression defining value values.
133
- # @param [Proc] converter A converter function. May also be given as a
134
- # block. Note that the converter will be passed all elements of
135
- # the MatchInfo.
136
- #
137
- def initialize(name, regex, converter = nil, &block)
138
- super(name, converter, &block)
139
- @regex = regex
140
- end
141
-
142
- ##
143
- # Overrides {Toys::Definition::Acceptor#match} to use the given regex.
144
- #
145
- def match(str)
146
- @regex.match(str)
147
- end
148
- end
149
-
150
- ##
151
- # An acceptor that recognizes a fixed set of values.
152
- #
153
- # You provide a list of valid values. The input argument string will be
154
- # matched against the string forms of these valid values. If it matches,
155
- # the converter will return the actual value.
156
- #
157
- # For example, you could pass `[:one, :two, 3]` as the set of values. If
158
- # an argument of `"two"` is passed in, the converter will yield a final
159
- # value of the symbol `:two`. If an argument of "3" is passed in, the
160
- # converter will yield the integer `3`. If an argument of "three" is
161
- # passed in, the match will fail.
162
- #
163
- class EnumAcceptor < Acceptor
164
- ##
165
- # Create an acceptor.
166
- #
167
- # @param [String] name A visible name for the acceptor, shown in help.
168
- # @param [Array] values Valid values.
169
- #
170
- def initialize(name, values)
171
- super(name)
172
- @values = Array(values).map { |v| [v.to_s, v] }
173
- end
174
-
175
- ##
176
- # Overrides {Toys::Definition::Acceptor#match} to find the value.
177
- #
178
- def match(str)
179
- @values.find { |s, _e| s == str }
180
- end
181
-
182
- ##
183
- # Overrides {Toys::Definition::Acceptor#convert} to return the original
184
- # element.
185
- #
186
- def convert(_str, elem)
187
- elem
188
- end
189
- end
190
- end
191
- end
@@ -1,112 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright 2018 Daniel Azuma
4
- #
5
- # All rights reserved.
6
- #
7
- # Redistribution and use in source and binary forms, with or without
8
- # modification, are permitted provided that the following conditions are met:
9
- #
10
- # * Redistributions of source code must retain the above copyright notice,
11
- # this list of conditions and the following disclaimer.
12
- # * Redistributions in binary form must reproduce the above copyright notice,
13
- # this list of conditions and the following disclaimer in the documentation
14
- # and/or other materials provided with the distribution.
15
- # * Neither the name of the copyright holder, nor the names of any other
16
- # contributors to this software, may be used to endorse or promote products
17
- # derived from this software without specific prior written permission.
18
- #
19
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
- # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
- # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
- # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
- # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
- # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
- # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
- # POSSIBILITY OF SUCH DAMAGE.
30
- ;
31
-
32
- module Toys
33
- module Definition
34
- ##
35
- # An alias is a name that refers to another name.
36
- #
37
- class Alias
38
- ##
39
- # Create a new alias.
40
- #
41
- # @param [Array<String>] full_name The name of the alias.
42
- # @param [String,Array<String>] target The name of the target. May either
43
- # be a local reference (a single string) or a global reference (an
44
- # array of strings)
45
- #
46
- def initialize(loader, full_name, target, priority)
47
- @target_name =
48
- if target.is_a?(::Array)
49
- target.map(&:to_s)
50
- else
51
- full_name[0..-2] + [target.to_s]
52
- end
53
- @target_name.freeze
54
- @full_name = full_name.map(&:to_s).freeze
55
- @priority = priority
56
- @tool_class = DSL::Tool.new_class(@full_name, priority, loader)
57
- end
58
-
59
- ##
60
- # Return the tool class.
61
- # @return [Class]
62
- #
63
- attr_reader :tool_class
64
-
65
- ##
66
- # Return the name of the tool as an array of strings.
67
- # This array may not be modified.
68
- # @return [Array<String>]
69
- #
70
- attr_reader :full_name
71
-
72
- ##
73
- # Return the priority of this alias.
74
- # @return [Integer]
75
- #
76
- attr_reader :priority
77
-
78
- ##
79
- # Return the name of the target as an array of strings.
80
- # This array may not be modified.
81
- # @return [Array<String>]
82
- #
83
- attr_reader :target_name
84
-
85
- ##
86
- # Returns the local name of this tool.
87
- # @return [String]
88
- #
89
- def simple_name
90
- full_name.last
91
- end
92
-
93
- ##
94
- # Returns a displayable name of this tool, generally the full name
95
- # delimited by spaces.
96
- # @return [String]
97
- #
98
- def display_name
99
- full_name.join(" ")
100
- end
101
-
102
- ##
103
- # Returns a displayable name of the target, generally the full name
104
- # delimited by spaces.
105
- # @return [String]
106
- #
107
- def display_target
108
- target_name.join(" ")
109
- end
110
- end
111
- end
112
- end
@@ -1,140 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright 2018 Daniel Azuma
4
- #
5
- # All rights reserved.
6
- #
7
- # Redistribution and use in source and binary forms, with or without
8
- # modification, are permitted provided that the following conditions are met:
9
- #
10
- # * Redistributions of source code must retain the above copyright notice,
11
- # this list of conditions and the following disclaimer.
12
- # * Redistributions in binary form must reproduce the above copyright notice,
13
- # this list of conditions and the following disclaimer in the documentation
14
- # and/or other materials provided with the distribution.
15
- # * Neither the name of the copyright holder, nor the names of any other
16
- # contributors to this software, may be used to endorse or promote products
17
- # derived from this software without specific prior written permission.
18
- #
19
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
- # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
- # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
- # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
- # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
- # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
- # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
- # POSSIBILITY OF SUCH DAMAGE.
30
- ;
31
-
32
- require "optparse"
33
-
34
- module Toys
35
- module Definition
36
- ##
37
- # Representation of a formal positional argument
38
- #
39
- class Arg
40
- ##
41
- # Create an Arg definition
42
- # @private
43
- #
44
- def initialize(key, type, accept, default, desc, long_desc, display_name = nil)
45
- @key = key
46
- @type = type
47
- @accept = accept
48
- @default = default
49
- @desc = Utils::WrappableString.make(desc)
50
- @long_desc = Utils::WrappableString.make_array(long_desc)
51
- @display_name = display_name || key.to_s.tr("-", "_").gsub(/\W/, "").upcase
52
- end
53
-
54
- ##
55
- # Returns the key.
56
- # @return [Symbol]
57
- #
58
- attr_reader :key
59
-
60
- ##
61
- # Type of this argument.
62
- # @return [:required,:optional,:remaining]
63
- #
64
- attr_reader :type
65
-
66
- ##
67
- # Returns the acceptor, which may be `nil`.
68
- # @return [Object]
69
- #
70
- attr_accessor :accept
71
-
72
- ##
73
- # Returns the default value, which may be `nil`.
74
- # @return [Object]
75
- #
76
- attr_reader :default
77
-
78
- ##
79
- # Returns the short description string.
80
- # @return [Toys::Utils::WrappableString]
81
- #
82
- attr_reader :desc
83
-
84
- ##
85
- # Returns the long description strings as an array.
86
- # @return [Array<Toys::Utils::WrappableString>]
87
- #
88
- attr_reader :long_desc
89
-
90
- ##
91
- # Returns the displayable name.
92
- # @return [String]
93
- #
94
- attr_accessor :display_name
95
-
96
- ##
97
- # Process the given value through the acceptor.
98
- # May raise an exception if the acceptor rejected the input.
99
- #
100
- # @param [String] input Input value
101
- # @return [Object] Accepted value
102
- #
103
- def process_value(input)
104
- return input unless accept
105
- result = input
106
- optparse = ::OptionParser.new
107
- optparse.accept(accept) if accept.is_a?(Acceptor)
108
- optparse.on("--abc VALUE", accept) { |v| result = v }
109
- optparse.parse(["--abc", input])
110
- result
111
- end
112
-
113
- ##
114
- # Set the short description string.
115
- #
116
- # The description may be provided as a {Toys::Utils::WrappableString}, a
117
- # single string (which will be wrapped), or an array of strings, which will
118
- # be interpreted as string fragments that will be concatenated and wrapped.
119
- #
120
- # @param [Toys::Utils::WrappableString,String,Array<String>] desc
121
- #
122
- def desc=(desc)
123
- @desc = Utils::WrappableString.make(desc)
124
- end
125
-
126
- ##
127
- # Set the long description strings.
128
- #
129
- # Each string may be provided as a {Toys::Utils::WrappableString}, a single
130
- # string (which will be wrapped), or an array of strings, which will be
131
- # interpreted as string fragments that will be concatenated and wrapped.
132
- #
133
- # @param [Array<Toys::Utils::WrappableString,String,Array<String>>] long_desc
134
- #
135
- def long_desc=(long_desc)
136
- @long_desc = Utils::WrappableString.make_array(long_desc)
137
- end
138
- end
139
- end
140
- end
@@ -1,370 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- # Copyright 2018 Daniel Azuma
4
- #
5
- # All rights reserved.
6
- #
7
- # Redistribution and use in source and binary forms, with or without
8
- # modification, are permitted provided that the following conditions are met:
9
- #
10
- # * Redistributions of source code must retain the above copyright notice,
11
- # this list of conditions and the following disclaimer.
12
- # * Redistributions in binary form must reproduce the above copyright notice,
13
- # this list of conditions and the following disclaimer in the documentation
14
- # and/or other materials provided with the distribution.
15
- # * Neither the name of the copyright holder, nor the names of any other
16
- # contributors to this software, may be used to endorse or promote products
17
- # derived from this software without specific prior written permission.
18
- #
19
- # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
20
- # AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
21
- # IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
22
- # ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE
23
- # LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
24
- # CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
25
- # SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
26
- # INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
27
- # CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
28
- # ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
29
- # POSSIBILITY OF SUCH DAMAGE.
30
- ;
31
-
32
- module Toys
33
- module Definition
34
- ##
35
- # Representation of a single flag.
36
- #
37
- class FlagSyntax
38
- ##
39
- # Parse flag syntax
40
- # @param [String] str syntax.
41
- #
42
- def initialize(str)
43
- case str
44
- when /^(-([\?\w]))$/
45
- setup(str, [$1], $1, $2, "-", nil, nil, nil, nil)
46
- when /^(-([\?\w]))( ?)\[(\w+)\]$/
47
- setup(str, [$1], $1, $2, "-", :value, :optional, $3, $4)
48
- when /^(-([\?\w]))\[( )(\w+)\]$/
49
- setup(str, [$1], $1, $2, "-", :value, :optional, $3, $4)
50
- when /^(-([\?\w]))( ?)(\w+)$/
51
- setup(str, [$1], $1, $2, "-", :value, :required, $3, $4)
52
- when /^--\[no-\](\w[\?\w-]*)$/
53
- setup(str, ["--#{$1}", "--no-#{$1}"], str, $1, "--", :boolean, nil, nil, nil)
54
- when /^(--(\w[\?\w-]*))$/
55
- setup(str, [$1], $1, $2, "--", nil, nil, nil, nil)
56
- when /^(--(\w[\?\w-]*))([= ])\[(\w+)\]$/
57
- setup(str, [$1], $1, $2, "--", :value, :optional, $3, $4)
58
- when /^(--(\w[\?\w-]*))\[([= ])(\w+)\]$/
59
- setup(str, [$1], $1, $2, "--", :value, :optional, $3, $4)
60
- when /^(--(\w[\?\w-]*))([= ])(\w+)$/
61
- setup(str, [$1], $1, $2, "--", :value, :required, $3, $4)
62
- else
63
- raise ToolDefinitionError, "Illegal flag: #{str.inspect}"
64
- end
65
- end
66
-
67
- attr_reader :original_str
68
- attr_reader :flags
69
- attr_reader :str_without_value
70
- attr_reader :sort_str
71
- attr_reader :flag_style
72
- attr_reader :flag_type
73
- attr_reader :value_type
74
- attr_reader :value_delim
75
- attr_reader :value_label
76
- attr_reader :canonical_str
77
-
78
- ## @private
79
- def configure_canonical(canonical_flag_type, canonical_value_type,
80
- canonical_value_label, canonical_value_delim)
81
- return unless flag_type.nil?
82
- @flag_type = canonical_flag_type
83
- return unless canonical_flag_type == :value
84
- @value_type = canonical_value_type
85
- canonical_value_delim = "" if canonical_value_delim == "=" && flag_style == "-"
86
- canonical_value_delim = "=" if canonical_value_delim == "" && flag_style == "--"
87
- @value_delim = canonical_value_delim
88
- @value_label = canonical_value_label
89
- label = @value_type == :optional ? "[#{@value_label}]" : @value_label
90
- @canonical_str = "#{str_without_value}#{@value_delim}#{label}"
91
- end
92
-
93
- private
94
-
95
- def setup(original_str, flags, str_without_value, sort_str, flag_style, flag_type,
96
- value_type, value_delim, value_label)
97
- @original_str = original_str
98
- @flags = flags
99
- @str_without_value = str_without_value
100
- @sort_str = sort_str
101
- @flag_style = flag_style
102
- @flag_type = flag_type
103
- @value_type = value_type
104
- @value_delim = value_delim
105
- @value_label = value_label ? value_label.upcase : value_label
106
- @canonical_str = original_str
107
- end
108
- end
109
-
110
- ##
111
- # Representation of a formal set of flags that set a particular context
112
- # key. The flags within a single Flag definition are synonyms.
113
- #
114
- class Flag
115
- ##
116
- # The default handler replaces the previous value.
117
- # @return [Proc]
118
- #
119
- DEFAULT_HANDLER = ->(val, _prev) { val }
120
-
121
- ##
122
- # Create a Flag definition
123
- # @private
124
- #
125
- def initialize(key, flags, used_flags, report_collisions, accept, handler,
126
- default, display_name, group)
127
- @group = group
128
- @key = key
129
- @flag_syntax = flags.map { |s| FlagSyntax.new(s) }
130
- @accept = accept
131
- @handler = handler || DEFAULT_HANDLER
132
- @desc = Utils::WrappableString.make(desc)
133
- @long_desc = Utils::WrappableString.make_array(long_desc)
134
- @default = default
135
- needs_val = (!accept.nil? && accept != ::TrueClass && accept != ::FalseClass) ||
136
- (!default.nil? && default != true && default != false)
137
- create_default_flag_if_needed(needs_val)
138
- remove_used_flags(used_flags, report_collisions)
139
- canonicalize(needs_val)
140
- summarize(display_name)
141
- end
142
-
143
- ##
144
- # Returns the flag group containing this flag
145
- # @return [Toys::Definition::FlagGroup]
146
- #
147
- attr_reader :group
148
-
149
- ##
150
- # Returns the key.
151
- # @return [Symbol]
152
- #
153
- attr_reader :key
154
-
155
- ##
156
- # Returns an array of FlagSyntax for the flags.
157
- # @return [Array<FlagSyntax>]
158
- #
159
- attr_reader :flag_syntax
160
-
161
- ##
162
- # Returns the acceptor, which may be `nil`.
163
- # @return [Object]
164
- #
165
- attr_reader :accept
166
-
167
- ##
168
- # Returns the default value, which may be `nil`.
169
- # @return [Object]
170
- #
171
- attr_reader :default
172
-
173
- ##
174
- # Returns the short description string.
175
- # @return [Toys::Utils::WrappableString]
176
- #
177
- attr_reader :desc
178
-
179
- ##
180
- # Returns the long description strings as an array.
181
- # @return [Array<Toys::Utils::WrappableString>]
182
- #
183
- attr_reader :long_desc
184
-
185
- ##
186
- # Returns the handler for setting/updating the value.
187
- # @return [Proc]
188
- #
189
- attr_reader :handler
190
-
191
- ##
192
- # The type of flag. Possible values are `:boolean` for a simple boolean
193
- # switch, or `:value` for a flag that sets a value.
194
- # @return [:boolean,:value]
195
- #
196
- attr_reader :flag_type
197
-
198
- ##
199
- # The type of value. Set to `:required` or `:optional` if the flag type
200
- # is `:value`. Otherwise set to `nil`.
201
- # @return [:required,:optional,nil]
202
- #
203
- attr_reader :value_type
204
-
205
- ##
206
- # The string label for the value as it should display in help, or `nil`
207
- # if the flag type is not `:value`.
208
- # @return [String,nil]
209
- #
210
- attr_reader :value_label
211
-
212
- ##
213
- # The value delimiter, which may be `""`, `" "`, or `"="`. Set to `nil`
214
- # if the flag type is not `:value`.
215
- # @return [String,nil]
216
- #
217
- attr_reader :value_delim
218
-
219
- ##
220
- # Returns the display name of this flag.
221
- # @return [String]
222
- #
223
- attr_reader :display_name
224
-
225
- ##
226
- # Returns a string that can be used to sort this flag
227
- # @return [String]
228
- #
229
- attr_reader :sort_str
230
-
231
- ##
232
- # Returns an array of FlagSyntax including only single-dash flags
233
- # @return [Array<FlagSyntax>]
234
- #
235
- def single_flag_syntax
236
- @single_flag_syntax ||= flag_syntax.find_all { |ss| ss.flag_style == "-" }
237
- end
238
-
239
- ##
240
- # Returns an array of FlagSyntax including only double-dash flags
241
- # @return [Array<FlagSyntax>]
242
- #
243
- def double_flag_syntax
244
- @double_flag_syntax ||= flag_syntax.find_all { |ss| ss.flag_style == "--" }
245
- end
246
-
247
- ##
248
- # Returns the list of effective flags used.
249
- # @return [Array<String>]
250
- #
251
- def effective_flags
252
- @effective_flags ||= flag_syntax.map(&:flags).flatten
253
- end
254
-
255
- ##
256
- # Returns a list suitable for passing to OptionParser.
257
- # @return [Array]
258
- #
259
- def optparser_info
260
- @optparser_info ||= flag_syntax.map(&:canonical_str) + Array(accept)
261
- end
262
-
263
- ##
264
- # Returns true if this flag is active. That is, it has a nonempty
265
- # flags list.
266
- # @return [Boolean]
267
- #
268
- def active?
269
- !effective_flags.empty?
270
- end
271
-
272
- ##
273
- # Set the short description string.
274
- #
275
- # The description may be provided as a {Toys::Utils::WrappableString}, a
276
- # single string (which will be wrapped), or an array of strings, which will
277
- # be interpreted as string fragments that will be concatenated and wrapped.
278
- #
279
- # @param [Toys::Utils::WrappableString,String,Array<String>] desc
280
- #
281
- def desc=(desc)
282
- @desc = Utils::WrappableString.make(desc)
283
- end
284
-
285
- ##
286
- # Set the long description strings.
287
- #
288
- # Each string may be provided as a {Toys::Utils::WrappableString}, a single
289
- # string (which will be wrapped), or an array of strings, which will be
290
- # interpreted as string fragments that will be concatenated and wrapped.
291
- #
292
- # @param [Array<Toys::Utils::WrappableString,String,Array<String>>] long_desc
293
- #
294
- def long_desc=(long_desc)
295
- @long_desc = Utils::WrappableString.make_array(long_desc)
296
- end
297
-
298
- private
299
-
300
- def create_default_flag_if_needed(needs_val)
301
- return unless @flag_syntax.empty?
302
- canonical_flag = key.to_s.downcase.tr("_", "-").gsub(/[^a-z0-9-]/, "").sub(/^-+/, "")
303
- unless canonical_flag.empty?
304
- flag = needs_val ? "--#{canonical_flag} VALUE" : "--#{canonical_flag}"
305
- @flag_syntax << FlagSyntax.new(flag)
306
- end
307
- end
308
-
309
- def remove_used_flags(used_flags, report_collisions)
310
- @flag_syntax.select! do |fs|
311
- fs.flags.all? do |f|
312
- collision = used_flags.include?(f)
313
- if collision && report_collisions
314
- raise ToolDefinitionError,
315
- "Cannot use flag #{f.inspect} because it is already assigned or reserved."
316
- end
317
- !collision
318
- end
319
- end
320
- used_flags.concat(effective_flags.uniq)
321
- end
322
-
323
- def canonicalize(needs_val)
324
- @flag_type = needs_val ? :value : nil
325
- @value_type = nil
326
- @value_label = needs_val ? "VALUE" : nil
327
- @value_delim = " "
328
- single_flag_syntax.reverse_each do |flag|
329
- analyze_flag_syntax(flag)
330
- end
331
- double_flag_syntax.reverse_each do |flag|
332
- analyze_flag_syntax(flag)
333
- end
334
- @flag_type ||= :boolean
335
- @value_type ||= :required if @flag_type == :value
336
- flag_syntax.each do |flag|
337
- flag.configure_canonical(@flag_type, @value_type, @value_label, @value_delim)
338
- end
339
- end
340
-
341
- def analyze_flag_syntax(flag)
342
- return if flag.flag_type.nil?
343
- if !@flag_type.nil? && @flag_type != flag.flag_type
344
- raise ToolDefinitionError, "Cannot have both value and boolean flags for #{key.inspect}"
345
- end
346
- @flag_type = flag.flag_type
347
- return unless @flag_type == :value
348
- if !@value_type.nil? && @value_type != flag.value_type
349
- raise ToolDefinitionError,
350
- "Cannot have both required and optional values for flag #{key.inspect}"
351
- end
352
- @value_type = flag.value_type
353
- @value_label = flag.value_label
354
- @value_delim = flag.value_delim
355
- end
356
-
357
- def summarize(name)
358
- @display_name =
359
- name ||
360
- double_flag_syntax.first&.canonical_str ||
361
- single_flag_syntax.first&.canonical_str ||
362
- key.to_s
363
- @sort_str =
364
- double_flag_syntax.first&.sort_str ||
365
- single_flag_syntax.first&.sort_str ||
366
- ""
367
- end
368
- end
369
- end
370
- end