rubycli 0.1.7 → 0.2.0

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.
@@ -0,0 +1,110 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Compact examples that highlight how Rubycli parses different documentation styles.
4
+ module DocumentationStyleShowcase
5
+ module_function
6
+
7
+ # Canonical uppercase placeholders covering required, optional, and repeated forms.
8
+ #
9
+ # NAME [String] Name to display
10
+ # COUNT [Integer] Repetition count
11
+ # --prefix PREFIX [String] Descriptor printed before the subject
12
+ # --tags TAG... [String[]] Comma-separated or JSON list of tags (repeatable)
13
+ # --quiet [Boolean] Suppress the trailing newline marker
14
+ def canonical(subject, count = 1, prefix: nil, tags: nil, quiet: false)
15
+ build_payload(:canonical, subject, count, prefix, tags, quiet)
16
+ end
17
+
18
+ # Minimal uppercase form – same signature, but placeholders omit type hints entirely.
19
+ #
20
+ # NAME Name to display
21
+ # COUNT Repetition count
22
+ # --prefix PREFIX Descriptor printed before the subject
23
+ # --tags TAG... JSON/YAML array or comma-separated list
24
+ # --quiet
25
+ def canonical_min(subject, count = 1, prefix: nil, tags: nil, quiet: false)
26
+ build_payload(:canonical_min, subject, count, prefix, tags, quiet)
27
+ end
28
+
29
+ # Angle-bracket placeholders with parenthesized type hints.
30
+ # <subject> [String] Subject text to render
31
+ # <count> [Integer] Repetition count
32
+ # --prefix=<prefix> [String] Prefix that appears before the subject
33
+ # --tags <tag>... [String[]] JSON/YAML array or comma-separated tags (repeatable)
34
+ # --quiet [Boolean] Suppress the trailing newline marker
35
+ def angled(subject, count = 1, prefix: nil, tags: nil, quiet: false)
36
+ build_payload(:angled, subject, count, prefix, tags, quiet)
37
+ end
38
+
39
+ # Minimal angle-bracket form without explicit types.
40
+ #
41
+ # <subject> Subject text to render
42
+ # <count> Repetition count
43
+ # --prefix=<prefix> Prefix shown before the subject
44
+ # --tags <tag>... JSON/YAML array or comma-separated list
45
+ # --quiet
46
+ def angled_min(subject, count = 1, prefix: nil, tags: nil, quiet: false)
47
+ build_payload(:angled_min, subject, count, prefix, tags, quiet)
48
+ end
49
+
50
+ # Bracketed type hints combined with ellipsis for repeated values.
51
+ #
52
+ # <subject> [String] Subject identifier to capture
53
+ # <count> [Integer, nil] Repetition count (accepts nil)
54
+ # --prefix PREFIX [String, nil] Heading for the entry
55
+ # --tags <tag>... [String[]] JSON/YAML array or comma-separated tags (repeatable)
56
+ # --quiet [Boolean] Suppress the trailing newline marker
57
+ # => [Hash] Summary of captured attributes
58
+ def typed(subject, count = 1, prefix: nil, tags: nil, quiet: false)
59
+ build_payload(:typed, subject, count, prefix, tags, quiet)
60
+ end
61
+
62
+ # Minimal typed variant using compact notation.
63
+ #
64
+ # <subject> [String] Subject identifier
65
+ # <count> [Integer] Repetition count
66
+ # --prefix PREFIX [String] Title prefix
67
+ # --tags <tag>... [String[]] JSON/YAML array or comma-separated list
68
+ # --quiet [Boolean]
69
+ def typed_min(subject, count = 1, prefix: nil, tags: nil, quiet: false)
70
+ build_payload(:typed_min, subject, count, prefix, tags, quiet)
71
+ end
72
+
73
+ # YARD-style annotations without inline option metadata.
74
+ #
75
+ # @param subject [String] Subject to process
76
+ # @param count [Integer] Number of repetitions when composing the label
77
+ # @param prefix [String, nil] Descriptor printed before the subject
78
+ # @param tags [Array<String>, nil] Additional tags (JSON/YAML array or comma-separated, repeatable)
79
+ # @param quiet [Boolean] Suppress the trailing newline marker
80
+ # @return [Hash] Normalized configuration summary
81
+ def yard(subject, count = 1, prefix: nil, tags: nil, quiet: false)
82
+ build_payload(:yard, subject, count, prefix, tags, quiet)
83
+ end
84
+
85
+ # Minimal YARD-style form with the shortest acceptable annotations.
86
+ #
87
+ # @param subject Subject to process
88
+ # @param count Repetition count
89
+ # @param prefix Descriptor printed before the subject
90
+ # @param tags Tags list (JSON/YAML array or comma-separated)
91
+ # @param quiet Quiet flag
92
+ def yard_min(subject, count = 1, prefix: nil, tags: nil, quiet: false)
93
+ build_payload(:yard_min, subject, count, prefix, tags, quiet)
94
+ end
95
+
96
+ class << self
97
+ private
98
+
99
+ def build_payload(style, subject, count, prefix, tags, quiet)
100
+ {
101
+ style: style,
102
+ subject: subject,
103
+ count: count,
104
+ prefix: prefix,
105
+ tags: tags,
106
+ quiet: quiet
107
+ }
108
+ end
109
+ end
110
+ end
@@ -0,0 +1,14 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Example showcasing inference when documentation is incomplete.
4
+ module FallbackExample
5
+ module_function
6
+
7
+ # AMOUNT [Integer] Base amount to process
8
+ def scale(amount, factor = 2, clamp: nil, notify: false)
9
+ result = amount * factor
10
+ result = [result, clamp].min if clamp
11
+ puts "Scaled to #{result}" if notify
12
+ result
13
+ end
14
+ end
@@ -0,0 +1,13 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Example showing how comments beyond the live signature are treated.
4
+ module FallbackExampleWithExtraDocs
5
+ module_function
6
+
7
+ # AMOUNT [Integer] Base amount to process
8
+ # CLAMP [Integer] (unused placeholder)
9
+ # --ghost [Boolean] Imaginary toggle that is not implemented
10
+ def scale(amount)
11
+ amount * 2
12
+ end
13
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Example CLI target without doc comments; see README section 1.
4
+ module HelloApp
5
+ module_function
6
+
7
+ def greet(name)
8
+ puts "Hello, #{name}!"
9
+ end
10
+ end
@@ -0,0 +1,17 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Example CLI target with documentation-driven options; see README section 2.
4
+ module HelloApp
5
+ module_function
6
+
7
+ # NAME [String] Name to greet
8
+ # --shout [Boolean] Print in uppercase
9
+ def greet(name, shout: false)
10
+ message = "Hello, #{name}!"
11
+ message = message.upcase if shout
12
+ puts message
13
+ end
14
+ end
15
+
16
+ # Provide a constant that matches the file name so Rubycli infers it automatically.
17
+ HelloAppWithDocs = HelloApp
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'rubycli'
4
+
5
+ module HelloApp
6
+ module_function
7
+
8
+ # NAME [String] Name to greet
9
+ # --shout [Boolean] Print in uppercase
10
+ # => [String] Printed message
11
+ def greet(name, shout: false)
12
+ message = "Hello, #{name}!"
13
+ message = message.upcase if shout
14
+ puts message
15
+ message
16
+ end
17
+ end
18
+
19
+ Rubycli.run(HelloApp)
@@ -0,0 +1,16 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Demonstrates a file whose constant does not match the file name. By default
4
+ # Rubycli will ask you to call out the constant explicitly:
5
+ # rubycli examples/mismatched_constant_runner.rb FriendlyGreeter greet --message "Hello"
6
+ # To auto-select it, pass --auto-target (or set RUBYCLI_AUTO_TARGET=auto).
7
+ class FriendlyGreeter
8
+ # NAME [String] Text to display
9
+ # --message MESSAGE [String] Greeting to print (defaults to "Hello")
10
+ # --quiet [Boolean] Skip the stdout line (the return value is still printed)
11
+ def self.greet(name = 'friend', message: 'Hello', quiet: false)
12
+ output = "#{message}, #{name}!"
13
+ puts(output) unless quiet
14
+ output
15
+ end
16
+ end
@@ -0,0 +1,20 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Demonstrates how Rubycli behaves when a file defines multiple constants.
4
+ # By default Rubycli will prefer the constant whose name matches the file
5
+ # (MultiConstantRunner). To invoke HelperRunner instead, specify it explicitly:
6
+ # rubycli examples/multi_constant_runner.rb HelperRunner inspect
7
+ class HelperRunner
8
+ def self.inspect
9
+ puts('Helper invoked')
10
+ :helper
11
+ end
12
+ end
13
+
14
+ class MultiConstantRunner
15
+ # TEXT [String] Message to display
16
+ def self.echo(text = 'hello')
17
+ puts(text)
18
+ text
19
+ end
20
+ end
@@ -0,0 +1,46 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Demonstrates instance-only CLI with constructor arguments and eval/json modes.
4
+ class NewModeRunner
5
+ attr_reader :items, :options
6
+
7
+ # ITEMS [String[]] List input (comma-separated or a JSON array)
8
+ # --options [Hash] Extra settings (JSON or eval literals)
9
+ def initialize(items, options: {})
10
+ @items = items
11
+ @options = options
12
+ end
13
+
14
+ # --mode MODE [Symbol] Execution mode (summary or reverse)
15
+ def run(mode: :summary)
16
+ case mode.to_sym
17
+ when :summary
18
+ {
19
+ count: items.size,
20
+ uppercased: items.map(&:upcase),
21
+ options: options
22
+ }
23
+ when :reverse
24
+ items.reverse
25
+ else
26
+ warn "unknown mode: #{mode}"
27
+ nil
28
+ end
29
+ end
30
+ end
31
+
32
+ # Usage examples (from the repository root):
33
+ # # Instance methods only, so --new is required; the array can be JSON or comma-separated
34
+ # rubycli --new='["a","b","c"]' examples/new_mode_runner.rb run --mode reverse
35
+ # rubycli --new a,b,c examples/new_mode_runner.rb run --mode summary
36
+ #
37
+ # # JSON mode: every following argument is parsed strictly as JSON (bare words are rejected)
38
+ # rubycli --json-args --new='["a","b"]' examples/new_mode_runner.rb run
39
+ #
40
+ # # Eval mode: Ruby literals are accepted, but every argument must be valid Ruby
41
+ # rubycli --eval-args --new='%w[x y]' examples/new_mode_runner.rb run --mode ':reverse'
42
+ #
43
+ # # --new passes a single value (items), so keyword arguments go through a pre-script,
44
+ # # which builds the instance on its own and needs no --new
45
+ # rubycli --pre-script 'NewModeRunner.new(%w[a b c], options: {from: :pre})' \
46
+ # examples/new_mode_runner.rb run --mode summary
@@ -0,0 +1,22 @@
1
+ # frozen_string_literal: true
2
+
3
+ # Demonstrates literal enum choices and strict validation.
4
+ module StrictChoicesDemo
5
+ module_function
6
+
7
+ # LEVEL %i[info warn error] Report severity
8
+ # --format TARGET ["text", "json", "yaml"] Output destination (strings)
9
+ # --notify [Boolean] Print a notification banner
10
+ def report(level, format: 'text', notify: false)
11
+ payload = { level: level, format: format, notify: notify }
12
+ message = "[#{level.to_s.upcase}] format=#{format}"
13
+ message = "#{message} (notify enabled)" if notify
14
+ puts message
15
+ payload
16
+ end
17
+ end
18
+
19
+ if $PROGRAM_NAME == __FILE__
20
+ require 'rubycli'
21
+ Rubycli.run(StrictChoicesDemo)
22
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'date'
4
+ require 'time'
5
+ require 'bigdecimal'
6
+ require 'pathname'
7
+
8
+ # Demonstrates standard library type coercions.
9
+ module TypedArgumentsDemo
10
+ module_function
11
+
12
+ # --date DATE [Date] Planned schedule date
13
+ # --moment TIME [Time] Execution timestamp
14
+ # --budget AMOUNT [BigDecimal] Budget amount
15
+ # --input FILE [Pathname] Path to source file
16
+ def ingest(
17
+ date: Date.today,
18
+ moment: Time.now,
19
+ budget: BigDecimal('0'),
20
+ input: Pathname.new('.')
21
+ )
22
+ summary = <<~TEXT
23
+ Date: #{date.iso8601}
24
+ Moment: #{moment.utc.iso8601}
25
+ Budget: #{budget.to_s('F')}
26
+ Input: #{input.expand_path}
27
+ TEXT
28
+
29
+ puts summary
30
+ {
31
+ date: date,
32
+ moment: moment,
33
+ budget: budget,
34
+ input: input
35
+ }
36
+ end
37
+ end
38
+
39
+ if $PROGRAM_NAME == __FILE__
40
+ require 'rubycli'
41
+ Rubycli.run(TypedArgumentsDemo)
42
+ end
@@ -29,13 +29,9 @@ module Rubycli
29
29
  def apply_argument_coercions(positional_args, keyword_args)
30
30
  ensure_modes_compatible!
31
31
 
32
- if json_mode?
33
- coerce_values!(positional_args, keyword_args) { |value| @json_coercer.coerce_json_value(value) }
34
- end
32
+ coerce_values!(positional_args, keyword_args) { |value| @json_coercer.coerce_json_value(value) } if json_mode?
35
33
 
36
- if eval_mode?
37
- coerce_values!(positional_args, keyword_args) { |value| @eval_coercer.coerce_eval_value(value) }
38
- end
34
+ coerce_values!(positional_args, keyword_args) { |value| @eval_coercer.coerce_eval_value(value) } if eval_mode?
39
35
  rescue ::ArgumentError => e
40
36
  raise Rubycli::ArgumentError, e.message
41
37
  end
@@ -54,13 +50,13 @@ module Rubycli
54
50
  end
55
51
 
56
52
  def ensure_modes_compatible!
57
- if json_mode? && eval_mode?
58
- raise Rubycli::ArgumentError, '--json-args cannot be combined with --eval-args or --eval-lax'
59
- end
53
+ return unless json_mode? && eval_mode?
54
+
55
+ raise Rubycli::ArgumentError, '--json-args cannot be combined with --eval-args or --eval-lax'
60
56
  end
61
57
 
62
- def coerce_values!(positional_args, keyword_args)
63
- positional_args.map! { |value| yield(value) }
58
+ def coerce_values!(positional_args, keyword_args, &block)
59
+ positional_args.map!(&block)
64
60
  keyword_args.keys.each do |key|
65
61
  keyword_args[key] = yield(keyword_args[key])
66
62
  end