samovar 2.3.0 → 2.4.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.
- checksums.yaml +4 -4
- checksums.yaml.gz.sig +0 -0
- data/lib/samovar/command.rb +119 -26
- data/lib/samovar/error.rb +33 -2
- data/lib/samovar/failure.rb +1 -0
- data/lib/samovar/flags.rb +104 -6
- data/lib/samovar/many.rb +42 -1
- data/lib/samovar/nested.rb +40 -2
- data/lib/samovar/one.rb +42 -1
- data/lib/samovar/option.rb +66 -6
- data/lib/samovar/options.rb +73 -6
- data/lib/samovar/output/columns.rb +19 -0
- data/lib/samovar/output/header.rb +18 -0
- data/lib/samovar/output/row.rb +15 -2
- data/lib/samovar/output/rows.rb +40 -4
- data/lib/samovar/output/usage_formatter.rb +32 -14
- data/lib/samovar/output.rb +2 -2
- data/lib/samovar/split.rb +44 -3
- data/lib/samovar/table.rb +44 -2
- data/lib/samovar/version.rb +3 -2
- data/lib/samovar.rb +3 -3
- data/license.md +1 -1
- data/readme.md +10 -158
- data.tar.gz.sig +0 -0
- metadata +6 -9
- metadata.gz.sig +0 -0
data/lib/samovar/nested.rb
CHANGED
@@ -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
|
-
#
|
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,
|
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,
|
85
|
+
raise MissingValueError.new(parent, @key)
|
45
86
|
end
|
46
87
|
end
|
47
88
|
end
|
data/lib/samovar/option.rb
CHANGED
@@ -1,12 +1,26 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
|
6
|
-
require_relative
|
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})"]
|
data/lib/samovar/options.rb
CHANGED
@@ -1,12 +1,21 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2016-
|
4
|
+
# Copyright, 2016-2025, by Samuel Williams.
|
5
5
|
|
6
|
-
require_relative
|
6
|
+
require_relative "option"
|
7
7
|
|
8
8
|
module Samovar
|
9
|
+
# Represents a collection of command-line options.
|
10
|
+
#
|
11
|
+
# Options provide a DSL for defining multiple option flags in a single block.
|
9
12
|
class Options
|
13
|
+
# Parse and create an options collection from a block.
|
14
|
+
#
|
15
|
+
# @parameter arguments [Array] The arguments for the options collection.
|
16
|
+
# @parameter options [Hash] Additional options.
|
17
|
+
# @yields {|...| ...} A block that defines options using {#option}.
|
18
|
+
# @returns [Options] The frozen options collection.
|
10
19
|
def self.parse(*arguments, **options, &block)
|
11
20
|
options = self.new(*arguments, **options)
|
12
21
|
|
@@ -15,6 +24,10 @@ module Samovar
|
|
15
24
|
return options.freeze
|
16
25
|
end
|
17
26
|
|
27
|
+
# Initialize a new options collection.
|
28
|
+
#
|
29
|
+
# @parameter title [String] The title for this options group in usage output.
|
30
|
+
# @parameter key [Symbol] The key to use for storing parsed options.
|
18
31
|
def initialize(title = "Options", key: :options)
|
19
32
|
@title = title
|
20
33
|
@ordered = []
|
@@ -27,6 +40,9 @@ module Samovar
|
|
27
40
|
@defaults = {}
|
28
41
|
end
|
29
42
|
|
43
|
+
# Initialize a duplicate of this options collection.
|
44
|
+
#
|
45
|
+
# @parameter source [Options] The source options to duplicate.
|
30
46
|
def initialize_dup(source)
|
31
47
|
super
|
32
48
|
|
@@ -35,12 +51,29 @@ module Samovar
|
|
35
51
|
@defaults = @defaults.dup
|
36
52
|
end
|
37
53
|
|
54
|
+
# The title for this options group in usage output.
|
55
|
+
#
|
56
|
+
# @attribute [String]
|
38
57
|
attr :title
|
58
|
+
|
59
|
+
# The ordered list of options.
|
60
|
+
#
|
61
|
+
# @attribute [Array(Option)]
|
39
62
|
attr :ordered
|
40
63
|
|
64
|
+
# The key to use for storing parsed options.
|
65
|
+
#
|
66
|
+
# @attribute [Symbol]
|
41
67
|
attr :key
|
68
|
+
|
69
|
+
# The default values for options.
|
70
|
+
#
|
71
|
+
# @attribute [Hash]
|
42
72
|
attr :defaults
|
43
73
|
|
74
|
+
# Freeze this options collection.
|
75
|
+
#
|
76
|
+
# @returns [Options] The frozen options collection.
|
44
77
|
def freeze
|
45
78
|
return self if frozen?
|
46
79
|
|
@@ -53,24 +86,41 @@ module Samovar
|
|
53
86
|
super
|
54
87
|
end
|
55
88
|
|
89
|
+
# Iterate over each option.
|
90
|
+
#
|
91
|
+
# @yields {|option| ...} Each option in the collection.
|
56
92
|
def each(&block)
|
57
93
|
@ordered.each(&block)
|
58
94
|
end
|
59
95
|
|
96
|
+
# Check if this options collection is empty.
|
97
|
+
#
|
98
|
+
# @returns [Boolean] True if there are no options.
|
60
99
|
def empty?
|
61
100
|
@ordered.empty?
|
62
101
|
end
|
63
102
|
|
103
|
+
# Define a new option in this collection.
|
104
|
+
#
|
105
|
+
# @parameter arguments [Array] The arguments for the option.
|
106
|
+
# @parameter options [Hash] Additional options.
|
107
|
+
# @yields {|value| ...} An optional block to transform the parsed value.
|
64
108
|
def option(*arguments, **options, &block)
|
65
109
|
self << Option.new(*arguments, **options, &block)
|
66
110
|
end
|
67
111
|
|
112
|
+
# Merge another options collection into this one.
|
113
|
+
#
|
114
|
+
# @parameter options [Options] The options to merge.
|
68
115
|
def merge!(options)
|
69
116
|
options.each do |option|
|
70
117
|
self << option
|
71
118
|
end
|
72
119
|
end
|
73
120
|
|
121
|
+
# Add an option to this collection.
|
122
|
+
#
|
123
|
+
# @parameter option [Option] The option to add.
|
74
124
|
def << option
|
75
125
|
@ordered << option
|
76
126
|
option.flags.each do |flag|
|
@@ -86,24 +136,41 @@ module Samovar
|
|
86
136
|
end
|
87
137
|
end
|
88
138
|
|
139
|
+
# Parse options from the input.
|
140
|
+
#
|
141
|
+
# @parameter input [Array(String)] The command-line arguments.
|
142
|
+
# @parameter parent [Command | Nil] The parent command.
|
143
|
+
# @parameter default [Hash | Nil] Default values to use.
|
144
|
+
# @returns [Hash] The parsed option values.
|
89
145
|
def parse(input, parent = nil, default = nil)
|
90
146
|
values = (default || @defaults).dup
|
91
147
|
|
92
148
|
while option = @keyed[input.first]
|
93
|
-
prefix = input.first
|
149
|
+
# prefix = input.first
|
94
150
|
result = option.parse(input)
|
95
151
|
if result != nil
|
96
152
|
values[option.key] = result
|
97
153
|
end
|
98
154
|
end
|
99
155
|
|
156
|
+
# Validate required options
|
157
|
+
@ordered.each do |option|
|
158
|
+
if option.required && !values.key?(option.key)
|
159
|
+
raise MissingValueError.new(parent, option.key)
|
160
|
+
end
|
161
|
+
end
|
162
|
+
|
100
163
|
return values
|
101
|
-
end
|
102
|
-
|
164
|
+
end # Generate a string representation for usage output.
|
165
|
+
#
|
166
|
+
# @returns [String] The usage string.
|
103
167
|
def to_s
|
104
|
-
@ordered.collect(&:to_s).join(
|
168
|
+
@ordered.collect(&:to_s).join(" ")
|
105
169
|
end
|
106
170
|
|
171
|
+
# Generate usage information for this options collection.
|
172
|
+
#
|
173
|
+
# @parameter rows [Output::Rows] The rows to append usage information to.
|
107
174
|
def usage(rows)
|
108
175
|
@ordered.each do |option|
|
109
176
|
rows << option
|
@@ -4,15 +4,34 @@
|
|
4
4
|
# Copyright, 2016-2023, by Samuel Williams.
|
5
5
|
|
6
6
|
module Samovar
|
7
|
+
# Namespace for output formatting classes.
|
7
8
|
module Output
|
9
|
+
end
|
10
|
+
end
|
11
|
+
|
12
|
+
module Samovar
|
13
|
+
module Output
|
14
|
+
# Represents column widths for aligned output formatting.
|
15
|
+
#
|
16
|
+
# Calculates the maximum width of each column across all rows for proper text alignment.
|
8
17
|
class Columns
|
18
|
+
# Initialize column width calculator.
|
19
|
+
#
|
20
|
+
# @parameter rows [Array(Array)] The rows to calculate column widths from.
|
9
21
|
def initialize(rows)
|
10
22
|
@rows = rows
|
11
23
|
@widths = calculate_widths(rows)
|
12
24
|
end
|
13
25
|
|
26
|
+
# The calculated column widths.
|
27
|
+
#
|
28
|
+
# @attribute [Array(Integer)]
|
14
29
|
attr :widths
|
15
30
|
|
31
|
+
# Calculate the maximum width for each column.
|
32
|
+
#
|
33
|
+
# @parameter rows [Array(Array)] The rows to analyze.
|
34
|
+
# @returns [Array(Integer)] The maximum width of each column.
|
16
35
|
def calculate_widths(rows)
|
17
36
|
widths = []
|
18
37
|
|
@@ -5,15 +5,33 @@
|
|
5
5
|
|
6
6
|
module Samovar
|
7
7
|
module Output
|
8
|
+
# Represents a header row in usage output.
|
9
|
+
#
|
10
|
+
# Headers display command names and their descriptions.
|
8
11
|
class Header
|
12
|
+
# Initialize a new header.
|
13
|
+
#
|
14
|
+
# @parameter name [String] The command name.
|
15
|
+
# @parameter object [Command] The command class.
|
9
16
|
def initialize(name, object)
|
10
17
|
@name = name
|
11
18
|
@object = object
|
12
19
|
end
|
13
20
|
|
21
|
+
# The command name.
|
22
|
+
#
|
23
|
+
# @attribute [String]
|
14
24
|
attr :name
|
25
|
+
|
26
|
+
# The command class.
|
27
|
+
#
|
28
|
+
# @attribute [Command]
|
15
29
|
attr :object
|
16
30
|
|
31
|
+
# Generate an aligned header string.
|
32
|
+
#
|
33
|
+
# @parameter columns [Columns] The columns for alignment (unused for headers).
|
34
|
+
# @returns [String] The command line usage string.
|
17
35
|
def align(columns)
|
18
36
|
@object.command_line(@name)
|
19
37
|
end
|
data/lib/samovar/output/row.rb
CHANGED
@@ -1,22 +1,35 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
|
6
6
|
module Samovar
|
7
7
|
module Output
|
8
|
+
# Represents a row in usage output.
|
9
|
+
#
|
10
|
+
# Rows display formatted option or argument information with proper column alignment.
|
8
11
|
class Row < Array
|
12
|
+
# Initialize a new row.
|
13
|
+
#
|
14
|
+
# @parameter object [Object] The object to convert to a row (must respond to `to_a`).
|
9
15
|
def initialize(object)
|
10
16
|
@object = object
|
11
17
|
super object.to_a.collect(&:to_s)
|
12
18
|
end
|
13
19
|
|
20
|
+
# The source object for this row.
|
21
|
+
#
|
22
|
+
# @attribute [Object]
|
14
23
|
attr :object
|
15
24
|
|
25
|
+
# Generate an aligned row string.
|
26
|
+
#
|
27
|
+
# @parameter columns [Columns] The columns for alignment.
|
28
|
+
# @returns [String] The aligned row string.
|
16
29
|
def align(columns)
|
17
30
|
self.collect.with_index do |value, index|
|
18
31
|
value.ljust(columns.widths[index])
|
19
|
-
end.join(
|
32
|
+
end.join(" ")
|
20
33
|
end
|
21
34
|
end
|
22
35
|
end
|
data/lib/samovar/output/rows.rb
CHANGED
@@ -1,40 +1,65 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
3
|
# Released under the MIT License.
|
4
|
-
# Copyright, 2019-
|
4
|
+
# Copyright, 2019-2025, by Samuel Williams.
|
5
5
|
|
6
|
-
require_relative
|
7
|
-
require_relative
|
8
|
-
require_relative
|
6
|
+
require_relative "header"
|
7
|
+
require_relative "columns"
|
8
|
+
require_relative "row"
|
9
9
|
|
10
10
|
module Samovar
|
11
11
|
module Output
|
12
|
+
# Represents a collection of rows for usage output.
|
13
|
+
#
|
14
|
+
# Manages hierarchical usage information with support for nesting and formatting.
|
12
15
|
class Rows
|
13
16
|
include Enumerable
|
14
17
|
|
18
|
+
# Initialize a new rows collection.
|
19
|
+
#
|
20
|
+
# @parameter level [Integer] The indentation level for this collection.
|
15
21
|
def initialize(level = 0)
|
16
22
|
@level = level
|
17
23
|
@rows = []
|
18
24
|
end
|
19
25
|
|
26
|
+
# The indentation level.
|
27
|
+
#
|
28
|
+
# @attribute [Integer]
|
20
29
|
attr :level
|
21
30
|
|
31
|
+
# Check if this collection is empty.
|
32
|
+
#
|
33
|
+
# @returns [Boolean] True if there are no rows.
|
22
34
|
def empty?
|
23
35
|
@rows.empty?
|
24
36
|
end
|
25
37
|
|
38
|
+
# Get the first row.
|
39
|
+
#
|
40
|
+
# @returns [Object | Nil] The first row.
|
26
41
|
def first
|
27
42
|
@rows.first
|
28
43
|
end
|
29
44
|
|
45
|
+
# Get the last row.
|
46
|
+
#
|
47
|
+
# @returns [Object | Nil] The last row.
|
30
48
|
def last
|
31
49
|
@rows.last
|
32
50
|
end
|
33
51
|
|
52
|
+
# Get the indentation string for this level.
|
53
|
+
#
|
54
|
+
# @returns [String] The indentation string.
|
34
55
|
def indentation
|
35
56
|
@indentation ||= "\t" * @level
|
36
57
|
end
|
37
58
|
|
59
|
+
# Iterate over each row.
|
60
|
+
#
|
61
|
+
# @parameter ignore_nested [Boolean] Whether to skip nested rows.
|
62
|
+
# @yields {|row, rows| ...} Each row with its parent collection.
|
38
63
|
def each(ignore_nested: false, &block)
|
39
64
|
return to_enum(:each, ignore_nested: ignore_nested) unless block_given?
|
40
65
|
|
@@ -47,16 +72,27 @@ module Samovar
|
|
47
72
|
end
|
48
73
|
end
|
49
74
|
|
75
|
+
# Add a row to this collection.
|
76
|
+
#
|
77
|
+
# @parameter object [Object] The object to add as a row.
|
78
|
+
# @returns [Rows] Self.
|
50
79
|
def << object
|
51
80
|
@rows << Row.new(object)
|
52
81
|
|
53
82
|
return self
|
54
83
|
end
|
55
84
|
|
85
|
+
# Get the columns for alignment.
|
86
|
+
#
|
87
|
+
# @returns [Columns] The columns calculator.
|
56
88
|
def columns
|
57
89
|
@columns ||= Columns.new(@rows.select{|row| row.is_a? Array})
|
58
90
|
end
|
59
91
|
|
92
|
+
# Create a nested section in the output.
|
93
|
+
#
|
94
|
+
# @parameter arguments [Array] Arguments for the header.
|
95
|
+
# @yields {|rows| ...} A block that populates the nested rows.
|
60
96
|
def nested(*arguments)
|
61
97
|
@rows << Header.new(*arguments)
|
62
98
|
|