cuprum-cli 0.1.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 +7 -0
- data/CHANGELOG.md +34 -0
- data/CODE_OF_CONDUCT.md +10 -0
- data/LICENSE +21 -0
- data/README.md +163 -0
- data/lib/cuprum/cli/argument.rb +172 -0
- data/lib/cuprum/cli/arguments/class_methods.rb +283 -0
- data/lib/cuprum/cli/arguments.rb +16 -0
- data/lib/cuprum/cli/coercion.rb +131 -0
- data/lib/cuprum/cli/command.rb +102 -0
- data/lib/cuprum/cli/commands/ci/report.rb +121 -0
- data/lib/cuprum/cli/commands/ci/rspec_command.rb +108 -0
- data/lib/cuprum/cli/commands/ci/rspec_each_command.rb +185 -0
- data/lib/cuprum/cli/commands/ci.rb +12 -0
- data/lib/cuprum/cli/commands/echo_command.rb +76 -0
- data/lib/cuprum/cli/commands/file/generate_file.rb +141 -0
- data/lib/cuprum/cli/commands/file/new_command.rb +86 -0
- data/lib/cuprum/cli/commands/file/render_erb.rb +88 -0
- data/lib/cuprum/cli/commands/file/resolve_template.rb +136 -0
- data/lib/cuprum/cli/commands/file/templates/rspec.rb.erb +14 -0
- data/lib/cuprum/cli/commands/file/templates/ruby.rb.erb +29 -0
- data/lib/cuprum/cli/commands/file/templates.rb +71 -0
- data/lib/cuprum/cli/commands/file.rb +14 -0
- data/lib/cuprum/cli/commands.rb +12 -0
- data/lib/cuprum/cli/dependencies/file_system/mock.rb +297 -0
- data/lib/cuprum/cli/dependencies/file_system.rb +247 -0
- data/lib/cuprum/cli/dependencies/standard_io/helpers.rb +138 -0
- data/lib/cuprum/cli/dependencies/standard_io/mock.rb +85 -0
- data/lib/cuprum/cli/dependencies/standard_io.rb +110 -0
- data/lib/cuprum/cli/dependencies/system_command/mock.rb +57 -0
- data/lib/cuprum/cli/dependencies/system_command.rb +147 -0
- data/lib/cuprum/cli/dependencies.rb +25 -0
- data/lib/cuprum/cli/errors/files/file_not_writeable.rb +42 -0
- data/lib/cuprum/cli/errors/files/missing_parameter.rb +71 -0
- data/lib/cuprum/cli/errors/files/missing_template.rb +36 -0
- data/lib/cuprum/cli/errors/files/template_error.rb +37 -0
- data/lib/cuprum/cli/errors/files/template_not_resolved.rb +54 -0
- data/lib/cuprum/cli/errors/files.rb +19 -0
- data/lib/cuprum/cli/errors/system_command_failure.rb +44 -0
- data/lib/cuprum/cli/errors.rb +11 -0
- data/lib/cuprum/cli/integrations/thor/arguments_parser.rb +99 -0
- data/lib/cuprum/cli/integrations/thor/registry.rb +42 -0
- data/lib/cuprum/cli/integrations/thor/task.rb +211 -0
- data/lib/cuprum/cli/integrations/thor.rb +14 -0
- data/lib/cuprum/cli/integrations.rb +8 -0
- data/lib/cuprum/cli/metadata.rb +215 -0
- data/lib/cuprum/cli/option.rb +165 -0
- data/lib/cuprum/cli/options/class_methods.rb +232 -0
- data/lib/cuprum/cli/options/quiet.rb +32 -0
- data/lib/cuprum/cli/options/verbose.rb +32 -0
- data/lib/cuprum/cli/options.rb +18 -0
- data/lib/cuprum/cli/registry.rb +141 -0
- data/lib/cuprum/cli/rspec/deferred/arguments_examples.rb +203 -0
- data/lib/cuprum/cli/rspec/deferred/ci/report_examples.rb +450 -0
- data/lib/cuprum/cli/rspec/deferred/ci.rb +8 -0
- data/lib/cuprum/cli/rspec/deferred/dependencies/file_system_examples.rb +1469 -0
- data/lib/cuprum/cli/rspec/deferred/dependencies.rb +8 -0
- data/lib/cuprum/cli/rspec/deferred/metadata_examples.rb +856 -0
- data/lib/cuprum/cli/rspec/deferred/options_examples.rb +234 -0
- data/lib/cuprum/cli/rspec/deferred/registry_examples.rb +451 -0
- data/lib/cuprum/cli/rspec/deferred.rb +8 -0
- data/lib/cuprum/cli/rspec.rb +8 -0
- data/lib/cuprum/cli/version.rb +59 -0
- data/lib/cuprum/cli.rb +47 -0
- metadata +173 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: f0569f2b0f6fd69ffae0141d299421c0a42d91182e8d795151ab0d8994838cf5
|
|
4
|
+
data.tar.gz: d585735c4712bd25a0729c09fb8547e46b0e4a2222d3cf1476c178ea17d54bc1
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: b65e0e51ef3d04c4adb58998dc62e3a153e9e8cb2c44f21fd540798993bf13633e7079c07df56a03e30d0d0d5300f31b3eeacf3b2575ae4c88f513ecd60f5e1f
|
|
7
|
+
data.tar.gz: d0b99e59cc83645160bad0fec52432cee1d10b1f7efaf4f5771dff0c7a1df747dd2e9364ac27994ba73b17aaa6c74070711ff048e4185512a085a2d85346d6ee
|
data/CHANGELOG.md
ADDED
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.1.0
|
|
4
|
+
|
|
5
|
+
Initial version.
|
|
6
|
+
|
|
7
|
+
## Commands
|
|
8
|
+
|
|
9
|
+
Implemented `Cuprum::Cli::Command`
|
|
10
|
+
|
|
11
|
+
- Added support for positional arguments
|
|
12
|
+
- Added support for keyword options and flags
|
|
13
|
+
|
|
14
|
+
### Built-In Commands
|
|
15
|
+
|
|
16
|
+
- Implemented `Cuprum::Cli::Ci::RSpecCommand`
|
|
17
|
+
- Implemented `Cuprum::Cli::Ci::RSpecEachCommand`
|
|
18
|
+
- Implemented `Cuprum::Cli::File::NewCommand`
|
|
19
|
+
|
|
20
|
+
### Dependencies
|
|
21
|
+
|
|
22
|
+
- Implemented `Cuprum::Cli::Dependencies::FileSystem`
|
|
23
|
+
- Implemented `Cuprum::Cli::Dependencies::StandardIo`
|
|
24
|
+
- Implemented `Cuprum::Cli::Dependencies::SystemCommand`
|
|
25
|
+
|
|
26
|
+
### Registries
|
|
27
|
+
|
|
28
|
+
- Implemented `Cuprum::Cli::Registry`
|
|
29
|
+
|
|
30
|
+
## Integrations
|
|
31
|
+
|
|
32
|
+
Added `Thor` integration
|
|
33
|
+
|
|
34
|
+
- Implemented `Cuprum::Cli::Integrations::Thor::Registry`
|
data/CODE_OF_CONDUCT.md
ADDED
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
# Code of Conduct
|
|
2
|
+
|
|
3
|
+
"cuprum-cli" follows [The Ruby Community Conduct Guideline](https://www.ruby-lang.org/en/conduct) in all "collaborative space", which is defined as community communications channels (such as mailing lists, submitted patches, commit comments, etc.):
|
|
4
|
+
|
|
5
|
+
* Participants will be tolerant of opposing views.
|
|
6
|
+
* Participants must ensure that their language and actions are free of personal attacks and disparaging personal remarks.
|
|
7
|
+
* When interpreting the words and actions of others, participants should always assume good intentions.
|
|
8
|
+
* Behaviour which can be reasonably considered harassment will not be tolerated.
|
|
9
|
+
|
|
10
|
+
If you have any concerns about behaviour within this project, please contact us at ["sleepingkingstudios@gmail.com"](mailto:"sleepingkingstudios@gmail.com").
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
The MIT License (MIT)
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Rob Smith
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in
|
|
13
|
+
all copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
|
|
21
|
+
THE SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,163 @@
|
|
|
1
|
+
# Cuprum::Cli
|
|
2
|
+
|
|
3
|
+
A command-line utility powered by [Cuprum](https://www.sleepingkingstudios.com/cuprum/) that provides tools and utilities for defining command-line tools.
|
|
4
|
+
|
|
5
|
+
<blockquote>
|
|
6
|
+
Read The
|
|
7
|
+
<a href="https://www.sleepingkingstudios.com/cuprum-cli" target="_blank">
|
|
8
|
+
Documentation
|
|
9
|
+
</a>
|
|
10
|
+
</blockquote>
|
|
11
|
+
|
|
12
|
+
## About
|
|
13
|
+
|
|
14
|
+
### Documentation
|
|
15
|
+
|
|
16
|
+
Documentation is generated using [YARD](https://yardoc.org/), and can be generated locally using the `yard` gem.
|
|
17
|
+
|
|
18
|
+
### License
|
|
19
|
+
|
|
20
|
+
Copyright (c) 2026 Rob Smith
|
|
21
|
+
|
|
22
|
+
`Cuprum::Cli` is released under the [MIT License](https://opensource.org/licenses/MIT).
|
|
23
|
+
|
|
24
|
+
### Contribute
|
|
25
|
+
|
|
26
|
+
The canonical repository for this gem is located at https://github.com/sleepingkingstudios/cuprum-cli.
|
|
27
|
+
|
|
28
|
+
To report a bug or submit a feature request, please use the [Issue Tracker](https://github.com/sleepingkingstudios/cuprum-cli/issues).
|
|
29
|
+
|
|
30
|
+
To contribute code, please fork the repository, make the desired updates, and then provide a [Pull Request](https://github.com/sleepingkingstudios/cuprum-cli/pulls). Pull requests must include appropriate tests for consideration, and all code must be properly formatted.
|
|
31
|
+
|
|
32
|
+
### Code of Conduct
|
|
33
|
+
|
|
34
|
+
Please note that the `Cuprum::Cli` project is released with a [Contributor Code of Conduct](https://github.com/sleepingkingstudios/cuprum-cli/blob/master/CODE_OF_CONDUCT.md). By contributing to this project, you agree to abide by its terms.
|
|
35
|
+
|
|
36
|
+
### Local Development
|
|
37
|
+
|
|
38
|
+
The test suite for `Cuprum::Cli` is written using [RSpec](https://rspec.info/), with optional integration dependencies managed using [Appraisal](https://github.com/thoughtbot/appraisal).
|
|
39
|
+
|
|
40
|
+
To run the general test suite:
|
|
41
|
+
|
|
42
|
+
```bash
|
|
43
|
+
bundle exec rspec
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
To run the test suite including the `Thor` integration:
|
|
47
|
+
|
|
48
|
+
```bash
|
|
49
|
+
BUNDLER_GEMFILE=gemfiles/integrations_thor.gemfile INTEGRATION=thor bundle exec rspec
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
To run the [RuboCop](https://rubocop.org/) linter:
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
bundle exec rubocop
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
## Getting Started
|
|
59
|
+
|
|
60
|
+
Add the gem to your Gemfile or gemspec:
|
|
61
|
+
|
|
62
|
+
```ruby
|
|
63
|
+
group :development, :test do
|
|
64
|
+
gem 'cuprum-cli'
|
|
65
|
+
end
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
To ensure that dependent libraries are loaded, call the `Cuprum::Cli` initializer:
|
|
69
|
+
|
|
70
|
+
- In the initializer for your project:
|
|
71
|
+
|
|
72
|
+
```ruby
|
|
73
|
+
module Space
|
|
74
|
+
@initializer = SleepingKingStudios::Tools::Toolbox::Initializer.new do
|
|
75
|
+
Cuprum::Cli.initializer.call
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
- Or, in the entry points of your application (such as a `bin` script or `spec_helper.rb`).
|
|
81
|
+
|
|
82
|
+
Set up a <a href="https://www.sleepingkingstudios.com/cuprum-cli/integrations" target="_blank">CLI integration</a> and register your commands:
|
|
83
|
+
|
|
84
|
+
```ruby
|
|
85
|
+
# In tasks.thor:
|
|
86
|
+
require 'cuprum/cli/integrations/thor/registry'
|
|
87
|
+
|
|
88
|
+
Cuprum::Cli.initializer.call
|
|
89
|
+
|
|
90
|
+
registry = Cuprum::Cli::Integrations::Thor::Registry.new
|
|
91
|
+
|
|
92
|
+
registry.register Cuprum::Cli::Commands::Ci::RSpecCommand
|
|
93
|
+
registry.register Cuprum::Cli::Commands::Ci::RSpecCommand,
|
|
94
|
+
full_name: 'ci:rspec:sinatra4',
|
|
95
|
+
description: 'Runs the RSpec tests against Sinatra 4.X',
|
|
96
|
+
options: { gemfile: 'gemfiles/sinatra_4.gemfile' }
|
|
97
|
+
```
|
|
98
|
+
|
|
99
|
+
Finally, you can call the commands from your CLI tool:
|
|
100
|
+
|
|
101
|
+
```
|
|
102
|
+
% bundle exec thor list
|
|
103
|
+
ci
|
|
104
|
+
--
|
|
105
|
+
thor ci:rspec ...FILE_PATTERNS # Runs an RSpec command.
|
|
106
|
+
thor ci:rspec:sinatra4 ...FILE_PATTERNS # Runs the RSpec tests against Sinatra 4.X
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Defining Commands
|
|
110
|
+
|
|
111
|
+
You can also define custom CLI commands using the `Cuprum::Cli::Command` class. `Cuprum::Cli` defines a powerful DSL for quickly defining and configuring commands.
|
|
112
|
+
|
|
113
|
+
```ruby
|
|
114
|
+
class PingCommand < Cuprum::Cli::Command
|
|
115
|
+
dependency :system_command
|
|
116
|
+
|
|
117
|
+
argument :service_url,
|
|
118
|
+
default: 'www.example.com',
|
|
119
|
+
description: 'The URL of the remote service',
|
|
120
|
+
type: String
|
|
121
|
+
|
|
122
|
+
option :interval,
|
|
123
|
+
aliases: 'i',
|
|
124
|
+
default: 0.1,
|
|
125
|
+
description: 'The interval between pings',
|
|
126
|
+
type: Float
|
|
127
|
+
|
|
128
|
+
option :max_count,
|
|
129
|
+
aliases: %w[c],
|
|
130
|
+
default: 5,
|
|
131
|
+
description: 'The total number of pings sent to the server',
|
|
132
|
+
type: Integer
|
|
133
|
+
|
|
134
|
+
private
|
|
135
|
+
|
|
136
|
+
def format_options
|
|
137
|
+
# The ping command uses a non-standard options format.
|
|
138
|
+
options = +''
|
|
139
|
+
|
|
140
|
+
options << "-c#{max_count}"
|
|
141
|
+
options << "-i#{interval}"
|
|
142
|
+
options << '-q' # Only display the summary line.
|
|
143
|
+
end
|
|
144
|
+
|
|
145
|
+
def process
|
|
146
|
+
system_command.capture(
|
|
147
|
+
'ping',
|
|
148
|
+
arguments: [format_options, service_url]
|
|
149
|
+
)
|
|
150
|
+
end
|
|
151
|
+
end
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Now that we've defined a custom command, we can register it in our CLI integration:
|
|
155
|
+
|
|
156
|
+
```ruby
|
|
157
|
+
registry.register PingCommand
|
|
158
|
+
registry.register PingCommand,
|
|
159
|
+
full_name: 'ping:github',
|
|
160
|
+
options: { service_url: 'github.com' }
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
For more information on defining commands, see the <a href="https://www.sleepingkingstudios.com/cuprum-cli/commands" target="_blank">commands documentation</a>.
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'cuprum/cli'
|
|
4
|
+
require 'cuprum/cli/arguments'
|
|
5
|
+
|
|
6
|
+
module Cuprum::Cli # rubocop:disable Metrics/ModuleLength
|
|
7
|
+
# Data object representing a positional command argument.
|
|
8
|
+
Argument = Data.define(
|
|
9
|
+
:default,
|
|
10
|
+
:description,
|
|
11
|
+
:name,
|
|
12
|
+
:parameter_name,
|
|
13
|
+
:required,
|
|
14
|
+
:type,
|
|
15
|
+
:variadic
|
|
16
|
+
) do
|
|
17
|
+
# @param name [String, Symbol] the name of the argument.
|
|
18
|
+
# @param default [Object, Proc] the default value for the argument. If given
|
|
19
|
+
# and the value of the argument is nil, sets the argument value to the
|
|
20
|
+
# default value.
|
|
21
|
+
# @param description [String] a short, human-readable description of the
|
|
22
|
+
# argument.
|
|
23
|
+
# @param parameter_name [String] a representation of the possible values for
|
|
24
|
+
# the argument.
|
|
25
|
+
# @param required [true, false] if true, raises an exception if the argument
|
|
26
|
+
# is not provided to the command.
|
|
27
|
+
# @param type [Class, String, Symbol] the expected type of the argument
|
|
28
|
+
# value as a Class or class name. If given, raises an exception if the
|
|
29
|
+
# argument value is not an instance of the type. Defaults to :string.
|
|
30
|
+
# @param variadic [true, false] if true, the argument is variadic and
|
|
31
|
+
# represents an array of arguments provided to the command. Defaults to
|
|
32
|
+
# false.
|
|
33
|
+
def initialize( # rubocop:disable Metrics/MethodLength, Metrics/ParameterLists
|
|
34
|
+
name:,
|
|
35
|
+
default: nil,
|
|
36
|
+
description: nil,
|
|
37
|
+
parameter_name: nil,
|
|
38
|
+
required: false,
|
|
39
|
+
type: :string,
|
|
40
|
+
variadic: false
|
|
41
|
+
)
|
|
42
|
+
name = name.to_sym
|
|
43
|
+
required = required ? true : false
|
|
44
|
+
type = type.to_sym if type.is_a?(String)
|
|
45
|
+
variadic = variadic ? true : false
|
|
46
|
+
|
|
47
|
+
super(
|
|
48
|
+
default:,
|
|
49
|
+
description:,
|
|
50
|
+
name:,
|
|
51
|
+
parameter_name:,
|
|
52
|
+
required:,
|
|
53
|
+
type:,
|
|
54
|
+
variadic:
|
|
55
|
+
)
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
alias_method :required?, :required
|
|
59
|
+
|
|
60
|
+
alias_method :variadic?, :variadic
|
|
61
|
+
|
|
62
|
+
# @overload def resolve(value)
|
|
63
|
+
# Validates the value for the current argument.
|
|
64
|
+
#
|
|
65
|
+
# If the value is nil, applies the argument default (if any).
|
|
66
|
+
#
|
|
67
|
+
# @param value [Object] the value to validate.
|
|
68
|
+
#
|
|
69
|
+
# @return [Object] the validated argument value.
|
|
70
|
+
#
|
|
71
|
+
# @raise [Cuprum::Cli::Arguments::InvalidArgumentError] if the value is
|
|
72
|
+
# missing (for a required argument) or invalid.
|
|
73
|
+
def resolve(original_value) # rubocop:disable Metrics/CyclomaticComplexity
|
|
74
|
+
return resolve_variadic(original_value) if variadic?
|
|
75
|
+
|
|
76
|
+
value = original_value
|
|
77
|
+
value = default_value if blank?(value)
|
|
78
|
+
value = value.to_s if value.is_a?(Symbol)
|
|
79
|
+
|
|
80
|
+
return (type == :boolean ? false : nil) if value.nil? && !required?
|
|
81
|
+
|
|
82
|
+
return value if valid_argument?(value)
|
|
83
|
+
|
|
84
|
+
raise Cuprum::Cli::Arguments::InvalidArgumentError,
|
|
85
|
+
invalid_argument_message(original_value)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
private
|
|
89
|
+
|
|
90
|
+
def blank?(value)
|
|
91
|
+
value.nil? || (value.respond_to?(:empty?) && value.empty?)
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def default_value
|
|
95
|
+
default.is_a?(Proc) ? default.call : default
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def expected_array_type # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
99
|
+
message = required? ? 'a non-empty Array of' : 'an Array of'
|
|
100
|
+
|
|
101
|
+
case type
|
|
102
|
+
when :boolean
|
|
103
|
+
"#{message} true or false"
|
|
104
|
+
when Class
|
|
105
|
+
name = tools.string_tools.pluralize(type.name)
|
|
106
|
+
|
|
107
|
+
"#{message} #{tools.string_tools.camelize(name)}"
|
|
108
|
+
else
|
|
109
|
+
name = tools.string_tools.pluralize(type.to_s)
|
|
110
|
+
|
|
111
|
+
"#{message} #{tools.string_tools.camelize(name)}"
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def expected_type
|
|
116
|
+
case type
|
|
117
|
+
when :boolean
|
|
118
|
+
'true or false'
|
|
119
|
+
when Class
|
|
120
|
+
"an instance of #{type.name}"
|
|
121
|
+
else
|
|
122
|
+
"an instance of #{tools.string_tools.camelize(type.to_s)}"
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
def invalid_argument_message(value)
|
|
127
|
+
"invalid value for argument :#{name} - expected #{expected_type}, " \
|
|
128
|
+
"received #{value.inspect}"
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def invalid_variadic_argument_message(value)
|
|
132
|
+
"invalid value for variadic argument :#{name} - expected " \
|
|
133
|
+
"#{expected_array_type}, received #{value.inspect}"
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def resolve_variadic(original_value)
|
|
137
|
+
value = original_value
|
|
138
|
+
value = default_value if blank?(value)
|
|
139
|
+
|
|
140
|
+
return [] if value.nil? && !required?
|
|
141
|
+
|
|
142
|
+
return value if valid_arguments?(value)
|
|
143
|
+
|
|
144
|
+
raise Cuprum::Cli::Arguments::InvalidArgumentError,
|
|
145
|
+
invalid_variadic_argument_message(original_value)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def tools
|
|
149
|
+
SleepingKingStudios::Tools::Toolbelt.instance
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
def valid_argument?(value)
|
|
153
|
+
case type
|
|
154
|
+
when :boolean
|
|
155
|
+
value == true || value == false # rubocop:disable Style/MultipleComparison
|
|
156
|
+
when Class
|
|
157
|
+
value.is_a?(type)
|
|
158
|
+
else
|
|
159
|
+
expected = tools.string_tools.camelize(type.to_s)
|
|
160
|
+
expected = Object.const_get(expected)
|
|
161
|
+
|
|
162
|
+
value.is_a?(expected)
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
def valid_arguments?(value)
|
|
167
|
+
return false unless value.is_a?(Array)
|
|
168
|
+
|
|
169
|
+
value.all? { |item| valid_argument?(item) }
|
|
170
|
+
end
|
|
171
|
+
end
|
|
172
|
+
end
|
|
@@ -0,0 +1,283 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'weakref'
|
|
4
|
+
|
|
5
|
+
require 'cuprum/cli/arguments'
|
|
6
|
+
|
|
7
|
+
module Cuprum::Cli::Arguments
|
|
8
|
+
# Methods used to extend command class functionality for defining arguments.
|
|
9
|
+
module ClassMethods
|
|
10
|
+
# Helper class for defining command arguments.
|
|
11
|
+
class Builder
|
|
12
|
+
# @param command_class [Class] the command class.
|
|
13
|
+
# @param defined_arguments [Array<Cuprum::Cli::Argument>] the arguments
|
|
14
|
+
# defined for the command.
|
|
15
|
+
def initialize(command_class:, defined_arguments:)
|
|
16
|
+
@command_class = command_class
|
|
17
|
+
@defined_arguments = defined_arguments
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# @return [Class] the command class.
|
|
21
|
+
attr_reader :command_class
|
|
22
|
+
|
|
23
|
+
# @return [Array<Cuprum::Cli::Argument>] the arguments defined for the
|
|
24
|
+
# command.
|
|
25
|
+
attr_reader :defined_arguments
|
|
26
|
+
|
|
27
|
+
# (see Cuprum::Cli::Arguments::ClassMethods#argument)
|
|
28
|
+
def call(name, define_method: nil, define_predicate: nil, **options)
|
|
29
|
+
argument = Cuprum::Cli::Argument.new(name:, **options)
|
|
30
|
+
|
|
31
|
+
defined_arguments << argument
|
|
32
|
+
|
|
33
|
+
define_method = (options[:type] != :boolean) if define_method.nil?
|
|
34
|
+
define_predicate = (options[:type] == :boolean) if define_predicate.nil?
|
|
35
|
+
|
|
36
|
+
define_method_for(argument) if define_method
|
|
37
|
+
define_predicate_for(argument) if define_predicate
|
|
38
|
+
|
|
39
|
+
argument.name
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def define_method_for(argument)
|
|
45
|
+
command_class.define_method(argument.name) { @arguments[argument.name] }
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def define_predicate_for(argument)
|
|
49
|
+
command_class.define_method(:"#{argument.name}?") do
|
|
50
|
+
value = @arguments[argument.name]
|
|
51
|
+
|
|
52
|
+
return false if value.nil? || value == false
|
|
53
|
+
|
|
54
|
+
!(value.respond_to?(:empty?) && value.empty?)
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# @private
|
|
60
|
+
class VariadicArgumentsResolver
|
|
61
|
+
def initialize(defined_arguments)
|
|
62
|
+
@defined_arguments = defined_arguments
|
|
63
|
+
@variadic_index = defined_arguments.index(&:variadic?)
|
|
64
|
+
@variadic_argument = defined_arguments[variadic_index]
|
|
65
|
+
@before_arguments = defined_arguments[...variadic_index]
|
|
66
|
+
@after_arguments = defined_arguments[(1 + variadic_index)..]
|
|
67
|
+
@resolved = {}
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# @private
|
|
71
|
+
def call(*values)
|
|
72
|
+
values = resolve_before(values)
|
|
73
|
+
values = resolve_after(values)
|
|
74
|
+
|
|
75
|
+
resolved[variadic_argument.name] = values
|
|
76
|
+
|
|
77
|
+
resolved
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
attr_reader :after_arguments
|
|
83
|
+
|
|
84
|
+
attr_reader :before_arguments
|
|
85
|
+
|
|
86
|
+
attr_reader :resolved
|
|
87
|
+
|
|
88
|
+
attr_reader :variadic_argument
|
|
89
|
+
|
|
90
|
+
attr_reader :variadic_index
|
|
91
|
+
|
|
92
|
+
def resolve_after(values) # rubocop:disable Metrics/AbcSize
|
|
93
|
+
extra_count = values.count - after_arguments.count
|
|
94
|
+
|
|
95
|
+
if extra_count.negative?
|
|
96
|
+
values.concat(Array.new(-extra_count))
|
|
97
|
+
|
|
98
|
+
extra_count = 0
|
|
99
|
+
end
|
|
100
|
+
|
|
101
|
+
after_arguments&.each&.with_index(extra_count) do |argument, index|
|
|
102
|
+
resolved[argument.name] = argument.resolve(values[index])
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
values[...extra_count]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def resolve_before(values)
|
|
109
|
+
before_arguments&.each&.with_index do |argument, index|
|
|
110
|
+
resolved[argument.name] = argument.resolve(values[index])
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
values[before_arguments.count...] || []
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
private_constant :VariadicArgumentsResolver
|
|
117
|
+
|
|
118
|
+
# @overload argument(name, default: nil, description: nil, required: false, type: :string, variadic: false, **options)
|
|
119
|
+
# Defines an argument for the command class.
|
|
120
|
+
#
|
|
121
|
+
# @param name [String, Symbol] the name of the argument.
|
|
122
|
+
# @param default [Object, Proc] the default value for the argument. If
|
|
123
|
+
# given and the value of the argument is nil, sets the argument value to
|
|
124
|
+
# the default value.
|
|
125
|
+
# @param description [String] a short, human-readable description of the
|
|
126
|
+
# argument.
|
|
127
|
+
# @param required [true, false] if true, raises an exception if the
|
|
128
|
+
# argument is not provided to the command. Defaults to false.
|
|
129
|
+
# @param type [Class, String, Symbol] the expected type of the argument
|
|
130
|
+
# value as a Class or class name. If given, raises an exception if the
|
|
131
|
+
# argument value is not an instance of the type. Defaults to :string.
|
|
132
|
+
# @param variadic [true, false] if true, the argument is variadic and
|
|
133
|
+
# represents an array of arguments provided to the command. Defaults to
|
|
134
|
+
# false.
|
|
135
|
+
# @param options [Hash] additional options for defining the argument.
|
|
136
|
+
#
|
|
137
|
+
# @option options define_method [true, false] if true, defines a reader
|
|
138
|
+
# method for the argument. Defaults to false for boolean arguments and
|
|
139
|
+
# true for all other arguments.
|
|
140
|
+
# @option options define_predicate [true, false] if true, defines a
|
|
141
|
+
# predicate method for the argument, which returns true if the argument
|
|
142
|
+
# is not nil and not empty. Defaults to true for boolean arguments and
|
|
143
|
+
# false for all other arguments.
|
|
144
|
+
#
|
|
145
|
+
# @raise [ArgumentError] if variadic is true and the command already
|
|
146
|
+
# defines a variadic argument.
|
|
147
|
+
def argument(name, define_method: nil, define_predicate: nil, **)
|
|
148
|
+
handle_multiple_variadic_arguments(**)
|
|
149
|
+
|
|
150
|
+
arguments_builder.call(name, define_method:, define_predicate:, **)
|
|
151
|
+
end
|
|
152
|
+
|
|
153
|
+
# Appends a predefined argument value for the command.
|
|
154
|
+
#
|
|
155
|
+
# @param value [Object] the argument value to append.
|
|
156
|
+
#
|
|
157
|
+
# @return [void]
|
|
158
|
+
def argument_value(value)
|
|
159
|
+
defined_argument_values << value
|
|
160
|
+
|
|
161
|
+
nil
|
|
162
|
+
end
|
|
163
|
+
|
|
164
|
+
# @return [Array<Object>] predefined argument values for the command.
|
|
165
|
+
def argument_values = defined_argument_values
|
|
166
|
+
|
|
167
|
+
# @overload arguments()
|
|
168
|
+
# The defined arguments for the command class.
|
|
169
|
+
#
|
|
170
|
+
# @return [Array<Cuprum::Cli::Argument>] the defined arguments.
|
|
171
|
+
#
|
|
172
|
+
# @overload arguments(name, default: nil, description: nil, required: false, type: :string, **options)
|
|
173
|
+
# Defines a variadic argument for the command class.
|
|
174
|
+
#
|
|
175
|
+
# @param name [String, Symbol] the name of the argument.
|
|
176
|
+
# @param default [Object, Proc] the default value for the argument. If
|
|
177
|
+
# given and the value of the argument is nil, sets the argument value to
|
|
178
|
+
# the default value.
|
|
179
|
+
# @param description [String] a short, human-readable description of the
|
|
180
|
+
# argument.
|
|
181
|
+
# @param required [true, false] if true, raises an exception if the
|
|
182
|
+
# argument is not provided to the command.
|
|
183
|
+
# @param type [Class, String, Symbol] the expected type of the argument
|
|
184
|
+
# value as a Class or class name. If given, raises an exception if the
|
|
185
|
+
# argument value is not an instance of the type. Defaults to :string.
|
|
186
|
+
# @param options [Hash] additional options for defining the argument.
|
|
187
|
+
#
|
|
188
|
+
# @option options define_method [true, false] if true, defines a reader
|
|
189
|
+
# method for the argument. Defaults to false for boolean arguments and
|
|
190
|
+
# true for all other arguments.
|
|
191
|
+
# @option options define_predicate [true, false] if true, defines a
|
|
192
|
+
# predicate method for the argument, which returns true if the argument
|
|
193
|
+
# is not nil and not empty. Defaults to true for boolean arguments and
|
|
194
|
+
# false for all other arguments.
|
|
195
|
+
def arguments(name = nil, **)
|
|
196
|
+
if name.nil?
|
|
197
|
+
return defined_arguments unless defined_arguments.empty?
|
|
198
|
+
|
|
199
|
+
return superclass.arguments if superclass.respond_to?(:arguments)
|
|
200
|
+
|
|
201
|
+
return []
|
|
202
|
+
end
|
|
203
|
+
|
|
204
|
+
argument(name, **, variadic: true)
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
# Validates the given argument values against the defined class arguments.
|
|
208
|
+
#
|
|
209
|
+
# Also applies any default values from the defined arguments.
|
|
210
|
+
#
|
|
211
|
+
# @param values [Array] the arguments values to resolve.
|
|
212
|
+
#
|
|
213
|
+
# @return [Array] the arguments values with applied defaults.
|
|
214
|
+
#
|
|
215
|
+
# @raise [Cuprum::Cli::Arguments::ExtraArgumentsError] if provided more
|
|
216
|
+
# arguments than the command class defines arguments.
|
|
217
|
+
# @raise [Cuprum::Cli::Arguments::InvalidArgumentError] if any value does
|
|
218
|
+
# not match the expected argument type, or any required value is missing.
|
|
219
|
+
def resolve_arguments(*values) # rubocop:disable Metrics/AbcSize, Metrics/MethodLength
|
|
220
|
+
values = argument_values + values
|
|
221
|
+
defined_arguments = arguments
|
|
222
|
+
|
|
223
|
+
if defined_arguments.any?(&:variadic?)
|
|
224
|
+
return VariadicArgumentsResolver.new(defined_arguments).call(*values)
|
|
225
|
+
end
|
|
226
|
+
|
|
227
|
+
if values.size > defined_arguments.size
|
|
228
|
+
raise Cuprum::Cli::Arguments::ExtraArgumentsError,
|
|
229
|
+
extra_arguments_message(values.size)
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
defined_arguments.each.with_index.to_h do |argument, index|
|
|
233
|
+
[argument.name, argument.resolve(values[index])]
|
|
234
|
+
end
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
private
|
|
238
|
+
|
|
239
|
+
def arguments_builder
|
|
240
|
+
(
|
|
241
|
+
@arguments_builder ||= WeakRef.new(
|
|
242
|
+
Builder.new(command_class: self, defined_arguments:)
|
|
243
|
+
)
|
|
244
|
+
).__getobj__
|
|
245
|
+
rescue RefError
|
|
246
|
+
# :nocov:
|
|
247
|
+
@arguments_builder = WeakRef.new(
|
|
248
|
+
Builder.new(command_class: self, defined_arguments:)
|
|
249
|
+
)
|
|
250
|
+
@arguments_builder.__getobj__
|
|
251
|
+
# :nocov:
|
|
252
|
+
end
|
|
253
|
+
|
|
254
|
+
def defined_argument_values
|
|
255
|
+
@defined_argument_values ||= []
|
|
256
|
+
end
|
|
257
|
+
|
|
258
|
+
def defined_arguments
|
|
259
|
+
@defined_arguments ||= []
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def extra_arguments_message(count)
|
|
263
|
+
total_count = defined_arguments.size
|
|
264
|
+
last_required = (arguments.rindex(&:required?) || -1) + 1
|
|
265
|
+
expected =
|
|
266
|
+
last_required == total_count ? total_count : last_required..total_count
|
|
267
|
+
|
|
268
|
+
"wrong number of arguments (given #{count}, expected #{expected})"
|
|
269
|
+
end
|
|
270
|
+
|
|
271
|
+
def handle_multiple_variadic_arguments(variadic: false, **)
|
|
272
|
+
return unless variadic
|
|
273
|
+
|
|
274
|
+
matching = defined_arguments.find(&:variadic?)
|
|
275
|
+
|
|
276
|
+
return unless matching
|
|
277
|
+
|
|
278
|
+
message = "command already defines variadic argument :#{matching.name}"
|
|
279
|
+
|
|
280
|
+
raise ArgumentError, message
|
|
281
|
+
end
|
|
282
|
+
end
|
|
283
|
+
end
|