toys-core 0.18.0 → 0.19.1
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 +4 -4
- data/CHANGELOG.md +14 -0
- data/README.md +3 -3
- data/docs/guide.md +296 -187
- data/lib/toys/cli.rb +3 -2
- data/lib/toys/core.rb +1 -1
- data/lib/toys/standard_mixins/exec.rb +1 -1
- data/lib/toys/utils/exec.rb +2 -1
- data/lib/toys/utils/gems.rb +12 -9
- data/lib/toys/utils/standard_ui.rb +2 -1
- metadata +19 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 815f546de5a0da65099cf1fdce70527fb42aafd3793bf8a99e35f63d77c4e66b
|
|
4
|
+
data.tar.gz: 79bd650fb1bdc5d04a2b1e1d235864e39313b4320518b9ac6296a8e1892aa8b1
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: e696b9c3921aec0c56796bdd7b93a09d056f6d46347a5936bbf6a0ce9bc7b2811f90d4b9553c54bdf69b62d74289e4608da98473e9b62e34f155accbf665f7a1
|
|
7
|
+
data.tar.gz: 675e01b73df268ceda5cfd5bd62f4a21b164c2fce0b37e88f3397bc12f76ba8baa4b2969df5d82901ffbb78440dd1156374cc6642792d81a4ec9c149a800148a
|
data/CHANGELOG.md
CHANGED
|
@@ -1,5 +1,19 @@
|
|
|
1
1
|
# Release History
|
|
2
2
|
|
|
3
|
+
### v0.19.1 / 2026-01-06
|
|
4
|
+
|
|
5
|
+
* DOCS: Some formatting fixes in the user guide
|
|
6
|
+
|
|
7
|
+
### v0.19.0 / 2025-12-22
|
|
8
|
+
|
|
9
|
+
Compatibility update for Ruby 4.0, including:
|
|
10
|
+
|
|
11
|
+
* The logger gem is now an explicit dependency
|
|
12
|
+
* Calling a tool via exec no longer disables rubygems
|
|
13
|
+
* Bundler integration does a better job of cleaning up temporary lockfiles under bundler 4
|
|
14
|
+
|
|
15
|
+
Additionally, this release includes updates to readmes and users guides
|
|
16
|
+
|
|
3
17
|
### v0.18.0 / 2025-12-05
|
|
4
18
|
|
|
5
19
|
* ADDED: The load_gem directive can now take version requirements as positional arguments
|
data/README.md
CHANGED
|
@@ -99,9 +99,9 @@ look familiar. Let's run it now, and experiment with passing flags to it.
|
|
|
99
99
|
Notice that we did not create a `tool` block, but instead set up description,
|
|
100
100
|
flags, and functionality directly in the configuration block. This configures
|
|
101
101
|
the "root tool", i.e. what happens when you run the executable without passing
|
|
102
|
-
a tool name to it. (In fact, it's legal to do this in Toys as well,
|
|
103
|
-
functionality at the "top level" of a `.toys.rb` file without
|
|
104
|
-
`tool` block.)
|
|
102
|
+
a tool name to it. (In fact, it's technically legal to do this in Toys as well,
|
|
103
|
+
by setting functionality at the "top level" of a `.toys.rb` file without any
|
|
104
|
+
`tool` block, although you probably won't actually want to do so.)
|
|
105
105
|
|
|
106
106
|
### Tool-based executables
|
|
107
107
|
|
data/docs/guide.md
CHANGED
|
@@ -59,8 +59,8 @@ An executable can customize many aspects of its behavior, such as the
|
|
|
59
59
|
**logging output**, **error handling**, and even shell **tab completion**.
|
|
60
60
|
|
|
61
61
|
Finally, Toys-Core can also be used to publish **Toys extensions**, collections
|
|
62
|
-
of mixins, templates, and predefined tools that can be distributed as gems
|
|
63
|
-
enhance Toys for other users.
|
|
62
|
+
of mixins, templates, and/or predefined tools that can be distributed as gems
|
|
63
|
+
to enhance Toys for other users.
|
|
64
64
|
|
|
65
65
|
## Using the CLI object
|
|
66
66
|
|
|
@@ -80,27 +80,29 @@ to ensure that the `toys-core` gem is loaded, and `require "toys-core"`.
|
|
|
80
80
|
|
|
81
81
|
Following is a simple "hello world" example using the CLI:
|
|
82
82
|
|
|
83
|
-
|
|
83
|
+
```ruby
|
|
84
|
+
#!/usr/bin/env ruby
|
|
84
85
|
|
|
85
|
-
|
|
86
|
+
require "toys-core"
|
|
86
87
|
|
|
87
|
-
|
|
88
|
-
|
|
88
|
+
# Instantiate a CLI with the default options
|
|
89
|
+
cli = Toys::CLI.new
|
|
89
90
|
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
91
|
+
# Define the functionality
|
|
92
|
+
cli.add_config_block do
|
|
93
|
+
desc "My first executable!"
|
|
94
|
+
flag :whom, default: "world"
|
|
95
|
+
def run
|
|
96
|
+
puts "Hello, #{whom}!"
|
|
97
|
+
end
|
|
98
|
+
end
|
|
98
99
|
|
|
99
|
-
|
|
100
|
-
|
|
100
|
+
# Run the CLI, passing the command line arguments
|
|
101
|
+
result = cli.run(*ARGV)
|
|
101
102
|
|
|
102
|
-
|
|
103
|
-
|
|
103
|
+
# Handle the result code.
|
|
104
|
+
exit(result)
|
|
105
|
+
```
|
|
104
106
|
|
|
105
107
|
### CLI execution
|
|
106
108
|
|
|
@@ -123,10 +125,10 @@ When you call {Toys::CLI#run}, the CLI runs through three phases:
|
|
|
123
125
|
When the CLI needs the definition of a tool, it queries the {Toys::Loader}. The
|
|
124
126
|
loader object is configured with a set of tool _sources_ representing ways to
|
|
125
127
|
define a tool. These sources may be blocks passed directly to the CLI, or
|
|
126
|
-
directories and files loaded from the file system or even
|
|
127
|
-
repositories. When a tool is requested by name, the loader is
|
|
128
|
-
locating the tool definition in those sources, and constructing
|
|
129
|
-
definition object, represented by {Toys::ToolDefinition}.
|
|
128
|
+
directories and files loaded from the file system, from gems, or even from
|
|
129
|
+
remote git repositories. When a tool is requested by name, the loader is
|
|
130
|
+
responsible for locating the tool definition in those sources, and constructing
|
|
131
|
+
the tool definition object, represented by {Toys::ToolDefinition}.
|
|
130
132
|
|
|
131
133
|
One important property of the loader is that it is _lazy_. It queries tool
|
|
132
134
|
sources only when it has reason to believe that a tool it is looking for may be
|
|
@@ -141,19 +143,21 @@ when a tool is requested does the block actually execute. Furthermore, if you
|
|
|
141
143
|
have `tool` blocks inside the block, the loader will execute only those that
|
|
142
144
|
are relevant to a tool it wants. Hence:
|
|
143
145
|
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
146
|
+
```ruby
|
|
147
|
+
cli.add_config_block do
|
|
148
|
+
tool "foo" do
|
|
149
|
+
def run
|
|
150
|
+
puts "foo called"
|
|
151
|
+
end
|
|
152
|
+
end
|
|
150
153
|
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
end
|
|
155
|
-
end
|
|
154
|
+
tool "bar" do
|
|
155
|
+
def run
|
|
156
|
+
puts "bar called"
|
|
156
157
|
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
```
|
|
157
161
|
|
|
158
162
|
If only `foo` is requested, the loader will execute the `tool "foo" do` block
|
|
159
163
|
to get that tool definition, but will not execute the `tool "bar" do` block.
|
|
@@ -184,7 +188,7 @@ class, but it implements a few extra features and cleans up a few ambiguities.
|
|
|
184
188
|
|
|
185
189
|
The execution phase involves:
|
|
186
190
|
|
|
187
|
-
* Running the tool's initializers
|
|
191
|
+
* Running the tool's initializers (if any) in order.
|
|
188
192
|
* Running the tool's middleware. Each middleware "wraps" the execution of
|
|
189
193
|
subsequent middleware and the final tool execution, and has the opportunity
|
|
190
194
|
to inject functionality before and after the main execution, or even to
|
|
@@ -245,28 +249,30 @@ If you are writing your own command line executable using Toys-Core, often the
|
|
|
245
249
|
easiest way to define your tools is to use a block. The "hello world" example
|
|
246
250
|
at the start of this guide uses this technique:
|
|
247
251
|
|
|
248
|
-
|
|
252
|
+
```ruby
|
|
253
|
+
#!/usr/bin/env ruby
|
|
249
254
|
|
|
250
|
-
|
|
255
|
+
require "toys-core"
|
|
251
256
|
|
|
252
|
-
|
|
257
|
+
cli = Toys::CLI.new
|
|
253
258
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
259
|
+
# Define the functionality by passing a block to the CLI
|
|
260
|
+
cli.add_config_block do
|
|
261
|
+
desc "My first executable!"
|
|
262
|
+
flag :whom, default: "world"
|
|
263
|
+
def run
|
|
264
|
+
puts "Hello, #{whom}!"
|
|
265
|
+
end
|
|
266
|
+
end
|
|
262
267
|
|
|
263
|
-
|
|
264
|
-
|
|
268
|
+
result = cli.run(*ARGV)
|
|
269
|
+
exit(result)
|
|
270
|
+
```
|
|
265
271
|
|
|
266
272
|
The block simply contains Toys DSL syntax. The above example configures the
|
|
267
273
|
"root tool", that is, the functionality of the program if you do not pass a
|
|
268
274
|
tool name on the command line. You can also include "tool" blocks to define
|
|
269
|
-
named tools, just as you would in a normal Toys file.
|
|
275
|
+
named tools and subtools, just as you would in a normal Toys file.
|
|
270
276
|
|
|
271
277
|
The reference documentation for {Toys::CLI#add_config_block} lists several
|
|
272
278
|
options that can be passed in. `:context_directory` lets you select a context
|
|
@@ -282,25 +288,29 @@ messages and documentation, can also be set explicitly.
|
|
|
282
288
|
If you want to define tools in separate files, you can do so and pass the file
|
|
283
289
|
paths to the CLI using {Toys::CLI#add_config_path}.
|
|
284
290
|
|
|
285
|
-
|
|
291
|
+
```ruby
|
|
292
|
+
#!/usr/bin/env ruby
|
|
286
293
|
|
|
287
|
-
|
|
294
|
+
require "toys-core"
|
|
288
295
|
|
|
289
|
-
|
|
296
|
+
cli = Toys::CLI.new
|
|
290
297
|
|
|
291
|
-
|
|
292
|
-
|
|
298
|
+
# Load a file defining the functionality
|
|
299
|
+
cli.add_config_path("/usr/local/share/my_tool.rb")
|
|
293
300
|
|
|
294
|
-
|
|
295
|
-
|
|
301
|
+
result = cli.run(*ARGV)
|
|
302
|
+
exit(result)
|
|
303
|
+
```
|
|
296
304
|
|
|
297
305
|
The contents of `/usr/local/share/my_tool.rb` could then be:
|
|
298
306
|
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
307
|
+
```ruby
|
|
308
|
+
desc "My first executable!"
|
|
309
|
+
flag :whom, default: "world"
|
|
310
|
+
def run
|
|
311
|
+
puts "Hello, #{whom}!"
|
|
312
|
+
end
|
|
313
|
+
```
|
|
304
314
|
|
|
305
315
|
You can point to a specific file to load, or to a Toys directory, whose
|
|
306
316
|
contents will be loaded similarly to how a `.toys` directory is loaded.
|
|
@@ -329,45 +339,49 @@ priority level than previously added sources. Thus, any tools defined in the
|
|
|
329
339
|
new source would be overridden by tools of the same name defined in previously
|
|
330
340
|
added sources.
|
|
331
341
|
|
|
332
|
-
|
|
342
|
+
```ruby
|
|
343
|
+
#!/usr/bin/env ruby
|
|
333
344
|
|
|
334
|
-
|
|
345
|
+
require "toys-core"
|
|
335
346
|
|
|
336
|
-
|
|
347
|
+
cli = Toys::CLI.new
|
|
337
348
|
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
end
|
|
344
|
-
end
|
|
349
|
+
# Add a block defining a tool called "hello"
|
|
350
|
+
cli.add_config_block do
|
|
351
|
+
tool "hello" do
|
|
352
|
+
def run
|
|
353
|
+
puts "Hello from the first config block!"
|
|
345
354
|
end
|
|
355
|
+
end
|
|
356
|
+
end
|
|
346
357
|
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
352
|
-
end
|
|
353
|
-
end
|
|
358
|
+
# Add a lower-priority block defining a tool with the same name
|
|
359
|
+
cli.add_config_block do
|
|
360
|
+
tool "hello" do
|
|
361
|
+
def run
|
|
362
|
+
puts "Hello from the second config block!"
|
|
354
363
|
end
|
|
364
|
+
end
|
|
365
|
+
end
|
|
355
366
|
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
|
|
367
|
+
# Runs the tool defined in the first block
|
|
368
|
+
result = cli.run("hello")
|
|
369
|
+
exit(result)
|
|
370
|
+
```
|
|
359
371
|
|
|
360
372
|
When defining tool blocks or loading tools from files, you can also add the new
|
|
361
373
|
source at the *front* of the priority list by passing an argument:
|
|
362
374
|
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
end
|
|
375
|
+
```ruby
|
|
376
|
+
# Add tools with the highest priority
|
|
377
|
+
cli.add_config_block high_priority: true do
|
|
378
|
+
tool "hello" do
|
|
379
|
+
def run
|
|
380
|
+
puts "Hello from the second config block!"
|
|
370
381
|
end
|
|
382
|
+
end
|
|
383
|
+
end
|
|
384
|
+
```
|
|
371
385
|
|
|
372
386
|
Priorities are used by the `toys` gem when loading tools from different
|
|
373
387
|
directories. Any `.toys.rb` file or `.toys` directory is added to the CLI at
|
|
@@ -375,9 +389,86 @@ the front of the list, with the highest priority. Parent directories are added
|
|
|
375
389
|
at subsequently lower priorities, and common directories such as the home
|
|
376
390
|
directory are loaded at the lowest priority.
|
|
377
391
|
|
|
378
|
-
###
|
|
392
|
+
### Customizing built-in mixins and templates
|
|
379
393
|
|
|
380
|
-
|
|
394
|
+
Mixins and templates are two of the most useful mechanisms for sharing code and
|
|
395
|
+
generating code for tools. In the main Toys gem, a certain set of mixins are
|
|
396
|
+
built-in and can be referenced via symbols. For example, the *exec* mixin that
|
|
397
|
+
provides facilities for running and controlling external processes, can be
|
|
398
|
+
included using `include :exec`. In this section, we see how to define your own
|
|
399
|
+
"built-in" mixins and templates that can be referenced via symbol.
|
|
400
|
+
|
|
401
|
+
"Built-in" mixins and templates (and middleware, which we shall cover later)
|
|
402
|
+
are provided via the {Toys::ModuleLookup} mechanism. ModuleLookup lets you
|
|
403
|
+
select a directory for "standard" instances. By default, Toys-Core establishes
|
|
404
|
+
the `toys/standard_mixins` directory in the gem as the standard directory for
|
|
405
|
+
mixins, and whenever you reference a mixin by symbol, it is used to determine
|
|
406
|
+
the name of a file to open and the name of a module to load. You can, however,
|
|
407
|
+
change this directory and provide a different ModuleLookup when constructing a
|
|
408
|
+
CLI object.
|
|
409
|
+
|
|
410
|
+
Suppose, for example, you are writing a gem `my_tools` that uses Toys-Core, and
|
|
411
|
+
you have a directory in your gem's `lib` called `my_tools/mixins` where you
|
|
412
|
+
want your standard mixins to live. You could define mixins there:
|
|
413
|
+
|
|
414
|
+
```ruby
|
|
415
|
+
# This file is my_tools/mixins/foo_mixin.rb
|
|
416
|
+
|
|
417
|
+
require "toys-core"
|
|
418
|
+
|
|
419
|
+
module MyTools
|
|
420
|
+
module Mixins
|
|
421
|
+
module FooMixin
|
|
422
|
+
include Toys::Mixin
|
|
423
|
+
|
|
424
|
+
def foo
|
|
425
|
+
puts "Foo was called"
|
|
426
|
+
end
|
|
427
|
+
end
|
|
428
|
+
end
|
|
429
|
+
end
|
|
430
|
+
```
|
|
431
|
+
|
|
432
|
+
Here is how you could configure a CLI to load standard mixins from that
|
|
433
|
+
directory, and then use the above mixin.
|
|
434
|
+
|
|
435
|
+
```ruby
|
|
436
|
+
# This file is my_tools.rb
|
|
437
|
+
|
|
438
|
+
require "toys-core"
|
|
439
|
+
|
|
440
|
+
my_mixin_lookup = Toys::ModuleLookup.new.add_path("my_tools/mixins")
|
|
441
|
+
cli = Toys::CLI.new(mixin_lookup: my_mixin_lookup)
|
|
442
|
+
|
|
443
|
+
cli.add_config_block do
|
|
444
|
+
def run
|
|
445
|
+
include :foo_mixin
|
|
446
|
+
foo
|
|
447
|
+
end
|
|
448
|
+
end
|
|
449
|
+
```
|
|
450
|
+
|
|
451
|
+
When you configure a ModuleLookup, you provide one or more paths, which are
|
|
452
|
+
path prefixes that are used in a `require` statement. In the above example,
|
|
453
|
+
we used the path `my_tools/mixins` for the ModuleLookup. Now when the CLI uses
|
|
454
|
+
this ModuleLookup to find a mixin called `:foo_mixin`, it will attempt to
|
|
455
|
+
`require "my_tools/mixins/foo_mixin"`, which matches the file where we defined
|
|
456
|
+
our mixin.
|
|
457
|
+
|
|
458
|
+
Notice also that `foo_mixin.rb` above defines FooMixin within a specific module
|
|
459
|
+
hierarchy, corresponding to the file name `my_tools/mixins/foo_mixin.rb`
|
|
460
|
+
according to standard Ruby naming conventions. The fully-qualified module name
|
|
461
|
+
for the mixin must match this expected name, constructed from the path provided
|
|
462
|
+
to the ModuleLookup and the name of the mixin. You can change the way this name
|
|
463
|
+
mapping occurs, by providing the `:module_base` argument to the ModuleLookup
|
|
464
|
+
constructor.
|
|
465
|
+
|
|
466
|
+
Template lookup happens similarly. Toys-Core does not provide a set of default
|
|
467
|
+
templates, but the Toys gem does; the `StandardCLI` class used by Toys sets the
|
|
468
|
+
`:template_lookup` to point to the `toys/templates` directory in that gem's
|
|
469
|
+
library. If you want to customize the default template lookup for your
|
|
470
|
+
Toys-based library, you can similarly provide your own ModuleLookup. This will
|
|
471
|
+
let you control how templates are resolved when specified by symbol.
|
|
381
472
|
|
|
382
473
|
## Customizing diagnostic output
|
|
383
474
|
|
|
@@ -395,22 +486,24 @@ Toys provides a Logger for each tool execution. Tools can access this Logger by
|
|
|
395
486
|
calling the `logger` method, or by getting the `Toys::Context::Key::LOGGER`
|
|
396
487
|
context object.
|
|
397
488
|
|
|
398
|
-
|
|
489
|
+
```ruby
|
|
490
|
+
#!/usr/bin/env ruby
|
|
399
491
|
|
|
400
|
-
|
|
492
|
+
require "toys-core"
|
|
401
493
|
|
|
402
|
-
|
|
494
|
+
cli = Toys::CLI.new
|
|
403
495
|
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
408
|
-
end
|
|
409
|
-
end
|
|
496
|
+
cli.add_config_block do
|
|
497
|
+
tool "hello" do
|
|
498
|
+
def run
|
|
499
|
+
logger.info "This log entry is displayed in verbose mode."
|
|
410
500
|
end
|
|
501
|
+
end
|
|
502
|
+
end
|
|
411
503
|
|
|
412
|
-
|
|
413
|
-
|
|
504
|
+
result = cli.run(*ARGV)
|
|
505
|
+
exit(result)
|
|
506
|
+
```
|
|
414
507
|
|
|
415
508
|
#### Log level and verbosity
|
|
416
509
|
|
|
@@ -433,15 +526,19 @@ Passing `verbosity: 1` will set the starting verbosity to 1, meaning
|
|
|
433
526
|
the invoker then provides an extra `--verbose` flag, the verbosity will further
|
|
434
527
|
increase to 2, allowing `Logger::DEBUG` entries to appear.
|
|
435
528
|
|
|
436
|
-
|
|
437
|
-
|
|
438
|
-
|
|
529
|
+
```ruby
|
|
530
|
+
# ...
|
|
531
|
+
result = cli.run(*ARGV, verbosity: 1)
|
|
532
|
+
exit(result)
|
|
533
|
+
```
|
|
439
534
|
|
|
440
535
|
You can also modify the log level that verbosity 0 maps to by passing the
|
|
441
536
|
`base_level` argument to the CLI constructor. The following causes verbosity 0
|
|
442
537
|
to map to `Logger::INFO` rather than `Logger::WARN`.
|
|
443
538
|
|
|
444
|
-
|
|
539
|
+
```ruby
|
|
540
|
+
cli = Toys::CLI.new(base_level: Logger::INFO)
|
|
541
|
+
```
|
|
445
542
|
|
|
446
543
|
#### Customizing the logger
|
|
447
544
|
|
|
@@ -450,8 +547,10 @@ configures it to log to STDERR. If you want to change any of these settings,
|
|
|
450
547
|
you can provide your own logger by passing a `logger` to the CLI constructor
|
|
451
548
|
constructor.
|
|
452
549
|
|
|
453
|
-
|
|
454
|
-
|
|
550
|
+
```ruby
|
|
551
|
+
my_logger = Logger.new("my_logfile.log")
|
|
552
|
+
cli = Toys::CLI.new(logger: my_logger)
|
|
553
|
+
```
|
|
455
554
|
|
|
456
555
|
A logger passed directly to the CLI is *global*. The CLI will attempt to use it
|
|
457
556
|
for every execution, even if multiple executions are happening concurrently. In
|
|
@@ -461,10 +560,12 @@ your CLI might be run multiple times concurrently, we recommend instead passing
|
|
|
461
560
|
a `logger_factory` to the CLI constructor. This is a Proc that will be invoked
|
|
462
561
|
to create a new logger for each execution.
|
|
463
562
|
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
563
|
+
```ruby
|
|
564
|
+
my_logger_factory = Proc.new do
|
|
565
|
+
Logger.new("my_logfile.log")
|
|
566
|
+
end
|
|
567
|
+
cli = Toys::CLI.new(logger_factory: my_logger_factory)
|
|
568
|
+
```
|
|
468
569
|
|
|
469
570
|
#### StandardUI logging
|
|
470
571
|
|
|
@@ -474,8 +575,10 @@ formats log entries with the severity and timestamp using ANSI coloring.
|
|
|
474
575
|
You can use this logger by passing {Toys::Utils::StandardUI#logger_factory} to
|
|
475
576
|
the CLI constructor:
|
|
476
577
|
|
|
477
|
-
|
|
478
|
-
|
|
578
|
+
```ruby
|
|
579
|
+
standard_ui = Toys::Utils::StandardUI.new
|
|
580
|
+
cli = Toys::CLI.new(logger_factory: standard_ui.logger_factory)
|
|
581
|
+
```
|
|
479
582
|
|
|
480
583
|
You can also customize the logger by subclassing StandardUI and overriding its
|
|
481
584
|
methods or adjusting its parameters. In particular, you can alter the
|
|
@@ -501,15 +604,17 @@ final handling of an unhandled exception, such as displaying the error to the
|
|
|
501
604
|
terminal, or reraising the exception. The handler should then return the
|
|
502
605
|
desired result code for the execution.
|
|
503
606
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
607
|
+
```ruby
|
|
608
|
+
my_error_handler = Proc.new |wrapped_error| do
|
|
609
|
+
# Propagate signals out and let the Ruby VM handle them.
|
|
610
|
+
raise wrapped_error.cause if wrapped_error.cause.is_a?(SignalException)
|
|
611
|
+
# Handle any other exception types by printing a message.
|
|
612
|
+
$stderr.puts "An error occurred. Please contact your administrator."
|
|
613
|
+
# Return the result code
|
|
614
|
+
255
|
|
615
|
+
end
|
|
616
|
+
cli = Toys::CLI.new(error_handler: my_error_handler)
|
|
617
|
+
```
|
|
513
618
|
|
|
514
619
|
If you do not set an error handler, the exception is raised out of the
|
|
515
620
|
{Toys::CLI#run} call. In the case of signals, the *cause*, represented by a
|
|
@@ -530,8 +635,10 @@ conventional result code of `128 + signo` (e.g. 130 for interrupts).
|
|
|
530
635
|
You can use this error handler by passing
|
|
531
636
|
{Toys::Utils::StandardUI#error_handler} to the CLI constructor:
|
|
532
637
|
|
|
533
|
-
|
|
534
|
-
|
|
638
|
+
```ruby
|
|
639
|
+
standard_ui = Toys::Utils::StandardUI.new
|
|
640
|
+
cli = Toys::CLI.new(error_handler: standard_ui.error_handler)
|
|
641
|
+
```
|
|
535
642
|
|
|
536
643
|
You can also customize the error handler by subclassing StandardUI and
|
|
537
644
|
overriding its methods. In particular, you can alter what is displayed in
|
|
@@ -588,7 +695,7 @@ replace normal tool execution.
|
|
|
588
695
|
|
|
589
696
|
Middleware is normally configured as part of the CLI object. Each CLI includes
|
|
590
697
|
an ordered list, a _stack_, of middleware specifications, each represented by
|
|
591
|
-
{Toys::Middleware::Spec}. A middleware spec can
|
|
698
|
+
{Toys::Middleware::Spec}. A middleware spec can reference a specific middleware
|
|
592
699
|
object, a class to instantiate, or a name that can be looked up from a
|
|
593
700
|
directory of middleware class files. You can pass an array of these specs to a
|
|
594
701
|
CLI object when you instantiate it.
|
|
@@ -597,12 +704,14 @@ A useful example can be seen in the default Toys CLI behavior. If you do not
|
|
|
597
704
|
provide a middleware stack when instantiating {Toys::CLI}, the class uses a
|
|
598
705
|
default stack that looks approximately like this:
|
|
599
706
|
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
707
|
+
```ruby
|
|
708
|
+
[
|
|
709
|
+
Toys::Middleware.spec(:set_default_descriptions),
|
|
710
|
+
Toys::Middleware.spec(:show_help, help_flags: true, fallback_execution: true),
|
|
711
|
+
Toys::Middleware.spec(:handle_usage_errors),
|
|
712
|
+
Toys::Middleware.spec(:add_verbosity_flags),
|
|
713
|
+
]
|
|
714
|
+
```
|
|
606
715
|
|
|
607
716
|
Each of the names, e.g. `:set_default_descriptions`, is the name of a Ruby
|
|
608
717
|
file in the `toys-core` gem under `toys/standard_middleware`. You can configure
|
|
@@ -659,51 +768,53 @@ following is a simple middleware that adds the `--show-timing` flag to every
|
|
|
659
768
|
tool. When the flag is set, the middleware displays how long the tool took to
|
|
660
769
|
execute.
|
|
661
770
|
|
|
662
|
-
|
|
663
|
-
|
|
664
|
-
|
|
665
|
-
|
|
666
|
-
|
|
667
|
-
|
|
668
|
-
|
|
669
|
-
|
|
670
|
-
|
|
671
|
-
|
|
672
|
-
|
|
673
|
-
|
|
674
|
-
|
|
675
|
-
|
|
676
|
-
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
|
|
681
|
-
|
|
682
|
-
|
|
683
|
-
|
|
684
|
-
|
|
685
|
-
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
|
|
692
|
-
|
|
693
|
-
|
|
694
|
-
|
|
695
|
-
|
|
696
|
-
|
|
697
|
-
|
|
698
|
-
|
|
699
|
-
|
|
700
|
-
|
|
701
|
-
|
|
702
|
-
|
|
703
|
-
|
|
704
|
-
|
|
705
|
-
|
|
706
|
-
|
|
771
|
+
```ruby
|
|
772
|
+
class TimingMiddleware
|
|
773
|
+
# This is a context key that will be used to store the "--show-timing"
|
|
774
|
+
# flag state. We can use `Object.new` to ensure that the key is unique
|
|
775
|
+
# across other middlewares and tool definitions.
|
|
776
|
+
KEY = Object.new.freeze
|
|
777
|
+
|
|
778
|
+
# This method intercepts tool configuration. We use it to add a flag that
|
|
779
|
+
# enables timing display.
|
|
780
|
+
def config(tool, _loader)
|
|
781
|
+
# Add a flag to control this functionality. Suppress collisions, i.e.
|
|
782
|
+
# just silently do nothing if the tool has already added a flag called
|
|
783
|
+
# "--show-timing".
|
|
784
|
+
tool.add_flag(KEY, "--show-timing", report_collisions: false)
|
|
785
|
+
|
|
786
|
+
# Calling yield passes control to the rest of the middleware stack.
|
|
787
|
+
# Normally you should call yield, to ensure that the remaining
|
|
788
|
+
# middleware can run. If you omit this, no additional middleware will
|
|
789
|
+
# be able to run tool configuration. Note you can also perform
|
|
790
|
+
# additional processing after the yield call, i.e. after the rest of
|
|
791
|
+
# the middleware stack has run.
|
|
792
|
+
yield
|
|
793
|
+
end
|
|
794
|
+
|
|
795
|
+
# This method intercepts tool execution. We use it to collect timing
|
|
796
|
+
# information, and display it if the flag has been provided in the
|
|
797
|
+
# command line arguments.
|
|
798
|
+
def run(context)
|
|
799
|
+
# Read monotonic time at the start of execution.
|
|
800
|
+
start_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
801
|
+
|
|
802
|
+
# Call yield to run the rest of the middleware stack, including the
|
|
803
|
+
# actual tool execution. If you omit this, you will prevent the rest of
|
|
804
|
+
# the middleware stack, AND the actual tool execution, from running.
|
|
805
|
+
# So you could omit the yield call if your goal is to replace tool
|
|
806
|
+
# execution with your own code.
|
|
807
|
+
yield
|
|
808
|
+
|
|
809
|
+
# Read monotonic time again after execution.
|
|
810
|
+
end_time = Process.clock_gettime(Process::CLOCK_MONOTONIC)
|
|
811
|
+
|
|
812
|
+
# Display the elapsed time, if the tool was passed the "--show-timing"
|
|
813
|
+
# flag.
|
|
814
|
+
puts "Tool took #{end_time - start_time} secs" if context[KEY]
|
|
815
|
+
end
|
|
816
|
+
end
|
|
817
|
+
```
|
|
707
818
|
|
|
708
819
|
We can now insert our middleware into the stack when we create a CLI. Here
|
|
709
820
|
we'll take that "default" stack we saw earlier and add our timing middleware at
|
|
@@ -712,22 +823,20 @@ other middleware, and thus its timing measurement includes the latency incurred
|
|
|
712
823
|
by other middleware (including middleware that replaces execution such as
|
|
713
824
|
`:show_help`).
|
|
714
825
|
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
826
|
+
```ruby
|
|
827
|
+
my_middleware_stack = [
|
|
828
|
+
Toys::Middleware.spec(TimingMiddleware),
|
|
829
|
+
Toys::Middleware.spec(:set_default_descriptions),
|
|
830
|
+
Toys::Middleware.spec(:show_help, help_flags: true, fallback_execution: true),
|
|
831
|
+
Toys::Middleware.spec(:handle_usage_errors),
|
|
832
|
+
Toys::Middleware.spec(:add_verbosity_flags),
|
|
833
|
+
]
|
|
834
|
+
cli = Toys::CLI.new(middleware_stack: my_middleware_stack)
|
|
835
|
+
```
|
|
723
836
|
|
|
724
837
|
Now, every tool run by this CLI wil have the `--show-timing` flag and
|
|
725
838
|
associated functionality.
|
|
726
839
|
|
|
727
|
-
#### Changing built-in middleware
|
|
728
|
-
|
|
729
|
-
(TODO)
|
|
730
|
-
|
|
731
840
|
## Shell and command line integration
|
|
732
841
|
|
|
733
842
|
(TODO)
|
data/lib/toys/cli.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "logger"
|
|
4
|
+
|
|
3
5
|
module Toys
|
|
4
6
|
##
|
|
5
7
|
# A Toys-based CLI.
|
|
@@ -125,7 +127,7 @@ module Toys
|
|
|
125
127
|
# Optional. If not provided, defaults to the set of standard template
|
|
126
128
|
# classes provided by toys core, as defined by
|
|
127
129
|
# {Toys::CLI.default_template_lookup}. If you explicitly want no
|
|
128
|
-
# standard
|
|
130
|
+
# standard templates, pass an empty instance of {Toys::ModuleLookup}.
|
|
129
131
|
#
|
|
130
132
|
# @param config_dir_name [String] A directory with this name that appears
|
|
131
133
|
# in the loader path, is treated as a configuration directory whose
|
|
@@ -562,7 +564,6 @@ module Toys
|
|
|
562
564
|
# @return [Proc]
|
|
563
565
|
#
|
|
564
566
|
def default_logger_factory
|
|
565
|
-
require "logger"
|
|
566
567
|
proc do
|
|
567
568
|
logger = ::Logger.new($stderr)
|
|
568
569
|
logger.level = ::Logger::WARN
|
data/lib/toys/core.rb
CHANGED
|
@@ -817,7 +817,7 @@ module Toys
|
|
|
817
817
|
def self._setup_clean_process(cmd)
|
|
818
818
|
raise ::ArgumentError, "Toys process is unknown" unless ::Toys.executable_path
|
|
819
819
|
cmd = ::Shellwords.split(cmd) if cmd.is_a?(::String)
|
|
820
|
-
cmd = [::RbConfig.ruby,
|
|
820
|
+
cmd = [::RbConfig.ruby, ::Toys.executable_path] + cmd
|
|
821
821
|
if defined?(::Bundler)
|
|
822
822
|
if ::Bundler.respond_to?(:with_unbundled_env)
|
|
823
823
|
::Bundler.with_unbundled_env { yield(cmd) }
|
data/lib/toys/utils/exec.rb
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "logger"
|
|
4
|
+
|
|
3
5
|
module Toys
|
|
4
6
|
module Utils
|
|
5
7
|
##
|
|
@@ -263,7 +265,6 @@ module Toys
|
|
|
263
265
|
#
|
|
264
266
|
def initialize(**opts, &block)
|
|
265
267
|
require "rbconfig"
|
|
266
|
-
require "logger"
|
|
267
268
|
require "stringio"
|
|
268
269
|
@default_opts = Opts.new(&block).add(opts)
|
|
269
270
|
end
|
data/lib/toys/utils/gems.rb
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "fileutils"
|
|
3
4
|
require "monitor"
|
|
4
5
|
|
|
5
6
|
module Toys
|
|
@@ -193,9 +194,7 @@ module Toys
|
|
|
193
194
|
end
|
|
194
195
|
end
|
|
195
196
|
|
|
196
|
-
##
|
|
197
197
|
# @private
|
|
198
|
-
#
|
|
199
198
|
def self.find_gemfile(search_dir, gemfile_names: nil)
|
|
200
199
|
gemfile_names ||= DEFAULT_GEMFILE_NAMES
|
|
201
200
|
Array(gemfile_names).each do |file|
|
|
@@ -207,13 +206,18 @@ module Toys
|
|
|
207
206
|
|
|
208
207
|
@global_mutex = ::Monitor.new
|
|
209
208
|
|
|
210
|
-
##
|
|
211
209
|
# @private
|
|
212
|
-
#
|
|
213
210
|
def self.synchronize(&block)
|
|
214
211
|
@global_mutex.synchronize(&block)
|
|
215
212
|
end
|
|
216
213
|
|
|
214
|
+
# @private
|
|
215
|
+
def self.delete_at_exit(path)
|
|
216
|
+
# Call this from a class method so it doesn't hold onto the instance
|
|
217
|
+
# for the duration of the Ruby process
|
|
218
|
+
at_exit { ::FileUtils.rm_f(path) }
|
|
219
|
+
end
|
|
220
|
+
|
|
217
221
|
private
|
|
218
222
|
|
|
219
223
|
def terminal
|
|
@@ -344,7 +348,7 @@ module Toys
|
|
|
344
348
|
elsif ::RUBY_VERSION < "3"
|
|
345
349
|
[">= 2.2", "< 2.5"]
|
|
346
350
|
else
|
|
347
|
-
["
|
|
351
|
+
[">= 2.2", "< 5"]
|
|
348
352
|
end
|
|
349
353
|
end
|
|
350
354
|
|
|
@@ -421,11 +425,10 @@ module Toys
|
|
|
421
425
|
end
|
|
422
426
|
|
|
423
427
|
def delete_modified_gemfile(modified_gemfile_path)
|
|
424
|
-
|
|
425
|
-
::File.delete(modified_gemfile_path) if ::File.exist?(modified_gemfile_path)
|
|
428
|
+
::FileUtils.rm_f(modified_gemfile_path)
|
|
426
429
|
modified_lockfile_path = find_lockfile_path(modified_gemfile_path)
|
|
427
|
-
::
|
|
428
|
-
|
|
430
|
+
::FileUtils.rm_f(modified_lockfile_path)
|
|
431
|
+
Gems.delete_at_exit(modified_lockfile_path)
|
|
429
432
|
end
|
|
430
433
|
|
|
431
434
|
def restore_toys_libs
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require "logger"
|
|
4
|
+
|
|
3
5
|
module Toys
|
|
4
6
|
module Utils
|
|
5
7
|
##
|
|
@@ -28,7 +30,6 @@ module Toys
|
|
|
28
30
|
# terminal output. Default is `$stderr`.
|
|
29
31
|
#
|
|
30
32
|
def initialize(output: nil)
|
|
31
|
-
require "logger"
|
|
32
33
|
require "toys/utils/terminal"
|
|
33
34
|
@terminal = output || $stderr
|
|
34
35
|
@terminal = Terminal.new(output: @terminal) unless @terminal.is_a?(Terminal)
|
metadata
CHANGED
|
@@ -1,14 +1,28 @@
|
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
|
2
2
|
name: toys-core
|
|
3
3
|
version: !ruby/object:Gem::Version
|
|
4
|
-
version: 0.
|
|
4
|
+
version: 0.19.1
|
|
5
5
|
platform: ruby
|
|
6
6
|
authors:
|
|
7
7
|
- Daniel Azuma
|
|
8
8
|
bindir: bin
|
|
9
9
|
cert_chain: []
|
|
10
10
|
date: 1980-01-02 00:00:00.000000000 Z
|
|
11
|
-
dependencies:
|
|
11
|
+
dependencies:
|
|
12
|
+
- !ruby/object:Gem::Dependency
|
|
13
|
+
name: logger
|
|
14
|
+
requirement: !ruby/object:Gem::Requirement
|
|
15
|
+
requirements:
|
|
16
|
+
- - ">="
|
|
17
|
+
- !ruby/object:Gem::Version
|
|
18
|
+
version: '0'
|
|
19
|
+
type: :runtime
|
|
20
|
+
prerelease: false
|
|
21
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
22
|
+
requirements:
|
|
23
|
+
- - ">="
|
|
24
|
+
- !ruby/object:Gem::Version
|
|
25
|
+
version: '0'
|
|
12
26
|
description: Toys-Core is the command line tool framework underlying Toys. It can
|
|
13
27
|
be used to create command line executables using the Toys DSL and classes.
|
|
14
28
|
email:
|
|
@@ -78,10 +92,10 @@ homepage: https://github.com/dazuma/toys
|
|
|
78
92
|
licenses:
|
|
79
93
|
- MIT
|
|
80
94
|
metadata:
|
|
81
|
-
changelog_uri: https://dazuma.github.io/toys/gems/toys-core/v0.
|
|
95
|
+
changelog_uri: https://dazuma.github.io/toys/gems/toys-core/v0.19.1/file.CHANGELOG.html
|
|
82
96
|
source_code_uri: https://github.com/dazuma/toys/tree/main/toys-core
|
|
83
97
|
bug_tracker_uri: https://github.com/dazuma/toys/issues
|
|
84
|
-
documentation_uri: https://dazuma.github.io/toys/gems/toys-core/v0.
|
|
98
|
+
documentation_uri: https://dazuma.github.io/toys/gems/toys-core/v0.19.1
|
|
85
99
|
rdoc_options: []
|
|
86
100
|
require_paths:
|
|
87
101
|
- lib
|
|
@@ -96,7 +110,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
|
96
110
|
- !ruby/object:Gem::Version
|
|
97
111
|
version: '0'
|
|
98
112
|
requirements: []
|
|
99
|
-
rubygems_version:
|
|
113
|
+
rubygems_version: 4.0.3
|
|
100
114
|
specification_version: 4
|
|
101
115
|
summary: Framework for creating command line executables
|
|
102
116
|
test_files: []
|