samovar 2.4.0 → 2.4.2

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 85e0df9ca2ed86284ab62c49ef724f93818ecb8571a33cccca98759f0a6b2612
4
- data.tar.gz: 833c98f6619c7fb73243f6bbbbbb84e25a8d100c03af55392bd97f20a4246803
3
+ metadata.gz: 467711ed04ecc759513ab6187d17a8c5208fa24869d202fb23a46217d3ceedab
4
+ data.tar.gz: 293c9e07e9f0ff0b9550b0ad3411b5e10f460a6c72dd8be2bc254d985dec95da
5
5
  SHA512:
6
- metadata.gz: 0aff1d4bc6f8c2154dcd7e216a8f082fc50b44239321a122b139edff54e0749ab7109a45ab87e2615677e181426717b4912d81118f87f01cd4bf7032653d2041
7
- data.tar.gz: d3c94b2986da89eba71d18d06df18dd0541775f2f1c2ba2c327e8407a38e05757e073b5f97c137c8c0241ab29811fd9e0a3fed3c2613034ca7db15ef0e5aff12
6
+ metadata.gz: bf717af75e91db10dc75008e1421601181168190aa89ea5bd289ca5e832505d1382cee60ac7e1b66d4500673779cc2cc5325dc33110ce4871443fa93c820d817
7
+ data.tar.gz: c7c0f9b82b3ce5502c29da74b7810f69c63687237b2a7550dde8ff5eda35f3db6739848907d9669a2083df9ec186097c90d44e4e119aa1176ca1c6a52c7f6f07
checksums.yaml.gz.sig CHANGED
Binary file
@@ -0,0 +1,215 @@
1
+ # Getting Started
2
+
3
+ This guide explains how to use `samovar` to build command-line tools and applications.
4
+
5
+ ## Installation
6
+
7
+ Add the gem to your project:
8
+
9
+ ~~~ bash
10
+ $ bundle add samovar
11
+ ~~~
12
+
13
+ Or install it yourself as:
14
+
15
+ ~~~ bash
16
+ $ gem install samovar
17
+ ~~~
18
+
19
+ ## Core Concepts
20
+
21
+ Samovar provides a declarative class-based DSL for building command-line parsers. The main concepts include:
22
+
23
+ - **Commands**: Classes that represent specific functions in your program, inheriting from {ruby Samovar::Command}.
24
+ - **Options**: Command-line flags and arguments that can be parsed using the `options` block.
25
+ - **Nested Commands**: Sub-commands that can be composed using the `nested` method.
26
+ - **Tokens**: Positional arguments parsed using `one` and `many` methods.
27
+ - **Splits**: Separating arguments at a specific delimiter (e.g., `--`) using the `split` method.
28
+
29
+ ## Usage
30
+
31
+ Create `Command` classes that represent specific functions in your program. The top level command might look something like this:
32
+
33
+ ~~~ ruby
34
+ require "samovar"
35
+
36
+ class List < Samovar::Command
37
+ self.description = "List the current directory"
38
+
39
+ def call
40
+ system("ls -lah")
41
+ end
42
+ end
43
+
44
+ class Application < Samovar::Command
45
+ options do
46
+ option "--help", "Do you need help?"
47
+ end
48
+
49
+ nested :command, {
50
+ "list" => List
51
+ }, default: "list"
52
+
53
+ def call
54
+ if @options[:help]
55
+ self.print_usage
56
+ else
57
+ @command.call
58
+ end
59
+ end
60
+ end
61
+
62
+ Application.call # Defaults to ARGV.
63
+ ~~~
64
+
65
+ ### Basic Options
66
+
67
+ Options allow you to parse command-line flags and arguments:
68
+
69
+ ~~~ ruby
70
+ require "samovar"
71
+
72
+ class Application < Samovar::Command
73
+ options do
74
+ option "-f/--frobulate <text>", "Frobulate the text"
75
+ option "-x | -y", "Specify either x or y axis.", key: :axis
76
+ option "-F/--yeah/--flag", "A boolean flag with several forms."
77
+ option "--things <a,b,c>", "A list of things" do |value|
78
+ value.split(/\s*,\s*/)
79
+ end
80
+ end
81
+ end
82
+
83
+ application = Application.new(["-f", "Algebraic!"])
84
+ application.options[:frobulate] # 'Algebraic!'
85
+
86
+ application = Application.new(["-x", "-y"])
87
+ application.options[:axis] # :y
88
+
89
+ application = Application.new(["-F"])
90
+ application.options[:flag] # true
91
+
92
+ application = Application.new(["--things", "x,y,z"])
93
+ application.options[:things] # ['x', 'y', 'z']
94
+ ~~~
95
+
96
+ ### Nested Commands
97
+
98
+ You can create sub-commands that are nested within your main application:
99
+
100
+ ~~~ ruby
101
+ require "samovar"
102
+
103
+ class Create < Samovar::Command
104
+ def invoke(parent)
105
+ puts "Creating"
106
+ end
107
+ end
108
+
109
+ class Application < Samovar::Command
110
+ nested "<command>",
111
+ "create" => Create
112
+
113
+ def invoke(program_name: File.basename($0))
114
+ if @command
115
+ @command.invoke
116
+ else
117
+ print_usage(program_name)
118
+ end
119
+ end
120
+ end
121
+
122
+ Application.new(["create"]).invoke
123
+ ~~~
124
+
125
+ ### ARGV Splits
126
+
127
+ You can split arguments at a delimiter (typically `--`):
128
+
129
+ ~~~ ruby
130
+ require "samovar"
131
+
132
+ class Application < Samovar::Command
133
+ many :packages
134
+ split :argv
135
+ end
136
+
137
+ application = Application.new(["foo", "bar", "baz", "--", "apples", "oranges", "feijoas"])
138
+ application.packages # ['foo', 'bar', 'baz']
139
+ application.argv # ['apples', 'oranges', 'feijoas']
140
+ ~~~
141
+
142
+ ### Parsing Tokens
143
+
144
+ You can parse positional arguments using `one` and `many`:
145
+
146
+ ~~~ ruby
147
+ require "samovar"
148
+
149
+ class Application < Samovar::Command
150
+ self.description = "Mix together your favorite things."
151
+
152
+ one :fruit, "Name one fruit"
153
+ many :cakes, "Any cakes you like"
154
+ end
155
+
156
+ application = Application.new(["apple", "chocolate cake", "fruit cake"])
157
+ application.fruit # 'apple'
158
+ application.cakes # ['chocolate cake', 'fruit cake']
159
+ ~~~
160
+
161
+ ### Explicit Commands
162
+
163
+ Given a custom `Samovar::Command` subclass, you can instantiate it with options:
164
+
165
+ ~~~ ruby
166
+ application = Application["--root", path]
167
+ ~~~
168
+
169
+ You can also duplicate an existing command instance with additions/changes:
170
+
171
+ ~~~ ruby
172
+ concurrent_application = application["--threads", 12]
173
+ ~~~
174
+
175
+ These forms can be useful when invoking one command from another, or in unit tests.
176
+
177
+ ## Error Handling
178
+
179
+ Samovar provides two entry points with different error handling behaviors:
180
+
181
+ ### `call()` - High-Level Entry Point
182
+
183
+ Use `call()` for CLI applications. It handles parsing errors gracefully by printing usage information:
184
+
185
+ ~~~ ruby
186
+ # Automatically handles errors and prints usage
187
+ Application.call(ARGV)
188
+ ~~~
189
+
190
+ If parsing fails or the command raises a {ruby Samovar::Error}, it will:
191
+ - Print the usage information with the error highlighted
192
+ - Return `nil` instead of raising an exception
193
+
194
+ ### `parse()` - Low-Level Parsing
195
+
196
+ Use `parse()` when you need explicit error handling or for testing:
197
+
198
+ ~~~ ruby
199
+ begin
200
+ app = Application.parse(["--unknown-flag"])
201
+ rescue Samovar::InvalidInputError => error
202
+ # Handle the error yourself
203
+ puts "Invalid input: #{error.message}"
204
+ end
205
+ ~~~
206
+
207
+ The `parse()` method raises exceptions on parsing errors, giving you full control over error handling.
208
+
209
+ ### Error Types
210
+
211
+ Samovar defines several error types:
212
+
213
+ - {ruby Samovar::InvalidInputError}: Raised when unexpected command-line input is encountered
214
+ - {ruby Samovar::MissingValueError}: Raised when required arguments or options are missing
215
+
@@ -0,0 +1,14 @@
1
+ # Automatically generated context index for Utopia::Project guides.
2
+ # Do not edit then files in this directory directly, instead edit the guides and then run `bake utopia:project:agent:context:update`.
3
+ ---
4
+ description: Samovar is a flexible option parser excellent support for sub-commands
5
+ and help documentation.
6
+ metadata:
7
+ documentation_uri: https://ioquatix.github.io/samovar/
8
+ funding_uri: https://github.com/sponsors/ioquatix/
9
+ source_code_uri: https://github.com/ioquatix/samovar.git
10
+ files:
11
+ - path: getting-started.md
12
+ title: Getting Started
13
+ description: This guide explains how to use `samovar` to build command-line tools
14
+ and applications.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2017-2023, by Samuel Williams.
4
+ # Copyright, 2017-2026, by Samuel Williams.
5
5
 
6
6
  module Samovar
7
7
  # Represents a runtime failure in command execution.
data/lib/samovar/many.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2016-2023, by Samuel Williams.
4
+ # Copyright, 2016-2026, by Samuel Williams.
5
5
 
6
6
  module Samovar
7
7
  # Represents multiple positional arguments in a command.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2016-2023, by Samuel Williams.
4
+ # Copyright, 2016-2026, by Samuel Williams.
5
5
 
6
6
  module Samovar
7
7
  # Represents nested sub-commands in a command.
data/lib/samovar/one.rb CHANGED
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2016-2023, by Samuel Williams.
4
+ # Copyright, 2016-2026, by Samuel Williams.
5
5
 
6
6
  module Samovar
7
7
  # Represents a single positional argument in a command.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2016-2023, by Samuel Williams.
4
+ # Copyright, 2016-2026, by Samuel Williams.
5
5
 
6
6
  module Samovar
7
7
  # Namespace for output formatting classes.
@@ -1,7 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  # Released under the MIT License.
4
- # Copyright, 2019-2023, by Samuel Williams.
4
+ # Copyright, 2019-2026, by Samuel Williams.
5
5
 
6
6
  module Samovar
7
7
  module Output
@@ -2,8 +2,8 @@
2
2
 
3
3
  # Released under the MIT License.
4
4
  # Copyright, 2019-2025, by Samuel Williams.
5
+ # Copyright, 2026, by Gerhard Schlager.
5
6
 
6
- require "mapping/model"
7
7
  require "console/terminal"
8
8
 
9
9
  require_relative "../error"
@@ -17,8 +17,8 @@ module Samovar
17
17
  module Output
18
18
  # Formats and prints usage information to a terminal.
19
19
  #
20
- # Uses the `mapping` gem to handle different output object types with custom formatting rules.
21
- class UsageFormatter < Mapping::Model
20
+ # Dispatches on the type of each output object to apply custom formatting rules.
21
+ class UsageFormatter
22
22
  # Print usage information to the output.
23
23
  #
24
24
  # @parameter rows [Rows] The rows to format and print.
@@ -39,7 +39,6 @@ module Samovar
39
39
  def initialize(output)
40
40
  @output = output
41
41
  @width = 80
42
- @first = true
43
42
 
44
43
  @terminal = Console::Terminal.for(@output)
45
44
  @terminal[:header] = @terminal.style(nil, nil, :bright)
@@ -47,43 +46,50 @@ module Samovar
47
46
  @terminal[:error] = @terminal.style(:red)
48
47
  end
49
48
 
50
- map(InvalidInputError) do |error|
51
- # This is a little hack which avoids printing out "--help" if it was part of an incomplete parse. In the future I'd prefer if this was handled explicitly.
52
- @terminal.puts("#{error.message} in:", style: :error) unless error.help?
53
- end
54
-
55
- map(MissingValueError) do |error|
56
- @terminal.puts("#{error.message} in:", style: :error)
57
- end
58
-
59
- map(Header) do |header, rows|
60
- if @first
61
- @first = false
49
+ # Format and print the given object according to its type.
50
+ #
51
+ # @parameter object [Object] The object to format (a {Rows}, {Row}, {Header}, or error).
52
+ # @parameter arguments [Array] Extra context passed through to nested rows (the containing {Rows}).
53
+ def map(object, *arguments, first: true)
54
+ case object
55
+ when InvalidInputError
56
+ # This is a little hack which avoids printing out "--help" if it was part of an incomplete parse. In the future I'd prefer if this was handled explicitly.
57
+ @terminal.puts("#{object.message} in:", style: :error) unless object.help?
58
+ when MissingValueError
59
+ @terminal.puts("#{object.message} in:", style: :error)
60
+ when Header
61
+ header, rows = object, arguments.first
62
+
63
+ if first
64
+ first = false
65
+ else
66
+ @terminal.puts
67
+ end
68
+
69
+ command_line = header.object.command_line(header.name)
70
+ @terminal.puts "#{rows.indentation}#{command_line}", style: :header
71
+
72
+ if description = header.object.description
73
+ @terminal.puts "#{rows.indentation}\t#{description}", style: :description
74
+ @terminal.puts
75
+ end
76
+ when Row
77
+ row, rows = object, arguments.first
78
+ @terminal.puts "#{rows.indentation}#{row.align(rows.columns)}"
79
+ when Rows
80
+ object.each do |row, rows|
81
+ first = map(row, rows, first: first)
82
+ end
62
83
  else
63
- @terminal.puts
84
+ raise ArgumentError, "Unable to format #{object.class}!"
64
85
  end
65
86
 
66
- command_line = header.object.command_line(header.name)
67
- @terminal.puts "#{rows.indentation}#{command_line}", style: :header
68
-
69
- if description = header.object.description
70
- @terminal.puts "#{rows.indentation}\t#{description}", style: :description
71
- @terminal.puts
72
- end
73
- end
74
-
75
- map(Row) do |row, rows|
76
- @terminal.puts "#{rows.indentation}#{row.align(rows.columns)}"
77
- end
78
-
79
- map(Rows) do |items|
80
- items.collect{|row, rows| map(row, rows)}
87
+ return first
81
88
  end
82
89
 
83
90
  # Print the formatted usage output.
84
- def print(rows, first: @first)
85
- @first = first
86
- map(rows)
91
+ def print(rows, first: true)
92
+ map(rows, first: first)
87
93
  end
88
94
  end
89
95
  end
@@ -6,5 +6,5 @@
6
6
 
7
7
  # @namespace
8
8
  module Samovar
9
- VERSION = "2.4.0"
9
+ VERSION = "2.4.2"
10
10
  end
data/license.md CHANGED
@@ -1,7 +1,8 @@
1
1
  # MIT License
2
2
 
3
- Copyright, 2016-2025, by Samuel Williams.
3
+ Copyright, 2016-2026, by Samuel Williams.
4
4
  Copyright, 2018, by Gabriel Mazetto.
5
+ Copyright, 2026, by Gerhard Schlager.
5
6
 
6
7
  Permission is hereby granted, free of charge, to any person obtaining a copy
7
8
  of this software and associated documentation files (the "Software"), to deal
data/readme.md CHANGED
@@ -18,6 +18,32 @@ Please see the [project documentation](https://ioquatix.github.io/samovar/) for
18
18
 
19
19
  - [Getting Started](https://ioquatix.github.io/samovar/guides/getting-started/index) - This guide explains how to use `samovar` to build command-line tools and applications.
20
20
 
21
+ ## Releases
22
+
23
+ Please see the [project releases](https://ioquatix.github.io/samovar/releases/index) for all releases.
24
+
25
+ ### v2.4.2
26
+
27
+ - Drop dependency on `mapping` gem.
28
+
29
+ ### v2.4.0
30
+
31
+ - Fix option parsing and validation: required options are now detected correctly and raise `Samovar::MissingValueError` when missing.
32
+ - Fix flag value parsing: flags that expect a value no longer consume a following flag as their value (e.g. `--config <path>` will not consume `--verbose`).
33
+ - Usage improvements: required options are marked as `(required)` in usage output.
34
+
35
+ ### v2.3.0
36
+
37
+ - Add support for `--[no]-thing` explicit boolean flags, allowing users to explicitly enable or disable boolean options.
38
+
39
+ ### v2.2.0
40
+
41
+ - Add support for explicit output: commands can now specify an output stream (e.g. `STDOUT`, `STDERR`, or custom IO objects) via the `output:` parameter in `Command.call`.
42
+
43
+ ### v2.1.4
44
+
45
+ - `Command#to_s` now returns the class name by default, improving debugging and introspection.
46
+
21
47
  ## See Also
22
48
 
23
49
  - [Teapot](https://github.com/ioquatix/teapot/blob/master/lib/teapot/command.rb) is a build system and uses multiple top-level commands.
@@ -34,6 +60,22 @@ We welcome contributions to this project.
34
60
  4. Push to the branch (`git push origin my-new-feature`).
35
61
  5. Create new Pull Request.
36
62
 
63
+ ### Running Tests
64
+
65
+ To run the test suite:
66
+
67
+ ``` shell
68
+ bundle exec sus
69
+ ```
70
+
71
+ ### Making Releases
72
+
73
+ To make a new release:
74
+
75
+ ``` shell
76
+ bundle exec bake gem:release:patch # or minor or major
77
+ ```
78
+
37
79
  ### Developer Certificate of Origin
38
80
 
39
81
  In order to protect users of this project, we require all contributors to comply with the [Developer Certificate of Origin](https://developercertificate.org/). This ensures that all contributions are properly licensed and attributed.
@@ -42,7 +84,7 @@ In order to protect users of this project, we require all contributors to comply
42
84
 
43
85
  This project is best served by a collaborative and respectful environment. Treat each other professionally, respect differing viewpoints, and engage constructively. Harassment, discrimination, or harmful behavior is not tolerated. Communicate clearly, listen actively, and support one another. If any issues arise, please inform the project maintainers.
44
86
 
45
- ### Future Work
87
+ ## Future Work
46
88
 
47
89
  ### Multi-value Options
48
90
 
data/releases.md ADDED
@@ -0,0 +1,23 @@
1
+ # Releases
2
+
3
+ ## v2.4.2
4
+
5
+ - Drop dependency on `mapping` gem.
6
+
7
+ ## v2.4.0
8
+
9
+ - Fix option parsing and validation: required options are now detected correctly and raise `Samovar::MissingValueError` when missing.
10
+ - Fix flag value parsing: flags that expect a value no longer consume a following flag as their value (e.g. `--config <path>` will not consume `--verbose`).
11
+ - Usage improvements: required options are marked as `(required)` in usage output.
12
+
13
+ ## v2.3.0
14
+
15
+ - Add support for `--[no]-thing` explicit boolean flags, allowing users to explicitly enable or disable boolean options.
16
+
17
+ ## v2.2.0
18
+
19
+ - Add support for explicit output: commands can now specify an output stream (e.g. `STDOUT`, `STDERR`, or custom IO objects) via the `output:` parameter in `Command.call`.
20
+
21
+ ## v2.1.4
22
+
23
+ - `Command#to_s` now returns the class name by default, improving debugging and introspection.
data.tar.gz.sig CHANGED
Binary file
metadata CHANGED
@@ -1,11 +1,12 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: samovar
3
3
  version: !ruby/object:Gem::Version
4
- version: 2.4.0
4
+ version: 2.4.2
5
5
  platform: ruby
6
6
  authors:
7
7
  - Samuel Williams
8
8
  - Gabriel Mazetto
9
+ - Gerhard Schlager
9
10
  bindir: bin
10
11
  cert_chain:
11
12
  - |
@@ -53,24 +54,12 @@ dependencies:
53
54
  - - "~>"
54
55
  - !ruby/object:Gem::Version
55
56
  version: '1.0'
56
- - !ruby/object:Gem::Dependency
57
- name: mapping
58
- requirement: !ruby/object:Gem::Requirement
59
- requirements:
60
- - - "~>"
61
- - !ruby/object:Gem::Version
62
- version: '1.0'
63
- type: :runtime
64
- prerelease: false
65
- version_requirements: !ruby/object:Gem::Requirement
66
- requirements:
67
- - - "~>"
68
- - !ruby/object:Gem::Version
69
- version: '1.0'
70
57
  executables: []
71
58
  extensions: []
72
59
  extra_rdoc_files: []
73
60
  files:
61
+ - context/getting-started.md
62
+ - context/index.yaml
74
63
  - lib/samovar.rb
75
64
  - lib/samovar/command.rb
76
65
  - lib/samovar/error.rb
@@ -92,6 +81,7 @@ files:
92
81
  - lib/samovar/version.rb
93
82
  - license.md
94
83
  - readme.md
84
+ - releases.md
95
85
  homepage: https://github.com/ioquatix/samovar
96
86
  licenses:
97
87
  - MIT
@@ -106,14 +96,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
106
96
  requirements:
107
97
  - - ">="
108
98
  - !ruby/object:Gem::Version
109
- version: '3.2'
99
+ version: '3.3'
110
100
  required_rubygems_version: !ruby/object:Gem::Requirement
111
101
  requirements:
112
102
  - - ">="
113
103
  - !ruby/object:Gem::Version
114
104
  version: '0'
115
105
  requirements: []
116
- rubygems_version: 3.6.9
106
+ rubygems_version: 4.0.10
117
107
  specification_version: 4
118
108
  summary: Samovar is a flexible option parser excellent support for sub-commands and
119
109
  help documentation.
metadata.gz.sig CHANGED
Binary file