cli-dispatcher 1.1.11 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (3) hide show
  1. checksums.yaml +4 -4
  2. data/lib/structured.rb +101 -56
  3. metadata +4 -3
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 275e6d346e13d40131484394f8da7d754df96294e559ad1461628274d14755fb
4
- data.tar.gz: c7eb8d374b5eda6861b70465413574b67ed8e6fd9fa57d1b837a0df3d73c79a0
3
+ metadata.gz: 850d024672db8785ec7d329961a30baf9e8d4f1724189f6cf6250472cc17e5e8
4
+ data.tar.gz: 11e2ab38ddd9cc99d7ec2b66bf4b97d229a470ded4295ef2e7ecddca99bca74e
5
5
  SHA512:
6
- metadata.gz: 781dd82a1715c122bd8293605bd0bb2480e51128efdec7758b982bfe3cd7c02b7cf93f25427716b1d4e302664524b329bcbebaa74d9ee956ba1a46e392d9c050
7
- data.tar.gz: c3221e641bbc6b2461b500ab626de8954ab7f8e87cef2bb5bdcdebf910b2ba38bd7e0a4f069a9bd67ee2f82212cba7c2f49dacbca23f78e8439fa7a31b564b8e
6
+ metadata.gz: 3f5a3f85f1f64785b526df560efe2ef2ee94b031d0ef9bd47d43fd6500eaf4faba0b70a6230d1ccbc49db4d9a8d0681a1ba17edd847c105ab0b8d106568ccb2f
7
+ data.tar.gz: e6c3a31bd0825b3ae234dd68b6432e5a5b7f0fe4daf76a9494545018d264615630f89368b1c352d950be23a66be76e36fa5a3059bb38ad411d37f91912480f5a
data/lib/structured.rb CHANGED
@@ -172,7 +172,8 @@ module Structured
172
172
  # raises an error, but classes may override this method to use the undefined
173
173
  # elements.
174
174
  #
175
- # @param element The unknown element name, converted to a symbol.
175
+ # @param element The unknown element name. For a YAML file, this is typically
176
+ # a string.
176
177
  #
177
178
  # @param val The value associated with the unknown element.
178
179
  #
@@ -212,6 +213,7 @@ module Structured
212
213
  def reset_elements
213
214
  @elements = {}
214
215
  @default_element = nil
216
+ @default_key = nil
215
217
  @class_description = nil
216
218
  end
217
219
 
@@ -266,14 +268,32 @@ module Structured
266
268
 
267
269
  #
268
270
  # Accepts a default element for this class. The arguments are the same as
269
- # those for element_data.
271
+ # those for element_data except as noted below.
270
272
  #
271
- # **Caution**: The type argument should almost always be a single class, and
272
- # not a hash. This is because the default arguments are automatically
273
+ # If this method is called, then for any keys found in an input hash that
274
+ # have no corresponding #element declaration in the Structured class, the
275
+ # method receive_any will be invoked. The value from the input hash
276
+ # will be processed based on any type declaration, `preproc`, and `check`
277
+ # given to default_element.
278
+ #
279
+ # The default element keys can be processed based on the argument `key`,
280
+ # which should be a hash corresponding to the element_data arguments plus
281
+ # the key :type with the default key's expected type. If `key` is not given,
282
+ # then the key must be and is automatically converted to a Symbol.
283
+ #
284
+ # **Caution**: The `type` argument should almost always be a single class,
285
+ # and not a hash. This is because the default arguments are automatically
273
286
  # treated like a hash, with the otherwise-undefined element names being the
274
287
  # keys of the hash.
275
288
  #
276
289
  def default_element(*args, **params)
290
+ if (key_params = params.delete(:key))
291
+ @default_key = element_data(
292
+ key_params.delete(:type) || Object, key_params
293
+ )
294
+ else
295
+ @default_key = element_data(Symbol, preproc: proc { |s| s.to_sym })
296
+ end
277
297
  @default_element = element_data(*args, **params)
278
298
  end
279
299
 
@@ -384,47 +404,28 @@ module Structured
384
404
  input_err("Initializer is not a Hash") unless hash.is_a?(Hash)
385
405
  hash = try_read_file(hash)
386
406
 
387
- @elements.each do |elt, data|
388
- Structured.trace(elt.to_s) do
389
- val = hash[elt] || hash[elt.to_s]
390
- next if process_nil_val(obj, elt, val, data)
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)
407
+ @elements.each do |key, data|
408
+ Structured.trace(key.to_s) do
409
+ val = hash[key] || hash[key.to_s]
410
+ cval = process_value(obj, val, data)
411
+ apply_val(obj, key, cval) if cval
406
412
  end
407
413
  end
408
414
 
409
415
  # Process unknown elements
410
- unknown_elts = (hash.keys.map(&:to_sym) - @elements.keys)
411
- return if unknown_elts.empty?
416
+ unknown_keys = hash.keys.reject { |k| @elements.include?(k.to_sym) }
417
+ return if unknown_keys.empty?
412
418
  unless @default_element
413
- input_err("Unexpected element(s): #{unknown_elts.join(', ')}")
419
+ input_err("Unexpected element(s): #{unknown_keys.join(', ')}")
414
420
  end
415
- unknown_elts.each do |elt|
416
- Structured.trace(elt.to_s) do
417
- de = @default_element
418
- val = hash[elt] || hash[elt.to_s]
419
- if de[:preproc]
420
- val = try_run(de[:preproc], obj, val, "default preproc")
421
- end
422
- item = convert_item(val, de[:type], obj)
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)
421
+ unknown_keys.each do |key|
422
+ Structured.trace(key.to_s) do
423
+ val = hash[key]
424
+ ckey = process_value(obj, key, @default_key)
425
+ cval = process_value(obj, val, @default_element)
426
+ next unless cval
427
+ cval.receive_key(ckey) if cval.is_a?(Structured)
428
+ obj.receive_any(ckey, cval)
428
429
  end
429
430
  end
430
431
  end
@@ -450,24 +451,53 @@ module Structured
450
451
  end
451
452
  end
452
453
 
453
- # Deals with a nil value (either because no value was given, or because a
454
- # preproc deleted it).
454
+
455
+ #
456
+ # Given an element value and an #element_data hash of processing tools
457
+ # element, applies those processing tools. Namely, apply any preproc, check
458
+ # the type and perform other checks, and perform any conversions. The return
459
+ # value should be usable as the received value for the corresponding
460
+ # element.
455
461
  #
456
- # * If val is non-nil, then this method returns false.
462
+ # If this method returns nil, then there is no element to process. This
463
+ # method may also raise an InputError.
464
+ #
465
+ def process_value(obj, val, data)
466
+ val, ret = process_nil_val(val, data)
467
+ return val if ret
468
+ if data[:preproc]
469
+ val = try_run(data[:preproc], obj, val, "preproc")
470
+ val, ret = process_nil_val(val, data)
471
+ return val if ret
472
+ end
473
+
474
+ cval = convert_item(val, data[:type], obj)
475
+ if data[:check] && !try_run(data[:check], obj, cval, "check")
476
+ input_err "Value #{cval} failed check"
477
+ end
478
+ return cval
479
+ end
480
+
481
+ #
482
+ # Performs processing of an element value to deal with the possibility that
483
+ # the value is nil. This method returns [ the new value, boolean of whether
484
+ # to stop processing ] according to the following rules:
485
+ #
486
+ # * If val is non-nil, then this method returns val itself, and processing
487
+ # should not stop.
457
488
  # * If val is nil and this element is non-optional, then this method raises
458
489
  # an error.
459
- # * If val is nil and the element is optional, *and* the element has a
460
- # default value, then the object has the default value applied to the
461
- # element.
462
- # * In any event, if val is nil and the element is optional, returns true
463
- # which should signal to the caller to stop further processing of the
464
- # element.
465
- #
466
- def process_nil_val(obj, elt, val, data)
467
- return false if val
468
- input_err("Missing (or preproc deleted) #{elt}") unless data[:optional]
469
- apply_val(obj, elt, data[:default]) unless data[:default].nil?
470
- return true
490
+ # * If val is nil and the element is optional, then the object's default
491
+ # value is returned, and processing should stop.
492
+ # * If there is no default value for an optional element, then nil is
493
+ # returned, and processing should also stop.
494
+ #
495
+ def process_nil_val(val, data)
496
+ return [ val, false ] if val
497
+ unless data[:optional]
498
+ input_err("Required element is missing (or was deleted by a preproc)")
499
+ end
500
+ return [ data[:default], true ]
471
501
  end
472
502
 
473
503
  # Applies a value to an element for an object, after all processing for the
@@ -542,12 +572,22 @@ module Structured
542
572
  end
543
573
  end
544
574
 
575
+ #
576
+ # Several types can be automatically converted:
577
+ #
578
+ # * Symbol into String
579
+ # * String into Regexp
580
+ #
545
581
  def try_autoconvert(type, item)
546
582
 
547
583
  if type == String && item.is_a?(Symbol)
548
584
  return item.to_s
549
585
  end
550
586
 
587
+ if type == Symbol && item.is_a?(String)
588
+ return item.to_sym
589
+ end
590
+
551
591
  # Special case in which strings will be converted to Regexps
552
592
  if type == Regexp && item.is_a?(String)
553
593
  begin
@@ -563,7 +603,11 @@ module Structured
563
603
  # Receive hash values that are to be converted to Structured objects
564
604
  def convert_structured(item, type, parent)
565
605
  unless item.is_a?(Hash)
566
- input_err("#{item.inspect} not a #{type} or Structured hash")
606
+ if type.include?(Structured)
607
+ input_err("#{item.inspect} not a Structured hash for #{type}")
608
+ else
609
+ input_err("#{item.inspect} not a #{type}")
610
+ end
567
611
  end
568
612
 
569
613
  unless type.include?(Structured) || type.include?(StructuredPolymorphic)
@@ -604,7 +648,8 @@ module Structured
604
648
 
605
649
  if @default_element
606
650
  io.puts(
607
- " All other elements: #{describe_type(@default_element[:type])}"
651
+ " All other elements: #{describe_type(@default_key[:type])} => " \
652
+ "#{describe_type(@default_element[:type])}"
608
653
  )
609
654
  if @default_element[:description]
610
655
  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.11
4
+ version: 1.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Charles Duan
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2024-11-13 00:00:00.000000000 Z
11
+ date: 2024-11-15 00:00:00.000000000 Z
12
12
  dependencies: []
13
13
  description: |
14
14
  Library for creating command-line programs that accept commands. Also
@@ -26,7 +26,8 @@ files:
26
26
  homepage: https://github.com/charlesduan/cli-dispatcher
27
27
  licenses:
28
28
  - MIT
29
- metadata: {}
29
+ metadata:
30
+ source_code_uri: https://github.com/charlesduan/cli-dispatcher
30
31
  post_install_message:
31
32
  rdoc_options: []
32
33
  require_paths: