samovar 2.3.0 → 2.4.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.
data/lib/samovar/flags.rb CHANGED
@@ -1,37 +1,61 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2016-2024, by Samuel Williams.
4
+ # Copyright, 2016-2025, by Samuel Williams.
5
5
 
6
6
  module Samovar
7
+ # Represents a collection of flag alternatives for an option.
8
+ #
9
+ # Flags parse text like `-f/--flag <value>` into individual flag parsers.
7
10
  class Flags
11
+ # Initialize a new flags parser.
12
+ #
13
+ # @parameter text [String] The flags specification string (e.g., `-f/--flag <value>`).
8
14
  def initialize(text)
9
15
  @text = text
10
16
 
11
17
  @ordered = text.split(/\s+\|\s+/).map{|part| Flag.parse(part)}
12
18
  end
13
19
 
20
+ # Iterate over each flag.
21
+ #
22
+ # @yields {|flag| ...} Each flag in the collection.
14
23
  def each(&block)
15
24
  @ordered.each(&block)
16
25
  end
17
26
 
27
+ # Get the first flag.
28
+ #
29
+ # @returns [Flag] The first flag.
18
30
  def first
19
31
  @ordered.first
20
32
  end
21
33
 
22
- # Whether or not this flag should have a true/false value if not specified otherwise.
34
+ # Whether this flag should have a true/false value if not specified otherwise.
35
+ #
36
+ # @returns [Boolean] True if this is a boolean flag.
23
37
  def boolean?
24
38
  @ordered.count == 1 and @ordered.first.boolean?
25
39
  end
26
40
 
41
+ # The number of flag alternatives.
42
+ #
43
+ # @returns [Integer] The count of flags.
27
44
  def count
28
45
  return @ordered.count
29
46
  end
30
47
 
48
+ # Generate a string representation for usage output.
49
+ #
50
+ # @returns [String] The usage string.
31
51
  def to_s
32
52
  "[#{@ordered.join(' | ')}]"
33
53
  end
34
54
 
55
+ # Parse a flag from the input.
56
+ #
57
+ # @parameter input [Array(String)] The command-line arguments.
58
+ # @returns [Object | Nil] The parsed value, or nil if no match.
35
59
  def parse(input)
36
60
  @ordered.each do |flag|
37
61
  result = flag.parse(input)
@@ -44,7 +68,14 @@ module Samovar
44
68
  end
45
69
  end
46
70
 
71
+ # Represents a single command-line flag.
72
+ #
73
+ # A flag can be a simple boolean flag or a flag that accepts a value.
47
74
  class Flag
75
+ # Parse a flag specification string into a flag instance.
76
+ #
77
+ # @parameter text [String] The flag specification (e.g., `-f <value>` or `--flag`).
78
+ # @returns [Flag] A flag instance (either {ValueFlag} or {BooleanFlag}).
48
79
  def self.parse(text)
49
80
  if text =~ /(.*?)\s(\<.*?\>)/
50
81
  ValueFlag.new(text, $1, $2)
@@ -55,54 +86,107 @@ module Samovar
55
86
  end
56
87
  end
57
88
 
89
+ # Initialize a new flag.
90
+ #
91
+ # @parameter text [String] The full flag specification text.
92
+ # @parameter prefix [String] The primary flag prefix (e.g., `--flag`).
93
+ # @parameter alternatives [Array(String) | Nil] Alternative flag prefixes.
58
94
  def initialize(text, prefix, alternatives = nil)
59
95
  @text = text
60
96
  @prefix = prefix
61
97
  @alternatives = alternatives
62
98
  end
63
99
 
100
+ # The full flag specification text.
101
+ #
102
+ # @attribute [String]
64
103
  attr :text
104
+
105
+ # The primary flag prefix.
106
+ #
107
+ # @attribute [String]
65
108
  attr :prefix
109
+
110
+ # Alternative flag prefixes.
111
+ #
112
+ # @attribute [Array(String) | Nil]
66
113
  attr :alternatives
67
114
 
115
+ # Generate a string representation for usage output.
116
+ #
117
+ # @returns [String] The flag text.
68
118
  def to_s
69
119
  @text
70
120
  end
71
121
 
122
+ # Generate a key name for this flag.
123
+ #
124
+ # @returns [Symbol] The key name.
72
125
  def key
73
- @key ||= @prefix.sub(/^-*/, '').gsub('-', '_').to_sym
126
+ @key ||= @prefix.sub(/^-*/, "").gsub("-", "_").to_sym
74
127
  end
75
128
 
129
+ # Whether this is a boolean flag.
130
+ #
131
+ # @returns [Boolean] False by default.
76
132
  def boolean?
77
133
  false
78
134
  end
79
135
  end
80
136
 
137
+ # Represents a flag that accepts a value or acts as a boolean.
81
138
  class ValueFlag < Flag
139
+ # Initialize a new value flag.
140
+ #
141
+ # @parameter text [String] The full flag specification text.
142
+ # @parameter prefix [String] The primary flag prefix with alternatives (e.g., `-f/--flag`).
143
+ # @parameter value [String | Nil] The value placeholder (e.g., `<file>`).
82
144
  def initialize(text, prefix, value)
83
145
  super(text, prefix)
84
146
 
85
147
  @value = value
86
148
 
87
- *@alternatives, @prefix = @prefix.split('/')
149
+ *@alternatives, @prefix = @prefix.split("/")
88
150
  end
89
151
 
152
+ # Alternative flag prefixes.
153
+ #
154
+ # @attribute [Array(String)]
90
155
  attr :alternatives
156
+
157
+ # The value placeholder.
158
+ #
159
+ # @attribute [String | Nil]
91
160
  attr :value
92
161
 
162
+ # Whether this is a boolean flag (no value required).
163
+ #
164
+ # @returns [Boolean] True if no value is required.
93
165
  def boolean?
94
166
  @value.nil?
95
167
  end
96
168
 
169
+ # Check if the token matches this flag.
170
+ #
171
+ # @parameter token [String] The token to check.
172
+ # @returns [Boolean] True if the token matches.
97
173
  def prefix?(token)
98
174
  @prefix == token or @alternatives.include?(token)
99
175
  end
100
176
 
177
+ # Parse this flag from the input.
178
+ #
179
+ # @parameter input [Array(String)] The command-line arguments.
180
+ # @returns [String | Symbol | Nil] The parsed value.
101
181
  def parse(input)
102
182
  if prefix?(input.first)
183
+ # Whether we are expecting to parse a value from input:
103
184
  if @value
104
- return input.shift(2).last
185
+ # Get the actual value from input:
186
+ flag, value = input.shift(2)
187
+ return value
105
188
  else
189
+ # Otherwise, we are just a boolean flag:
106
190
  input.shift
107
191
  return key
108
192
  end
@@ -110,20 +194,34 @@ module Samovar
110
194
  end
111
195
  end
112
196
 
197
+ # Represents a boolean flag with `--flag` and `--no-flag` variants.
113
198
  class BooleanFlag < Flag
199
+ # Initialize a new boolean flag.
200
+ #
201
+ # @parameter text [String] The full flag specification text.
202
+ # @parameter prefix [String] The primary flag prefix (e.g., `--flag`).
203
+ # @parameter value [Object | Nil] Reserved for future use.
114
204
  def initialize(text, prefix, value = nil)
115
205
  super(text, prefix)
116
206
 
117
207
  @value = value
118
208
 
119
- @negated = @prefix.sub(/^--/, '--no-')
209
+ @negated = @prefix.sub(/^--/, "--no-")
120
210
  @alternatives = [@negated]
121
211
  end
122
212
 
213
+ # Check if the token matches this flag.
214
+ #
215
+ # @parameter token [String] The token to check.
216
+ # @returns [Boolean] True if the token matches.
123
217
  def prefix?(token)
124
218
  @prefix == token or @negated == token
125
219
  end
126
220
 
221
+ # Parse this flag from the input.
222
+ #
223
+ # @parameter input [Array(String)] The command-line arguments.
224
+ # @returns [Boolean | Nil] True, false, or nil.
127
225
  def parse(input)
128
226
  if input.first == @prefix
129
227
  input.shift
data/lib/samovar/many.rb CHANGED
@@ -4,7 +4,17 @@
4
4
  # Copyright, 2016-2023, by Samuel Williams.
5
5
 
6
6
  module Samovar
7
+ # Represents multiple positional arguments in a command.
8
+ #
9
+ # A `Many` parser extracts all arguments from the command line until it encounters a stop pattern (typically an option flag).
7
10
  class Many
11
+ # Initialize a new multi-argument parser.
12
+ #
13
+ # @parameter key [Symbol] The name of the attribute to store the values in.
14
+ # @parameter description [String | Nil] A description of the arguments for help output.
15
+ # @parameter stop [Regexp] A pattern that indicates the end of this argument list.
16
+ # @parameter default [Object] The default value if no arguments are provided.
17
+ # @parameter required [Boolean] Whether at least one argument is required.
8
18
  def initialize(key, description = nil, stop: /^-/, default: nil, required: false)
9
19
  @key = key
10
20
  @description = description
@@ -13,16 +23,41 @@ module Samovar
13
23
  @required = required
14
24
  end
15
25
 
26
+ # The name of the attribute to store the values in.
27
+ #
28
+ # @attribute [Symbol]
16
29
  attr :key
30
+
31
+ # A description of the arguments for help output.
32
+ #
33
+ # @attribute [String | Nil]
17
34
  attr :description
35
+
36
+ # A pattern that indicates the end of this argument list.
37
+ #
38
+ # @attribute [Regexp]
18
39
  attr :stop
40
+
41
+ # The default value if no arguments are provided.
42
+ #
43
+ # @attribute [Object]
19
44
  attr :default
45
+
46
+ # Whether at least one argument is required.
47
+ #
48
+ # @attribute [Boolean]
20
49
  attr :required
21
50
 
51
+ # Generate a string representation for usage output.
52
+ #
53
+ # @returns [String] The usage string.
22
54
  def to_s
23
55
  "<#{key}...>"
24
56
  end
25
57
 
58
+ # Generate an array representation for usage output.
59
+ #
60
+ # @returns [Array] The usage array.
26
61
  def to_a
27
62
  usage = [to_s, @description]
28
63
 
@@ -35,6 +70,12 @@ module Samovar
35
70
  return usage
36
71
  end
37
72
 
73
+ # Parse multiple arguments from the input.
74
+ #
75
+ # @parameter input [Array(String)] The command-line arguments.
76
+ # @parameter parent [Command | Nil] The parent command.
77
+ # @parameter default [Object | Nil] An override for the default value.
78
+ # @returns [Array(String) | Object | Nil] The parsed values, or the default if none match.
38
79
  def parse(input, parent = nil, default = nil)
39
80
  if @stop and stop_index = input.index{|item| @stop === item}
40
81
  input.shift(stop_index)
@@ -43,7 +84,7 @@ module Samovar
43
84
  elsif default ||= @default
44
85
  return default
45
86
  elsif @required
46
- raise MissingValueError.new(parent, self)
87
+ raise MissingValueError.new(parent, @key)
47
88
  end
48
89
  end
49
90
  end
@@ -4,7 +4,16 @@
4
4
  # Copyright, 2016-2023, by Samuel Williams.
5
5
 
6
6
  module Samovar
7
+ # Represents nested sub-commands in a command.
8
+ #
9
+ # A `Nested` parser allows you to define multiple sub-commands that can be invoked from the parent command.
7
10
  class Nested
11
+ # Initialize a new nested command parser.
12
+ #
13
+ # @parameter key [Symbol] The name of the attribute to store the selected command in.
14
+ # @parameter commands [Hash] A mapping of command names to command classes.
15
+ # @parameter default [String | Nil] The default command name if none is provided.
16
+ # @parameter required [Boolean] Whether a command is required.
8
17
  def initialize(key, commands, default: nil, required: false)
9
18
  @key = key
10
19
  @commands = commands
@@ -15,15 +24,36 @@ module Samovar
15
24
  @required = required
16
25
  end
17
26
 
27
+ # The name of the attribute to store the selected command in.
28
+ #
29
+ # @attribute [Symbol]
18
30
  attr :key
31
+
32
+ # A mapping of command names to command classes.
33
+ #
34
+ # @attribute [Hash]
19
35
  attr :commands
36
+
37
+ # The default command name if none is provided.
38
+ #
39
+ # @attribute [String | Nil]
20
40
  attr :default
41
+
42
+ # Whether a command is required.
43
+ #
44
+ # @attribute [Boolean]
21
45
  attr :required
22
46
 
47
+ # Generate a string representation for usage output.
48
+ #
49
+ # @returns [String] The usage string.
23
50
  def to_s
24
51
  "<#{@key}>"
25
52
  end
26
53
 
54
+ # Generate an array representation for usage output.
55
+ #
56
+ # @returns [Array] The usage array.
27
57
  def to_a
28
58
  usage = [self.to_s]
29
59
 
@@ -44,7 +74,12 @@ module Samovar
44
74
  return usage
45
75
  end
46
76
 
47
- # @param default [Command] the default command if any.
77
+ # Parse a nested command from the input.
78
+ #
79
+ # @parameter input [Array(String)] The command-line arguments.
80
+ # @parameter parent [Command | Nil] The parent command.
81
+ # @parameter default [Command | Nil] The default command instance.
82
+ # @returns [Command | Object | Nil] The parsed command instance, or the default if no match.
48
83
  def parse(input, parent = nil, default = nil)
49
84
  if command = @commands[input.first]
50
85
  name = input.shift
@@ -56,10 +91,13 @@ module Samovar
56
91
  elsif @default
57
92
  @commands[@default].new(input, name: @default, parent: parent)
58
93
  elsif @required
59
- raise MissingValueError.new(parent, self)
94
+ raise MissingValueError.new(parent, @key)
60
95
  end
61
96
  end
62
97
 
98
+ # Generate usage information for this nested command.
99
+ #
100
+ # @parameter rows [Output::Rows] The rows to append usage information to.
63
101
  def usage(rows)
64
102
  rows << self
65
103
 
data/lib/samovar/one.rb CHANGED
@@ -4,7 +4,17 @@
4
4
  # Copyright, 2016-2023, by Samuel Williams.
5
5
 
6
6
  module Samovar
7
+ # Represents a single positional argument in a command.
8
+ #
9
+ # A `One` parser extracts exactly one argument from the command line that matches the specified pattern.
7
10
  class One
11
+ # Initialize a new positional argument parser.
12
+ #
13
+ # @parameter key [Symbol] The name of the attribute to store the value in.
14
+ # @parameter description [String] A description of the argument for help output.
15
+ # @parameter pattern [Regexp] A pattern to match valid values.
16
+ # @parameter default [Object] The default value if no argument is provided.
17
+ # @parameter required [Boolean] Whether the argument is required.
8
18
  def initialize(key, description, pattern: //, default: nil, required: false)
9
19
  @key = key
10
20
  @description = description
@@ -13,16 +23,41 @@ module Samovar
13
23
  @required = required
14
24
  end
15
25
 
26
+ # The name of the attribute to store the value in.
27
+ #
28
+ # @attribute [Symbol]
16
29
  attr :key
30
+
31
+ # A description of the argument for help output.
32
+ #
33
+ # @attribute [String]
17
34
  attr :description
35
+
36
+ # A pattern to match valid values.
37
+ #
38
+ # @attribute [Regexp]
18
39
  attr :pattern
40
+
41
+ # The default value if no argument is provided.
42
+ #
43
+ # @attribute [Object]
19
44
  attr :default
45
+
46
+ # Whether the argument is required.
47
+ #
48
+ # @attribute [Boolean]
20
49
  attr :required
21
50
 
51
+ # Generate a string representation for usage output.
52
+ #
53
+ # @returns [String] The usage string.
22
54
  def to_s
23
55
  "<#{@key}>"
24
56
  end
25
57
 
58
+ # Generate an array representation for usage output.
59
+ #
60
+ # @returns [Array] The usage array.
26
61
  def to_a
27
62
  usage = [to_s, @description]
28
63
 
@@ -35,13 +70,19 @@ module Samovar
35
70
  return usage
36
71
  end
37
72
 
73
+ # Parse a single argument from the input.
74
+ #
75
+ # @parameter input [Array(String)] The command-line arguments.
76
+ # @parameter parent [Command | Nil] The parent command.
77
+ # @parameter default [Object | Nil] An override for the default value.
78
+ # @returns [String | Object | Nil] The parsed value, or the default if no match.
38
79
  def parse(input, parent = nil, default = nil)
39
80
  if input.first =~ @pattern
40
81
  input.shift
41
82
  elsif default ||= @default
42
83
  return default
43
84
  elsif @required
44
- raise MissingValueError.new(parent, self)
85
+ raise MissingValueError.new(parent, @key)
45
86
  end
46
87
  end
47
88
  end
@@ -1,12 +1,26 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2024, by Samuel Williams.
4
+ # Copyright, 2019-2025, by Samuel Williams.
5
5
 
6
- require_relative 'flags'
6
+ require_relative "flags"
7
+ require_relative "error"
7
8
 
8
9
  module Samovar
10
+ # Represents a single command-line option.
11
+ #
12
+ # An option is a flag-based argument that can have various forms (short, long, with or without values).
9
13
  class Option
14
+ # Initialize a new option.
15
+ #
16
+ # @parameter flags [String] The flags specification (e.g., `-f/--flag <value>`).
17
+ # @parameter description [String] A description of the option for help output.
18
+ # @parameter key [Symbol | Nil] The key to use for storing the value (defaults to derived from flag).
19
+ # @parameter default [Object] The default value if the option is not provided.
20
+ # @parameter value [Object | Nil] A fixed value to use regardless of user input.
21
+ # @parameter type [Class | Proc | Nil] The type to coerce the value to.
22
+ # @parameter required [Boolean] Whether the option is required.
23
+ # @yields {|value| ...} An optional block to transform the parsed value.
10
24
  def initialize(flags, description, key: nil, default: nil, value: nil, type: nil, required: false, &block)
11
25
  @flags = Flags.new(flags)
12
26
  @description = description
@@ -28,17 +42,50 @@ module Samovar
28
42
  @block = block
29
43
  end
30
44
 
45
+ # The flags for this option.
46
+ #
47
+ # @attribute [Flags]
31
48
  attr :flags
49
+
50
+ # A description of the option for help output.
51
+ #
52
+ # @attribute [String]
32
53
  attr :description
54
+
55
+ # The key to use for storing the value.
56
+ #
57
+ # @attribute [Symbol]
33
58
  attr :key
59
+
60
+ # The default value if the option is not provided.
61
+ #
62
+ # @attribute [Object]
34
63
  attr :default
35
64
 
65
+ # A fixed value to use regardless of user input.
66
+ #
67
+ # @attribute [Object | Nil]
36
68
  attr :value
37
69
 
70
+ # The type to coerce the value to.
71
+ #
72
+ # @attribute [Class | Proc | Nil]
38
73
  attr :type
74
+
75
+ # Whether the option is required.
76
+ #
77
+ # @attribute [Boolean]
39
78
  attr :required
79
+
80
+ # An optional block to transform the parsed value.
81
+ #
82
+ # @attribute [Proc | Nil]
40
83
  attr :block
41
84
 
85
+ # Coerce the result to the specified type.
86
+ #
87
+ # @parameter result [Object] The value to coerce.
88
+ # @returns [Object] The coerced value.
42
89
  def coerce_type(result)
43
90
  if @type == Integer
44
91
  Integer(result)
@@ -53,6 +100,10 @@ module Samovar
53
100
  end
54
101
  end
55
102
 
103
+ # Coerce and transform the result.
104
+ #
105
+ # @parameter result [Object] The value to coerce and transform.
106
+ # @returns [Object] The coerced and transformed value.
56
107
  def coerce(result)
57
108
  if @type
58
109
  result = coerce_type(result)
@@ -65,21 +116,30 @@ module Samovar
65
116
  return result
66
117
  end
67
118
 
119
+ # Parse this option from the input.
120
+ #
121
+ # @parameter input [Array(String)] The command-line arguments.
122
+ # @parameter parent [Command | Nil] The parent command (unused, kept for compatibility).
123
+ # @parameter default [Object | Nil] An override for the default value (unused, kept for compatibility).
124
+ # @returns [Object | Nil] The parsed value.
68
125
  def parse(input, parent = nil, default = nil)
69
126
  result = @flags.parse(input)
127
+
70
128
  if result != nil
71
129
  @value.nil? ? coerce(result) : @value
72
- elsif default ||= @default
73
- return default
74
- elsif @required
75
- raise MissingValueError.new(parent, self)
76
130
  end
77
131
  end
78
132
 
133
+ # Generate a string representation for usage output.
134
+ #
135
+ # @returns [String] The usage string.
79
136
  def to_s
80
137
  @flags
81
138
  end
82
139
 
140
+ # Generate an array representation for usage output.
141
+ #
142
+ # @returns [Array] The usage array.
83
143
  def to_a
84
144
  if @default
85
145
  [@flags, @description, "(default: #{@default})"]