cli-dispatcher 1.1.12 → 1.2.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/lib/structured.rb +170 -56
- metadata +7 -6
checksums.yaml
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
---
|
2
2
|
SHA256:
|
3
|
-
metadata.gz:
|
4
|
-
data.tar.gz:
|
3
|
+
metadata.gz: e946454a75a033c55b82ca4814654e27e75f8635ea02b42ad5fbd95a6cc7b4c8
|
4
|
+
data.tar.gz: f964e67077a8e2077a44db8b033d5a82be7fcbe10e9f2cad11504b6116e8397e
|
5
5
|
SHA512:
|
6
|
-
metadata.gz:
|
7
|
-
data.tar.gz:
|
6
|
+
metadata.gz: d93f6c815a777f40501094b84bc386178f8a45b20b6dccb4809667d3c7f855b2899a1aa48df04e9cc71eee43353b4105dd911ad1c422f187e276193aa75e8446
|
7
|
+
data.tar.gz: 6d81ee52b9cac75a9446b221b7b8c8edf767b5998146a87a14bfe0e9353e80ebfbb4b57ce639c3d2d7c1b7f5fb29c1bc809197b3d07aa71a7cfd7c355295eb99
|
data/lib/structured.rb
CHANGED
@@ -36,6 +36,8 @@ require 'yaml'
|
|
36
36
|
# As a result, at the end of the initialization of a Structured object, it will
|
37
37
|
# have instance variables set corresponding to all the defined elements.
|
38
38
|
#
|
39
|
+
# == Customization of Structured Classes
|
40
|
+
#
|
39
41
|
# The above explanation is default behavior, and several customizations are
|
40
42
|
# available.
|
41
43
|
#
|
@@ -58,6 +60,57 @@ require 'yaml'
|
|
58
60
|
# Please read the documentation for Structured::ClassMethods for more on
|
59
61
|
# defining expected elements, type checking, and so on.
|
60
62
|
#
|
63
|
+
# == Subfiles as Input
|
64
|
+
#
|
65
|
+
# The Structured class provides automatic support for separating inputs into
|
66
|
+
# YAML subfiles. This is useful for including complex objects in a file. Two
|
67
|
+
# types of subfile inputs are supported: those for object hashes, and those for
|
68
|
+
# arrays.
|
69
|
+
#
|
70
|
+
# To include a subfile as part of an object hash, include the key `read_file` in
|
71
|
+
# the hash, with the value being the file to be read. (Other keys besides
|
72
|
+
# `read_file` may be included.) The subfile should itself contain YAML for a
|
73
|
+
# hash with further keys for the object.
|
74
|
+
#
|
75
|
+
# To include multiple subfiles in an array, set the first element of the array
|
76
|
+
# to the string `read_file`, and then the other elements of the array should be
|
77
|
+
# filenames. These subfiles should contain YAML for arrays.
|
78
|
+
#
|
79
|
+
# Consider a Structured object for a book, containing elements for the title,
|
80
|
+
# subtitle, and an array of authors. The input file could look like this:
|
81
|
+
#
|
82
|
+
# ---
|
83
|
+
# title: A Book
|
84
|
+
# subtitle: Containing Many Pages
|
85
|
+
# author:
|
86
|
+
# - John Q. Public
|
87
|
+
# - Jane Doe
|
88
|
+
#
|
89
|
+
# Using the subfile feature, the input file could instead look like:
|
90
|
+
#
|
91
|
+
# ---
|
92
|
+
# title: A Book
|
93
|
+
# read_file: subtitle_file.yaml
|
94
|
+
# author:
|
95
|
+
# - read_file
|
96
|
+
# - author_file.yaml
|
97
|
+
#
|
98
|
+
# This would instruct Structured to read hash keys out of `subtitle_file.yaml`,
|
99
|
+
# and to read array elements out of `author_file.yaml`. These two files, in
|
100
|
+
# turn, should look like:
|
101
|
+
#
|
102
|
+
# # subtitle_file.yaml
|
103
|
+
# ---
|
104
|
+
# subtitle: Containing Many Pages
|
105
|
+
#
|
106
|
+
# # author_file.yaml
|
107
|
+
# ---
|
108
|
+
# - John Q. Public
|
109
|
+
# - Jane Doe
|
110
|
+
#
|
111
|
+
# When incorporated, Structured will combine these subfiles as if they were a
|
112
|
+
# single object specification.
|
113
|
+
#
|
61
114
|
module Structured
|
62
115
|
|
63
116
|
#
|
@@ -172,7 +225,8 @@ module Structured
|
|
172
225
|
# raises an error, but classes may override this method to use the undefined
|
173
226
|
# elements.
|
174
227
|
#
|
175
|
-
# @param element The unknown element name,
|
228
|
+
# @param element The unknown element name. For a YAML file, this is typically
|
229
|
+
# a string.
|
176
230
|
#
|
177
231
|
# @param val The value associated with the unknown element.
|
178
232
|
#
|
@@ -212,6 +266,7 @@ module Structured
|
|
212
266
|
def reset_elements
|
213
267
|
@elements = {}
|
214
268
|
@default_element = nil
|
269
|
+
@default_key = nil
|
215
270
|
@class_description = nil
|
216
271
|
end
|
217
272
|
|
@@ -266,14 +321,32 @@ module Structured
|
|
266
321
|
|
267
322
|
#
|
268
323
|
# Accepts a default element for this class. The arguments are the same as
|
269
|
-
# those for element_data.
|
324
|
+
# those for element_data except as noted below.
|
270
325
|
#
|
271
|
-
#
|
272
|
-
#
|
326
|
+
# If this method is called, then for any keys found in an input hash that
|
327
|
+
# have no corresponding #element declaration in the Structured class, the
|
328
|
+
# method receive_any will be invoked. The value from the input hash
|
329
|
+
# will be processed based on any type declaration, `preproc`, and `check`
|
330
|
+
# given to default_element.
|
331
|
+
#
|
332
|
+
# The default element keys can be processed based on the argument `key`,
|
333
|
+
# which should be a hash corresponding to the element_data arguments plus
|
334
|
+
# the key :type with the default key's expected type. If `key` is not given,
|
335
|
+
# then the key must be and is automatically converted to a Symbol.
|
336
|
+
#
|
337
|
+
# **Caution**: The `type` argument should almost always be a single class,
|
338
|
+
# and not a hash. This is because the default arguments are automatically
|
273
339
|
# treated like a hash, with the otherwise-undefined element names being the
|
274
340
|
# keys of the hash.
|
275
341
|
#
|
276
342
|
def default_element(*args, **params)
|
343
|
+
if (key_params = params.delete(:key))
|
344
|
+
@default_key = element_data(
|
345
|
+
key_params.delete(:type) || Object, **key_params
|
346
|
+
)
|
347
|
+
else
|
348
|
+
@default_key = element_data(Symbol, preproc: proc { |s| s.to_sym })
|
349
|
+
end
|
277
350
|
@default_element = element_data(*args, **params)
|
278
351
|
end
|
279
352
|
|
@@ -384,47 +457,28 @@ module Structured
|
|
384
457
|
input_err("Initializer is not a Hash") unless hash.is_a?(Hash)
|
385
458
|
hash = try_read_file(hash)
|
386
459
|
|
387
|
-
@elements.each do |
|
388
|
-
Structured.trace(
|
389
|
-
val = hash[
|
390
|
-
|
391
|
-
|
392
|
-
if data[:preproc]
|
393
|
-
val = try_run(data[:preproc], obj, val, "preproc")
|
394
|
-
next if process_nil_val(obj, elt, val, data)
|
395
|
-
end
|
396
|
-
|
397
|
-
cval = convert_item(val, data[:type], obj)
|
398
|
-
|
399
|
-
# Check for validity after preproc and conversion are run
|
400
|
-
if data[:check] && !try_run(data[:check], obj, cval, "check")
|
401
|
-
input_err "Value #{cval} failed check for #{elt}"
|
402
|
-
end
|
403
|
-
|
404
|
-
# Use the converted value
|
405
|
-
apply_val(obj, elt, cval)
|
460
|
+
@elements.each do |key, data|
|
461
|
+
Structured.trace(key.to_s) do
|
462
|
+
val = hash[key] || hash[key.to_s]
|
463
|
+
cval = process_value(obj, val, data)
|
464
|
+
apply_val(obj, key, cval) if cval
|
406
465
|
end
|
407
466
|
end
|
408
467
|
|
409
468
|
# Process unknown elements
|
410
|
-
|
411
|
-
return if
|
469
|
+
unknown_keys = hash.keys.reject { |k| @elements.include?(k.to_sym) }
|
470
|
+
return if unknown_keys.empty?
|
412
471
|
unless @default_element
|
413
|
-
input_err("Unexpected element(s): #{
|
472
|
+
input_err("Unexpected element(s): #{unknown_keys.join(', ')}")
|
414
473
|
end
|
415
|
-
|
416
|
-
Structured.trace(
|
417
|
-
|
418
|
-
|
419
|
-
|
420
|
-
|
421
|
-
|
422
|
-
|
423
|
-
if de[:check] && !try_run(de[:check], obj, item, "check")
|
424
|
-
input_err "Value #{item} failed default element check"
|
425
|
-
end
|
426
|
-
item.receive_key(elt) if item.is_a?(Structured)
|
427
|
-
obj.receive_any(elt, item)
|
474
|
+
unknown_keys.each do |key|
|
475
|
+
Structured.trace(key.to_s) do
|
476
|
+
val = hash[key]
|
477
|
+
ckey = process_value(obj, key, @default_key)
|
478
|
+
cval = process_value(obj, val, @default_element)
|
479
|
+
next unless cval
|
480
|
+
cval.receive_key(ckey) if cval.is_a?(Structured)
|
481
|
+
obj.receive_any(ckey, cval)
|
428
482
|
end
|
429
483
|
end
|
430
484
|
end
|
@@ -450,24 +504,68 @@ module Structured
|
|
450
504
|
end
|
451
505
|
end
|
452
506
|
|
453
|
-
|
454
|
-
|
507
|
+
def try_read_array(filenames)
|
508
|
+
new_item = []
|
509
|
+
begin
|
510
|
+
filenames.each do |file|
|
511
|
+
begin
|
512
|
+
res = YAML.load_file(file)
|
513
|
+
raise InputError unless res.is_a?(Array)
|
514
|
+
new_item.concat(res)
|
515
|
+
rescue
|
516
|
+
input_err("Failed to read array from #{file}: #$!")
|
517
|
+
end
|
518
|
+
end
|
519
|
+
end
|
520
|
+
return new_item
|
521
|
+
end
|
522
|
+
|
523
|
+
#
|
524
|
+
# Given an element value and an #element_data hash of processing tools
|
525
|
+
# element, applies those processing tools. Namely, apply any preproc, check
|
526
|
+
# the type and perform other checks, and perform any conversions. The return
|
527
|
+
# value should be usable as the received value for the corresponding
|
528
|
+
# element.
|
529
|
+
#
|
530
|
+
# If this method returns nil, then there is no element to process. This
|
531
|
+
# method may also raise an InputError.
|
532
|
+
#
|
533
|
+
def process_value(obj, val, data)
|
534
|
+
val, ret = process_nil_val(val, data)
|
535
|
+
return val if ret
|
536
|
+
if data[:preproc]
|
537
|
+
val = try_run(data[:preproc], obj, val, "preproc")
|
538
|
+
val, ret = process_nil_val(val, data)
|
539
|
+
return val if ret
|
540
|
+
end
|
541
|
+
|
542
|
+
cval = convert_item(val, data[:type], obj)
|
543
|
+
if data[:check] && !try_run(data[:check], obj, cval, "check")
|
544
|
+
input_err "Value #{cval} failed check"
|
545
|
+
end
|
546
|
+
return cval
|
547
|
+
end
|
548
|
+
|
455
549
|
#
|
456
|
-
#
|
550
|
+
# Performs processing of an element value to deal with the possibility that
|
551
|
+
# the value is nil. This method returns [ the new value, boolean of whether
|
552
|
+
# to stop processing ] according to the following rules:
|
553
|
+
#
|
554
|
+
# * If val is non-nil, then this method returns val itself, and processing
|
555
|
+
# should not stop.
|
457
556
|
# * If val is nil and this element is non-optional, then this method raises
|
458
557
|
# an error.
|
459
|
-
# * If val is nil and the element is optional,
|
460
|
-
#
|
461
|
-
#
|
462
|
-
#
|
463
|
-
#
|
464
|
-
|
465
|
-
|
466
|
-
|
467
|
-
|
468
|
-
|
469
|
-
|
470
|
-
return true
|
558
|
+
# * If val is nil and the element is optional, then the object's default
|
559
|
+
# value is returned, and processing should stop.
|
560
|
+
# * If there is no default value for an optional element, then nil is
|
561
|
+
# returned, and processing should also stop.
|
562
|
+
#
|
563
|
+
def process_nil_val(val, data)
|
564
|
+
return [ val, false ] if val
|
565
|
+
unless data[:optional]
|
566
|
+
input_err("Required element is missing (or was deleted by a preproc)")
|
567
|
+
end
|
568
|
+
return [ data[:default], true ]
|
471
569
|
end
|
472
570
|
|
473
571
|
# Applies a value to an element for an object, after all processing for the
|
@@ -504,6 +602,7 @@ module Structured
|
|
504
602
|
when Array
|
505
603
|
input_err("#{item} is not Array") unless item.is_a?(Array)
|
506
604
|
Structured.trace(Array) do
|
605
|
+
item = try_read_array(item[1..-1]) if item.first.to_s == 'read_file'
|
507
606
|
return item.map.with_index { |i, idx|
|
508
607
|
Structured.trace(idx) do
|
509
608
|
convert_item(i, type.first, parent)
|
@@ -542,12 +641,22 @@ module Structured
|
|
542
641
|
end
|
543
642
|
end
|
544
643
|
|
644
|
+
#
|
645
|
+
# Several types can be automatically converted:
|
646
|
+
#
|
647
|
+
# * Symbol into String
|
648
|
+
# * String into Regexp
|
649
|
+
#
|
545
650
|
def try_autoconvert(type, item)
|
546
651
|
|
547
652
|
if type == String && item.is_a?(Symbol)
|
548
653
|
return item.to_s
|
549
654
|
end
|
550
655
|
|
656
|
+
if type == Symbol && item.is_a?(String)
|
657
|
+
return item.to_sym
|
658
|
+
end
|
659
|
+
|
551
660
|
# Special case in which strings will be converted to Regexps
|
552
661
|
if type == Regexp && item.is_a?(String)
|
553
662
|
begin
|
@@ -563,7 +672,11 @@ module Structured
|
|
563
672
|
# Receive hash values that are to be converted to Structured objects
|
564
673
|
def convert_structured(item, type, parent)
|
565
674
|
unless item.is_a?(Hash)
|
566
|
-
|
675
|
+
if type.include?(Structured)
|
676
|
+
input_err("#{item.inspect} not a Structured hash for #{type}")
|
677
|
+
else
|
678
|
+
input_err("#{item.inspect} not a #{type}")
|
679
|
+
end
|
567
680
|
end
|
568
681
|
|
569
682
|
unless type.include?(Structured) || type.include?(StructuredPolymorphic)
|
@@ -604,7 +717,8 @@ module Structured
|
|
604
717
|
|
605
718
|
if @default_element
|
606
719
|
io.puts(
|
607
|
-
" All other elements: #{describe_type(@
|
720
|
+
" All other elements: #{describe_type(@default_key[:type])} => " \
|
721
|
+
"#{describe_type(@default_element[:type])}"
|
608
722
|
)
|
609
723
|
if @default_element[:description]
|
610
724
|
io.puts(TextTools.line_break(
|
metadata
CHANGED
@@ -1,14 +1,14 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: cli-dispatcher
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.1
|
4
|
+
version: 1.2.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Charles Duan
|
8
|
-
autorequire:
|
8
|
+
autorequire:
|
9
9
|
bindir: bin
|
10
10
|
cert_chain: []
|
11
|
-
date: 2024-
|
11
|
+
date: 2024-12-02 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
13
|
description: |
|
14
14
|
Library for creating command-line programs that accept commands. Also
|
@@ -28,7 +28,8 @@ licenses:
|
|
28
28
|
- MIT
|
29
29
|
metadata:
|
30
30
|
source_code_uri: https://github.com/charlesduan/cli-dispatcher
|
31
|
-
|
31
|
+
documentation_uri: https://rubydoc.info/gems/cli-dispatcher/
|
32
|
+
post_install_message:
|
32
33
|
rdoc_options: []
|
33
34
|
require_paths:
|
34
35
|
- lib
|
@@ -43,8 +44,8 @@ required_rubygems_version: !ruby/object:Gem::Requirement
|
|
43
44
|
- !ruby/object:Gem::Version
|
44
45
|
version: '0'
|
45
46
|
requirements: []
|
46
|
-
rubygems_version: 3.
|
47
|
-
signing_key:
|
47
|
+
rubygems_version: 3.5.11
|
48
|
+
signing_key:
|
48
49
|
specification_version: 4
|
49
50
|
summary: Command-line command dispatcher
|
50
51
|
test_files: []
|