toys-core 0.13.1 → 0.14.2

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 7d34e973aef54758dbd85e689d69bd68660024a41e344c40bdc64b84459e5b31
4
- data.tar.gz: 1c488073e098663bf9b2e6b9434c8c1932eeb66411855a4810cb57eca4ea1445
3
+ metadata.gz: de679852961f700752c16ccbec8b08bf28390070ce6e7a29f6d34bf6dd9a1ba3
4
+ data.tar.gz: b149a5af7458b8ef26bb2a12dcc3ede565af9450780e8de7d132576550c65217
5
5
  SHA512:
6
- metadata.gz: '09e199be2b41bc50a6a8e2d8a03f91be92c2f04f022abc5a6a64f4077f745a445777369e4a20787a4754fe8b709e5cfde138e65eee84ec3f4f6d21ca089072fc'
7
- data.tar.gz: b874efa699f0c7d76ffaa6848fb30668d830b9a1b8a342fed13283586829069cefdbe7a379a90749c365c510affa06394d82d21e601c3b6bd868f001f4287350
6
+ metadata.gz: f7dc86145d43b4c778da09c6f31195a89aa62eab6a2bb4ef2efe1100b18863b17854540f07711c255c9eb95f8f2232c53808bdef87e0237fd8ec541c0bbc79cd
7
+ data.tar.gz: 50f4c29636dca1fe75c94a7d53dc23315ba6f49e5f7e1ac39e13cd4f0459fbdf57b3eccee159558476042f9a8aaa831f7bcf3c86bb2dd976b02f63e029fe67a8
data/CHANGELOG.md CHANGED
@@ -1,5 +1,41 @@
1
1
  # Release History
2
2
 
3
+ ### v0.14.2 / 2022-10-09
4
+
5
+ * ADDED: The tool directive supports the delegate_relative argument, as a preferred alternative over alias_tool.
6
+ * FIXED: The toys file reference now properly appears in error messages on Ruby 3.1.
7
+ * FIXED: Error messages show the correct toys file line number on TruffleRuby.
8
+ * FIXED: Inspect strings for tool classes are less opaque and include the tool name.
9
+ * FIXED: The presence of an acceptor forces an ambiguous flag to take a value rather than erroring.
10
+
11
+ ### v0.14.1 / 2022-10-03
12
+
13
+ * (No significant changes)
14
+
15
+ ### v0.14.0 / 2022-10-03
16
+
17
+ Toys-Core 0.14.0 is a major release with pager support, support for tees and pipes in the Exec utility, some cleanup of the behavior of Acceptors, and other improvements.
18
+
19
+ Fixes that are potentially breaking:
20
+
21
+ * Disallowed acceptors on flags that are explicitly boolean.
22
+ * Acceptors no longer sometimes apply to the boolean setting of a flag with an optional value.
23
+
24
+ New functionality:
25
+
26
+ * Implemented a utility class and mixin for output pagers.
27
+ * Builtin commands that display data can format as either YAML or JSON.
28
+ * The Exec utility and mixin can tee (i.e. duplicate and split) output streams.
29
+ * The Exec utility and mixin can take pipes as input and output streams.
30
+ * The Exec mixin provides a `verbosity_flags` convenience method.
31
+ * `Loader#list_subtools` takes separate arguments for filtering namespaces and non-runnable tools
32
+
33
+ Fixes:
34
+
35
+ * Contents of preload directories are loaded in sorted order.
36
+ * Removed some potential deadlocks if a `Toys::Loader` is accessed from multiple threads
37
+ * Various clarifications, fixes, and updates to the users guide and documentation.
38
+
3
39
  ### v0.13.1 / 2022-03-01
4
40
 
5
41
  * FIXED: Toys::Utils::Gems no longer fails to install a bundle if it had locked to a different version of a builtin gem
data/README.md CHANGED
@@ -25,23 +25,15 @@ recommend first walking through the tutorial in the
25
25
  you are running a unix-like system such as Linux or macOS. Some commands might
26
26
  need to be modified if you're running on Windows.
27
27
 
28
- ### Install Toys
28
+ ### Install Toys-Core
29
29
 
30
30
  Install the **toys-core** gem using:
31
31
 
32
32
  $ gem install toys-core
33
33
 
34
- You may also install the **toys** gem, which brings in **toys-core** as a
34
+ You can also install the **toys** gem, which brings in **toys-core** as a
35
35
  dependency.
36
36
 
37
- Toys-Core requires Ruby 2.4 or later.
38
-
39
- Most parts of Toys-Core work on JRuby. However, JRuby is not recommended
40
- because of JVM boot latency, lack of support for Kernel#fork, and other issues.
41
-
42
- Most parts of Toys-Core work on TruffleRuby. However, TruffleRuby is not
43
- recommended because it has a few known bugs that affect Toys.
44
-
45
37
  ### Create a new executable
46
38
 
47
39
  We'll start by creating an executable Ruby script. Using your favorite text
@@ -77,7 +69,7 @@ use to write Toys files. You could point your executable at a directory
77
69
  containing actual Toys files, but the simplest option is to provide the
78
70
  information to the Toys CLI object in a block.
79
71
 
80
- Let's add some functionality.
72
+ Let's add some functionality to `mycmd`.
81
73
 
82
74
  #!/usr/bin/env ruby
83
75
 
@@ -107,7 +99,7 @@ look familiar. Let's run it now, and experiment with passing flags to it.
107
99
  Notice that we did not create a `tool` block, but instead set up description,
108
100
  flags, and functionality directly in the configuration block. This configures
109
101
  the "root tool", i.e. what happens when you run the executable without passing
110
- a tool name to it. (Note, it's legal to do this in Toys as well, by setting
102
+ a tool name to it. (In fact, it's legal to do this in Toys as well, by setting
111
103
  functionality at the "top level" of a `.toys.rb` file without including any
112
104
  `tool` block.)
113
105
 
@@ -172,8 +164,8 @@ modifies error handling and delimiter parsing.
172
164
  #### Pass some additional options to the CLI constructor ...
173
165
  cli = Toys::CLI.new(
174
166
  extra_delimiters: ":",
175
- error_handler: ->(_err) {
176
- puts "Dude, an error happened..."
167
+ error_handler: ->(err) {
168
+ puts "Aww shucks, an error happened: #{err.message}"
177
169
  return 1
178
170
  }
179
171
  )
@@ -209,8 +201,8 @@ Toys _middleware_ are objects that provide common functionality for all the
209
201
  tools in your executable. For example, a middleware adds the `--help` flag to
210
202
  your tools by default.
211
203
 
212
- The next example modifies the middleware stack to alter this common tool
213
- functionality.
204
+ The next example provides a custom middleware stack, resulting in a different
205
+ set of common tool functionality.
214
206
 
215
207
  #!/usr/bin/env ruby
216
208
 
@@ -339,6 +331,16 @@ handling errors, controlling log output, and providing your own mixins,
339
331
  templates, and middleware, in the
340
332
  [Toys-Core User Guide](https://dazuma.github.io/toys/gems/toys-core/latest/file.guide.html).
341
333
 
334
+ ## System requirements
335
+
336
+ Toys-Core requires Ruby 2.4 or later.
337
+
338
+ Most parts of Toys-Core work on JRuby. However, JRuby is not recommended
339
+ because of JVM boot latency, lack of support for Kernel#fork, and other issues.
340
+
341
+ Most parts of Toys-Core work on TruffleRuby. However, TruffleRuby is not
342
+ recommended because it has a few known bugs that affect Toys.
343
+
342
344
  ## License
343
345
 
344
346
  Copyright 2019-2022 Daniel Azuma and the Toys contributors
data/docs/guide.md CHANGED
@@ -29,7 +29,7 @@ sophisticated command line tools.
29
29
 
30
30
  ## Conceptual overview
31
31
 
32
- Toys-Core is a command line *framework* in the traditional sense. It is
32
+ Toys-Core is a **command line framework** in the traditional sense. It is
33
33
  intended to be used to write custom command line executables in Ruby. The
34
34
  framework provides common facilities such as argument parsing and online help,
35
35
  while your executable chooses and configures those facilities, and implements
@@ -44,7 +44,7 @@ written in **toys files** or in **blocks** passed to the CLI. It uses the same
44
44
  DSL used by Toys itself, and supports tools, subtools, flags, arguments, help
45
45
  text, and all the other features of Toys.
46
46
 
47
- An executable may customize its own facilities for writing tools by providing
47
+ An executable can customize its own facilities for writing tools by providing
48
48
  **built-in mixins** and **built-in templates**, and can implement default
49
49
  behavior across all tools by providing **middleware**.
50
50
 
@@ -52,7 +52,7 @@ Most executables will provide a set of **static tools**, but it is possible to
52
52
  support user-provided tools as Toys does. Executables can customize how tool
53
53
  definitions are searched and loaded from the file system.
54
54
 
55
- Finally, an executable may customize many aspects of its behavior, such as the
55
+ Finally, an executable can customize many aspects of its behavior, such as the
56
56
  **logging output**, **error handling**, and even shell **tab completion**.
57
57
 
58
58
  ## Using the CLI object
@@ -96,7 +96,109 @@ Following is a simple "hello world" example using the CLI:
96
96
 
97
97
  This section provides some detail on how a CLI executes your code.
98
98
 
99
- (TODO)
99
+ When you call {Toys::CLI#run}, the CLI runs through three phases:
100
+
101
+ * **Loading** in which the CLI identifies which tool to run, and loads the
102
+ tool from a tool **source**, which could be a block passed to the CLI, or a
103
+ file loaded from the file system, git, or other location.
104
+ * **Building context**, in which the CLI parses the command-line arguments
105
+ according to the flags and arguments declared by the tool, instantiates the
106
+ tool, and populates the {Toys::Context} object (which is `self` when the
107
+ tool is executed)
108
+ * **Execution**, which involves running any initializers defined on the tool,
109
+ applying middleware, running the tool, and handling errors.
110
+
111
+ #### The Loader
112
+
113
+ When the CLI needs the definition of a tool, it queries the {Toys::Loader}. The
114
+ loader object is configured with a set of tool _sources_ representing ways to
115
+ define a tool. These sources may be blocks passed directly to the CLI,
116
+ directories and files in the file system, and remote git repositories. When a
117
+ tool is requested by name, the loader is responsible for locating the tool
118
+ definition in those sources, and constructing the tool definition object,
119
+ represented by {Toys::ToolDefinition}.
120
+
121
+ One important property of the loader is that it is _lazy_. It queries tool
122
+ sources only if it has reason to believe that a tool it is looking for may be
123
+ defined there. For example, if your tools are defined in a directory structure,
124
+ the `foo bar` tool might live in the file `foo/bar.rb`. The loader will open
125
+ that file, if it exists, only when the `foo bar` tool is requested. If instead
126
+ `foo qux` is requested, the `foo/bar.rb` file is never even opened.
127
+
128
+ Perhaps more subtly, if you call {Toys::CLI#add_config_block} to define tools,
129
+ the block is stored in the loader object _but not called immediately_. Only
130
+ when a tool is requested does the block actually execute. Furthermore, if you
131
+ have `tool` blocks inside the block, the loader will execute only those that
132
+ are relevant to a tool it wants. Hence:
133
+
134
+ cli.add_config_block do
135
+ tool "foo" do
136
+ def run
137
+ puts "foo called"
138
+ end
139
+ end
140
+
141
+ tool "bar" do
142
+ def run
143
+ puts "bar called"
144
+ end
145
+ end
146
+ end
147
+
148
+ If only `foo` is requested, the loader will execute the `tool "foo" do` block
149
+ to get that tool definition, but will not execute the `tool "bar" do` block.
150
+
151
+ We will discuss more about the features of the loader below in the section on
152
+ [defining functionality](#Defining_functionality).
153
+
154
+ #### Building context
155
+
156
+ Once a tool is defined, the CLI prepares it for execution by building a
157
+ {Toys::Context} object. This object is `self` during tool runtime, and it
158
+ includes:
159
+
160
+ * The tool's methods, including its `run` entrypoint method.
161
+ * Access to core tool functionality such as exit codes and logging.
162
+ * The results from parsing the command line arguments
163
+ * The runtime environment, including the tool's name, where the tool was
164
+ defined, detailed results from argumet parsing, and so forth.
165
+
166
+ Much of this information is stored in a data hash, whose keys are defined as
167
+ constants under {Toys::Context::Key}.
168
+
169
+ Argument parsing is directed by the {Toys::ArgParser} class. This class, for
170
+ the most part, replicates the semantics of the standard Ruby OptionParser
171
+ class, but it implements a few extra features and cleans up a few ambiguities.
172
+
173
+ #### Tool execution and error handling
174
+
175
+ The execution phase involves:
176
+
177
+ * Running the tool's initializers, if any, in order.
178
+ * Running the tool's middleware. Each middleware "wraps" the execution of
179
+ subsequent middleware and the final tool execution, and has the opportunity
180
+ to inject functionality before and after the main execution, or even to
181
+ forgo or replace the main functionality, similar to Rack middleware.
182
+ * Executing the tool itself by calling its `run` method.
183
+
184
+ During execution, exceptions are caught and reported along with the location in
185
+ the tool source where it was triggered. This logic is handled by the
186
+ {Toys::ContextualError} class.
187
+
188
+ The CLI can be configured with an error handler that responds to any exceptions
189
+ raised during execution. An error handler is simply a callable object (such as
190
+ a `Proc`) that takes an exception as an argument. The provided
191
+ {Toys::CLI::DefaultErrorHandler} class provides the default behavior of the
192
+ normal `toys` CLI, but you can provide any object that duck types the `call`
193
+ method.
194
+
195
+ #### Multiple runs
196
+
197
+ The {Toys::CLI} object can be reused to run multiple tools. This may save on
198
+ loading overhead, as the tools can be loaded just once and their definitions
199
+ reused for multiple executions. It can even perform multiple executions
200
+ concurrently in separate threads, assuming the tool implementations themselves
201
+ are thread-safe.
100
202
 
101
203
  ### Configuring the CLI
102
204
 
@@ -117,8 +219,9 @@ These features include:
117
219
 
118
220
  Each of the actual parameters is covered in detail in the documentation for
119
221
  {Toys::CLI#initialize}. The configuration of a CLI cannot be changed once the
120
- CLI is constructed. If you need to a CLI with a modified configuration, use
121
- {Toys::CLI#child}.
222
+ CLI is constructed. If you need a CLI with modified configuration, use
223
+ {Toys::CLI#child}, which creates a _copy_ of the CLI with any modifications you
224
+ request.
122
225
 
123
226
  ## Defining functionality
124
227
 
@@ -151,3 +254,5 @@ CLI is constructed. If you need to a CLI with a modified configuration, use
151
254
  ### Tab completion
152
255
 
153
256
  ## Packaging your executable
257
+
258
+ ## Overview of Toys-Core classes
data/lib/toys/acceptor.rb CHANGED
@@ -83,7 +83,7 @@ module Toys
83
83
  # When given a valid input, return an array in which the first element is
84
84
  # the original input string, and the remaining elements (which may be
85
85
  # empty) comprise any additional information that may be useful during
86
- # conversion. If there is no additional information, you may return the
86
+ # conversion. If there is no additional information, you can return the
87
87
  # original input string by itself without wrapping in an array.
88
88
  #
89
89
  # When given an invalid input, return a falsy value such as `nil`.
@@ -95,8 +95,7 @@ module Toys
95
95
  # as the only array element, indicating all inputs are valid. You can
96
96
  # override this method to provide a different validation function.
97
97
  #
98
- # @param str [String,nil] The input argument string. May be `nil` if the
99
- # value is optional and not provided.
98
+ # @param str [String] The input argument string.
100
99
  # @return [String,Array,nil]
101
100
  #
102
101
  def match(str)
@@ -110,15 +109,14 @@ module Toys
110
109
  # original input string and any other values returned from {#match}. It
111
110
  # must return the final converted value to use.
112
111
  #
113
- # @param str [String,nil] Original argument string. May be `nil` if the
114
- # value is optional and not provided.
112
+ # @param str [String] Original argument string.
115
113
  # @param extra [Object...] Zero or more additional arguments comprising
116
114
  # additional elements returned from the match function.
117
115
  # @return [Object] The converted argument as it should be stored in the
118
116
  # context data.
119
117
  #
120
118
  def convert(str, *extra) # rubocop:disable Lint/UnusedMethodArgument
121
- str.nil? ? true : str
119
+ str
122
120
  end
123
121
 
124
122
  ##
@@ -183,7 +181,7 @@ module Toys
183
181
  #
184
182
  def match(str)
185
183
  result = @function.call(str) rescue REJECT # rubocop:disable Style/RescueModifier
186
- result.equal?(REJECT) ? nil : [str, result]
184
+ REJECT.equal?(result) ? nil : [str, result]
187
185
  end
188
186
 
189
187
  ##
@@ -240,7 +238,7 @@ module Toys
240
238
  # @private
241
239
  #
242
240
  def match(str)
243
- str.nil? ? [nil] : @regex.match(str)
241
+ @regex.match(str)
244
242
  end
245
243
 
246
244
  ##
@@ -293,7 +291,7 @@ module Toys
293
291
  # @private
294
292
  #
295
293
  def match(str)
296
- str.nil? ? [nil, nil] : @values.find { |s, _e| s == str }
294
+ @values.find { |s, _e| s == str }
297
295
  end
298
296
 
299
297
  ##
@@ -418,9 +416,7 @@ module Toys
418
416
  #
419
417
  NUMERIC_CONVERTER =
420
418
  proc do |s|
421
- if s.nil?
422
- nil
423
- elsif s.include?("/")
419
+ if s.include?("/")
424
420
  Rational(s)
425
421
  elsif s.include?(".") || (s.include?("e") && s !~ /\A-?0x/)
426
422
  Float(s)
@@ -429,6 +425,41 @@ module Toys
429
425
  end
430
426
  end
431
427
 
428
+ ##
429
+ # A set of strings that are considered true for boolean acceptors.
430
+ # Currently set to `["+", "true", "yes"]`.
431
+ # @return [Array<String>]
432
+ #
433
+ TRUE_STRINGS = ["+", "true", "yes"].freeze
434
+
435
+ ##
436
+ # A set of strings that are considered false for boolean acceptors.
437
+ # Currently set to `["-", "false", "no", "nil"]`.
438
+ # @return [Array<String>]
439
+ #
440
+ FALSE_STRINGS = ["-", "false", "no", "nil"].freeze
441
+
442
+ ##
443
+ # A converter proc that handles boolean strings. Recognizes {TRUE_STRINGS}
444
+ # and {FALSE_STRINGS}. Useful for Simple acceptors.
445
+ # @return [Proc]
446
+ #
447
+ BOOLEAN_CONVERTER =
448
+ proc do |s|
449
+ if s.empty?
450
+ REJECT
451
+ else
452
+ s = s.downcase
453
+ if TRUE_STRINGS.any? { |t| t.start_with?(s) }
454
+ true
455
+ elsif FALSE_STRINGS.any? { |f| f.start_with?(s) }
456
+ false
457
+ else
458
+ REJECT
459
+ end
460
+ end
461
+ end
462
+
432
463
  class << self
433
464
  ##
434
465
  # Lookup a standard acceptor name recognized by OptionParser.
@@ -521,7 +552,11 @@ module Toys
521
552
  end
522
553
 
523
554
  ##
524
- # @private
555
+ # Take the various ways to express an acceptor spec, and convert them to
556
+ # a canonical form expressed as a single object. This is called from the
557
+ # DSL to generate a spec object that can be stored.
558
+ #
559
+ # @private This interface is internal and subject to change without warning.
525
560
  #
526
561
  def scalarize_spec(spec, options, block)
527
562
  spec ||= block
@@ -564,8 +599,8 @@ module Toys
564
599
  ::Float => build_float,
565
600
  ::Rational => build_rational,
566
601
  ::Numeric => build_numeric,
567
- ::TrueClass => build_boolean(::TrueClass, true),
568
- ::FalseClass => build_boolean(::FalseClass, false),
602
+ ::TrueClass => build_boolean(::TrueClass),
603
+ ::FalseClass => build_boolean(::FalseClass),
569
604
  ::Array => build_array,
570
605
  ::Regexp => build_regexp,
571
606
  }
@@ -603,77 +638,48 @@ module Toys
603
638
  Simple.new(NUMERIC_CONVERTER, type_desc: "number", well_known_spec: ::Numeric)
604
639
  end
605
640
 
606
- TRUE_STRINGS = ["+", "true", "yes"].freeze
607
- FALSE_STRINGS = ["-", "false", "no", "nil"].freeze
608
- private_constant :TRUE_STRINGS, :FALSE_STRINGS
609
-
610
- def build_boolean(spec, default)
611
- Simple.new(type_desc: "boolean", well_known_spec: spec) do |s|
612
- if s.nil?
613
- default
614
- elsif s.empty?
615
- REJECT
616
- else
617
- s = s.downcase
618
- if TRUE_STRINGS.any? { |t| t.start_with?(s) }
619
- true
620
- elsif FALSE_STRINGS.any? { |f| f.start_with?(s) }
621
- false
622
- else
623
- REJECT
624
- end
625
- end
626
- end
641
+ def build_boolean(spec)
642
+ Simple.new(BOOLEAN_CONVERTER, type_desc: "boolean", well_known_spec: spec)
627
643
  end
628
644
 
629
645
  def build_array
630
646
  Simple.new(type_desc: "string array", well_known_spec: ::Array) do |s|
631
- if s.nil?
632
- nil
633
- else
634
- s.split(",").collect { |elem| elem unless elem.empty? }
635
- end
647
+ s.split(",").collect { |elem| elem unless elem.empty? }
636
648
  end
637
649
  end
638
650
 
639
651
  def build_regexp
640
652
  Simple.new(type_desc: "regular expression", well_known_spec: ::Regexp) do |s|
641
- if s.nil?
642
- nil
643
- else
644
- flags = 0
645
- if (match = %r{\A/((?:\\.|[^\\])*)/([[:alpha:]]*)\z}.match(s))
646
- s = match[1]
647
- opts = match[2] || ""
648
- flags |= ::Regexp::IGNORECASE if opts.include?("i")
649
- flags |= ::Regexp::MULTILINE if opts.include?("m")
650
- flags |= ::Regexp::EXTENDED if opts.include?("x")
651
- end
652
- ::Regexp.new(s, flags)
653
+ flags = 0
654
+ if (match = %r{\A/((?:\\.|[^\\])*)/([[:alpha:]]*)\z}.match(s))
655
+ s = match[1]
656
+ opts = match[2] || ""
657
+ flags |= ::Regexp::IGNORECASE if opts.include?("i")
658
+ flags |= ::Regexp::MULTILINE if opts.include?("m")
659
+ flags |= ::Regexp::EXTENDED if opts.include?("x")
653
660
  end
661
+ ::Regexp.new(s, flags)
654
662
  end
655
663
  end
656
664
 
657
665
  def build_decimal_integer
658
666
  Simple.new(type_desc: "decimal integer",
659
667
  well_known_spec: ::OptionParser::DecimalInteger) do |s|
660
- s.nil? ? nil : Integer(s, 10)
668
+ Integer(s, 10)
661
669
  end
662
670
  end
663
671
 
664
672
  def build_octal_integer
665
673
  Simple.new(type_desc: "octal integer",
666
674
  well_known_spec: ::OptionParser::OctalInteger) do |s|
667
- s.nil? ? nil : Integer(s, 8)
675
+ Integer(s, 8)
668
676
  end
669
677
  end
670
678
 
671
679
  def build_decimal_numeric
672
680
  Simple.new(type_desc: "decimal number",
673
681
  well_known_spec: ::OptionParser::DecimalNumeric) do |s|
674
- if s.nil?
675
- nil
676
- elsif s.include?(".") || (s.include?("e") && s !~ /\A-?0x/)
682
+ if s.include?(".") || (s.include?("e") && s !~ /\A-?0x/)
677
683
  Float(s)
678
684
  else
679
685
  Integer(s, 10)
@@ -439,7 +439,7 @@ module Toys
439
439
  return false unless @active_flag_def
440
440
  result = @active_flag_def.value_type == :required || !arg.start_with?("-")
441
441
  add_data(@active_flag_def.key, @active_flag_def.handler, @active_flag_def.acceptor,
442
- result ? arg : nil, :flag, @active_flag_arg)
442
+ result ? arg : true, :flag, @active_flag_arg)
443
443
  @seen_flag_keys << @active_flag_def.key
444
444
  @active_flag_def = nil
445
445
  @active_flag_arg = nil
@@ -481,7 +481,7 @@ module Toys
481
481
  @active_flag_def = flag_def
482
482
  @active_flag_arg = name
483
483
  else
484
- add_data(flag_def.key, flag_def.handler, flag_def.acceptor, nil, :flag, name)
484
+ add_data(flag_def.key, flag_def.handler, flag_def.acceptor, true, :flag, name)
485
485
  end
486
486
  else
487
487
  add_data(flag_def.key, flag_def.handler, flag_def.acceptor, following, :flag, name)
@@ -540,7 +540,7 @@ module Toys
540
540
  end
541
541
 
542
542
  def add_data(key, handler, accept, value, type_name, display_name)
543
- if accept
543
+ if accept && value.is_a?(::String)
544
544
  match = accept.match(value)
545
545
  unless match
546
546
  error_class = type_name == :flag ? FlagValueUnacceptableError : ArgValueUnacceptableError
@@ -562,7 +562,7 @@ module Toys
562
562
  @errors << FlagValueMissingError.new(name: @active_flag_arg)
563
563
  else
564
564
  add_data(@active_flag_def.key, @active_flag_def.handler, @active_flag_def.acceptor,
565
- nil, :flag, @active_flag_arg)
565
+ true, :flag, @active_flag_arg)
566
566
  end
567
567
  end
568
568
  end
data/lib/toys/cli.rb CHANGED
@@ -226,9 +226,9 @@ module Toys
226
226
  end
227
227
 
228
228
  ##
229
- # Make a clone with the same settings but no no config blocks and no paths
230
- # in the loader. This is sometimes useful for calling another tool that has
231
- # to be loaded from a different configuration.
229
+ # Make a clone with the same settings but no config blocks and no paths in
230
+ # the loader. This is sometimes useful for calling another tool that has to
231
+ # be loaded from a different configuration.
232
232
  #
233
233
  # @param opts [keywords] Any configuration arguments that should be
234
234
  # modified from the original. See {#initialize} for a list of
@@ -435,7 +435,11 @@ module Toys
435
435
  end
436
436
 
437
437
  ##
438
- # @private
438
+ # Take the various ways to express a completion spec, and convert them to a
439
+ # canonical form expressed as a single object. This is called from the DSL
440
+ # DSL to generate a spec object that can be stored.
441
+ #
442
+ # @private This interface is internal and subject to change without warning.
439
443
  #
440
444
  def self.scalarize_spec(spec, options, block)
441
445
  spec ||= block
data/lib/toys/context.rb CHANGED
@@ -302,7 +302,7 @@ module Toys
302
302
  end
303
303
 
304
304
  ##
305
- # Exit immediately with the given status code
305
+ # Exit immediately with the given status code.
306
306
  #
307
307
  # @param code [Integer] The status code, which should be 0 for no error,
308
308
  # or nonzero for an error condition. Default is 0.
@@ -313,7 +313,8 @@ module Toys
313
313
  end
314
314
 
315
315
  ##
316
- # Exit immediately with the given status code
316
+ # Exit immediately with the given status code. This class method can be
317
+ # called if the instance method is or could be replaced by the tool.
317
318
  #
318
319
  # @param code [Integer] The status code, which should be 0 for no error,
319
320
  # or nonzero for an error condition. Default is 0.
@@ -330,19 +331,22 @@ module Toys
330
331
  #
331
332
  # @param data [Hash]
332
333
  #
333
- # @private
334
+ # @private This interface is internal and subject to change without warning.
334
335
  #
335
336
  def initialize(data)
336
337
  @__data = data
337
338
  end
338
339
 
339
340
  ##
341
+ # Include the tool name in the object inspection dump.
342
+ #
340
343
  # @private
341
344
  #
342
345
  def inspect
343
- name = Array(@__data[Key::TOOL_NAME]).join(" ")
346
+ words = Array(@__data[Key::TOOL_NAME])
347
+ name = words.empty? ? "(root)" : words.join(" ").inspect
344
348
  id = object_id.to_s(16)
345
- "#<Toys::Context id=0x#{id} #{name}>"
349
+ "#<Toys::Context id=0x#{id} tool=#{name}>"
346
350
  end
347
351
  end
348
352
  end
data/lib/toys/core.rb CHANGED
@@ -9,7 +9,7 @@ module Toys
9
9
  # Current version of Toys core.
10
10
  # @return [String]
11
11
  #
12
- VERSION = "0.13.1"
12
+ VERSION = "0.14.2"
13
13
  end
14
14
 
15
15
  ##
@@ -68,6 +68,24 @@ module Toys
68
68
  end
69
69
  end
70
70
 
71
+ ##
72
+ # Called by the DSL implementation to analyze the name of a new tool
73
+ # definition in context.
74
+ #
75
+ # @private
76
+ #
77
+ def analyze_name(tool_class, words)
78
+ loader = tool_class.instance_variable_get(:@__loader)
79
+ subtool_words = tool_class.instance_variable_get(:@__words).dup
80
+ next_remaining = tool_class.instance_variable_get(:@__remaining_words)
81
+ loader.split_path(words).each do |word|
82
+ word = word.to_s
83
+ subtool_words << word
84
+ next_remaining = Loader.next_remaining_words(next_remaining, word)
85
+ end
86
+ [subtool_words, next_remaining]
87
+ end
88
+
71
89
  ##
72
90
  # Called by the DSL implementation to add a getter to the tool class.
73
91
  #