toys-core 0.7.0 → 0.8.0

Sign up to get free protection for your applications and to get access to all the features.
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