dry-cli 1.0.0 → 1.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.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1d37c32bb092f046364d8537259aec4a8f3e233fe57167b8d19c667f8c3421a7
4
- data.tar.gz: dcab3ac00499da55cad199c0ca98854e16109f23d0d257ffa9a0c826c765e476
3
+ metadata.gz: 87a109a8a8d9f3045e2cffa05a43fe8e31dd75fdd148cc6e7ea2610e2185e351
4
+ data.tar.gz: 7e5cf4b8a263cfe22c7be02cfa2faeca8ccd946933fb7c4e6d3c446341946324
5
5
  SHA512:
6
- metadata.gz: 1e29fb0c9aa6672108b1c4afc8629ab9274655675c95a04cf85503824c1565dcef22fe2da20f6ca8a6a65d8b4b07e626f3c793fbede40a10f7a645cfaf1f7421
7
- data.tar.gz: 8d65f61919776a08c9d6519cf2e6d47c85ec5c4e723234dcee2159b5a3fa6c99d2ecb754299dcd74f9f767e3c0d9e710f099ed16b347571256af082967d8e5cb
6
+ metadata.gz: b6eb3230d06cde1fe957bcff4c964f755735dff96c30ac1d8c4d39c2532d92b43e749363d9475eb09632da5ef0dd5608b7d354f6011c10189f9bd791d519f848
7
+ data.tar.gz: c5a9c59751712f8306daafd35e3bb5759a8ac0457b088c6efd7ff45f607c2467b3faeaf744af8e3cb96eed44216427e27fcd265195329b878b8e41852ceb494c
data/CHANGELOG.md CHANGED
@@ -1,16 +1,110 @@
1
- <!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
1
+ # Changelog
2
2
 
3
- ## 1.0.0 2022-11-05
3
+ All notable changes to this project will be documented in this file.
4
4
 
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
+ and this project adheres to [Break Versioning](https://www.taoensso.com/break-versioning).
7
+
8
+ ## [Unreleased]
9
+
10
+ ### Added
5
11
 
6
12
  ### Changed
7
13
 
8
- - Version bumped to 1.0.0 (@solnic)
14
+ ### Deprecated
15
+
16
+ ### Removed
17
+
18
+ ### Fixed
19
+
20
+ ### Security
21
+
22
+ [Unreleased]: https://github.com/dry-rb/dry-cli/compare/v1.4.1...main
23
+
24
+ ## [1.4.1] - 2026-01-21
25
+
26
+ ### Fixed
27
+
28
+ - Remove spurious leading blank line when printing usage output. (@aaronmallen in #154)
29
+
30
+ [1.4.1]: https://github.com/dry-rb/dry-cli/compare/v1.4.0...v1.4.1
31
+
32
+ ## [1.4.0] - 2026-01-09
33
+
34
+ ### Added
35
+
36
+ - Provide the CLI’s `out` and `err` streams to command instances (unless `@out` and `@err` ivars already exist in the command). (@aaronmallen in #150)
37
+
38
+ ### Changed
39
+
40
+ - Require Ruby 3.2 or later. (@timriley)
41
+
42
+ ### Fixed
43
+
44
+ - Pass a commands keyword arguments to any related callbacks. (@gustavothecoder in #136)
45
+ - Avoid duplicated option names in `--help` output when a subclass re-defines an option. (@gustavothecoder in #143)
46
+ - Properly raise an error when an invalid value is passed to an option (previously this was working for arguments only, not options). (@gustavothecoder in #142)
47
+
48
+ [1.4.0]: https://github.com/dry-rb/dry-cli/compare/v1.3.0...v1.4.0
49
+
50
+ ## [1.3.0] - 2025-07-29
51
+
52
+ ### Added
53
+
54
+ - Support unlimited nesting when registering commands via `register` with blocks. (@aaronmallen in #149)
55
+
56
+ You could previously do this only with fully qualified registrations:
57
+
58
+ ```ruby
59
+ Commands.register "nested one", MyFirstNestedCommand
60
+ Commands.register "nested one two", MySecondNestedCommand
61
+ Commands.register "nested one two three", MyThirdNestedCommand
62
+ ```
63
+
64
+ Now you can do the same via blocks:
65
+
66
+ ```ruby
67
+ Commands.register "nested" do
68
+ register "one", MyFirstNestedCommand do
69
+ register "two", MySecondNestedCommand do
70
+ register "three", MyThirdNestedCommand
71
+ end
72
+ end
73
+ end
74
+ ```
75
+
76
+ ### Changed
77
+
78
+ - Set minimum Ruby version to 3.1. (@timriley)
79
+
80
+ [1.3.0]: https://github.com/dry-rb/dry-cli/compare/v1.2.0...v1.3.0
81
+
82
+ ## [1.2.0] - 2024-10-15
83
+
84
+ ### Added
85
+
86
+ - Added `:hidden` option to register commands that should not be shown in the help output. (@benoittgt in #137)
87
+ - Provide suggestions when there is a typo in a command name. (@benoittgt in #138)
88
+
89
+ [1.2.0]: https://github.com/dry-rb/dry-cli/compare/v1.1.0...v1.2.0
90
+
91
+ ## [1.1.0] - 2024-07-14
9
92
 
10
- [Compare v0.7.0...v1.0.0](https://github.com/dry-rb/dry-cli/compare/v0.7.0...v1.0.0)
93
+ ### Added
94
+
95
+ - Added `:flag` option type. This acts like a `:boolean` that can only be set to true, so has no `--no-` prefix to disable it. (@Billiam in #117)
96
+
97
+ [1.1.0]: https://github.com/dry-rb/dry-cli/compare/v1.0.0...v1.1.0
11
98
 
12
- ## 0.7.0 2020-05-08
99
+ ## [1.0.0] - 2022-11-05
13
100
 
101
+ ### Changed
102
+
103
+ - Version bumped to 1.0.0 (@solnic)
104
+
105
+ [1.0.0]: https://github.com/dry-rb/dry-cli/compare/v0.7.0...v1.0.0
106
+
107
+ ## [0.7.0] - 2020-05-08
14
108
 
15
109
  ### Added
16
110
 
@@ -34,10 +128,9 @@
34
128
  - Remove concurrent-ruby as runtime dependency (@jodosha)
35
129
  - [Internal] Banner and Parses refactoring (@IvanShamatov)
36
130
 
37
- [Compare v0.6.0...v0.7.0](https://github.com/dry-rb/dry-cli/compare/v0.6.0...v0.7.0)
38
-
39
- ## 0.6.0 2020-03-06
131
+ [0.7.0]: https://github.com/dry-rb/dry-cli/compare/v0.6.0...v0.7.0
40
132
 
133
+ ## [0.6.0] - 2020-03-06
41
134
 
42
135
  ### Added
43
136
 
@@ -51,11 +144,9 @@
51
144
  - [John Ledbetter & Luca Guidi] Fix ruby 2.7 warnings (@jodosha)
52
145
  - Fix banner, when option is a type of Array (@IvanShamatov)
53
146
 
147
+ [0.6.0]: https://github.com/dry-rb/dry-cli/compare/v0.5.1...v0.6.0
54
148
 
55
- [Compare v0.5.1...v0.6.0](https://github.com/dry-rb/dry-cli/compare/v0.5.1...v0.6.0)
56
-
57
- ## 0.5.1 2020-01-23
58
-
149
+ ## [0.5.1] - 2020-01-23
59
150
 
60
151
  ### Added
61
152
 
@@ -69,48 +160,38 @@
69
160
 
70
161
  - Added missing 'set' require (@solnic)
71
162
 
163
+ [0.5.1]: https://github.com/dry-rb/dry-cli/compare/v0.5.0...v0.5.1
72
164
 
73
- [Compare v0.5.0...v0.5.1](https://github.com/dry-rb/dry-cli/compare/v0.5.0...v0.5.1)
74
-
75
- ## 0.5.0 2019-12-21
76
-
165
+ ## [0.5.0] - 2019-12-21
77
166
 
78
167
  ### Added
79
168
 
80
169
  - [Internal] removed runtime and development dependency against `hanami-utils` (@jodosha, @IvanShamatov, @solnic)
81
170
 
171
+ [0.5.0]: https://github.com/dry-rb/dry-cli/compare/v0.4.0...v0.5.0
82
172
 
83
- [Compare v0.4.0...v0.5.0](https://github.com/dry-rb/dry-cli/compare/v0.4.0...v0.5.0)
84
-
85
- ## 0.4.0 2019-12-10
86
-
173
+ ## [0.4.0] - 2019-12-10
87
174
 
88
175
  ### Added
89
176
 
90
177
  - `hanami-cli` => `dry-cli` (@jodosha, @IvanShamatov, @solnic)
91
178
 
179
+ [0.4.0]: https://github.com/dry-rb/dry-cli/compare/v0.3.1...v0.4.0
92
180
 
93
- [Compare v0.3.1...v0.4.0](https://github.com/dry-rb/dry-cli/compare/v0.3.1...v0.4.0)
94
-
95
- ## 0.3.1 2019-01-18
96
-
181
+ ## [0.3.1] - 2019-01-18
97
182
 
98
183
  ### Added
99
184
 
100
185
  - Official support for Ruby: MRI 2.6 (@jodosha)
101
186
  - Support `bundler` 2.0+ (@jodosha)
102
187
 
188
+ [0.3.1]: https://github.com/dry-rb/dry-cli/compare/v0.3.0...v0.3.1
103
189
 
104
- [Compare v0.3.0...v0.3.1](https://github.com/dry-rb/dry-cli/compare/v0.3.0...v0.3.1)
190
+ ## [0.3.0] - 2018-10-24
105
191
 
106
- ## 0.3.0 2018-10-24
107
-
108
-
109
-
110
- [Compare v0.3.0.beta1...v0.3.0](https://github.com/dry-rb/dry-cli/compare/v0.3.0.beta1...v0.3.0)
111
-
112
- ## 0.3.0.beta1 2018-08-08
192
+ [0.3.0]: https://github.com/dry-rb/dry-cli/compare/v0.3.0.beta1...v0.3.0
113
193
 
194
+ ## [0.3.0.beta1] - 2018-08-08
114
195
 
115
196
  ### Added
116
197
 
@@ -123,29 +204,21 @@
123
204
 
124
205
  - Print informative message when unknown or wrong option is passed (`"test" was called with arguments "--framework=unknown"`) (@davydovanton)
125
206
 
207
+ [0.3.0.beta1]: https://github.com/dry-rb/dry-cli/compare/v0.2.0...v0.3.0.beta1
126
208
 
127
- [Compare v0.2.0...v0.3.0.beta1](https://github.com/dry-rb/dry-cli/compare/v0.2.0...v0.3.0.beta1)
128
-
129
- ## 0.2.0 2018-04-11
209
+ ## [0.2.0] - 2018-04-11
130
210
 
211
+ [0.2.0]: https://github.com/dry-rb/dry-cli/compare/v0.2.0.rc2...v0.2.0
131
212
 
213
+ ## [0.2.0.rc2] - 2018-04-06
132
214
 
133
- [Compare v0.2.0.rc2...v0.2.0](https://github.com/dry-rb/dry-cli/compare/v0.2.0.rc2...v0.2.0)
215
+ [0.2.0.rc2]: https://github.com/dry-rb/dry-cli/compare/v0.2.0.rc1...v0.2.0.rc2
134
216
 
135
- ## 0.2.0.rc2 2018-04-06
217
+ ## [0.2.0.rc1] - 2018-03-30
136
218
 
219
+ [0.2.0.rc1]: https://github.com/dry-rb/dry-cli/compare/v0.2.0.beta2...v0.2.0.rc1
137
220
 
138
-
139
- [Compare v0.2.0.rc1...v0.2.0.rc2](https://github.com/dry-rb/dry-cli/compare/v0.2.0.rc1...v0.2.0.rc2)
140
-
141
- ## 0.2.0.rc1 2018-03-30
142
-
143
-
144
-
145
- [Compare v0.2.0.beta2...v0.2.0.rc1](https://github.com/dry-rb/dry-cli/compare/v0.2.0.beta2...v0.2.0.rc1)
146
-
147
- ## 0.2.0.beta2 2018-03-23
148
-
221
+ ## [0.2.0.beta2] - 2018-03-23
149
222
 
150
223
  ### Added
151
224
 
@@ -155,21 +228,17 @@
155
228
 
156
229
  - Ensure callbacks' context of execution (aka `self`) to be the command that is being executed (@jodosha, @davydovanton)
157
230
 
231
+ [0.2.0.beta2]: https://github.com/dry-rb/dry-cli/compare/v0.2.0.beta1...v0.2.0.beta2
158
232
 
159
- [Compare v0.2.0.beta1...v0.2.0.beta2](https://github.com/dry-rb/dry-cli/compare/v0.2.0.beta1...v0.2.0.beta2)
160
-
161
- ## 0.2.0.beta1 2018-02-28
162
-
233
+ ## [0.2.0.beta1] - 2018-02-28
163
234
 
164
235
  ### Added
165
236
 
166
237
  - Register `before`/`after` callbacks for commands (@davydovanton)
167
238
 
239
+ [0.2.0.beta1]: https://github.com/dry-rb/dry-cli/compare/v0.1.1...v0.2.0.beta1
168
240
 
169
- [Compare v0.1.1...v0.2.0.beta1](https://github.com/dry-rb/dry-cli/compare/v0.1.1...v0.2.0.beta1)
170
-
171
- ## 0.1.1 2018-02-27
172
-
241
+ ## [0.1.1] - 2018-02-27
173
242
 
174
243
  ### Added
175
244
 
@@ -180,39 +249,29 @@
180
249
  - Ensure default values for arguments to be sent to commands (@AlfonsoUceda)
181
250
  - Ensure to fail when a missing required argument isn't provider, but an option is provided instead (@AlfonsoUceda)
182
251
 
252
+ [0.1.1]: https://github.com/dry-rb/dry-cli/compare/v0.1.0...v0.1.1
183
253
 
184
- [Compare v0.1.0...v0.1.1](https://github.com/dry-rb/dry-cli/compare/v0.1.0...v0.1.1)
254
+ ## [0.1.0] - 2017-10-25
185
255
 
186
- ## 0.1.0 2017-10-25
256
+ [0.1.0]: https://github.com/dry-rb/dry-cli/compare/v0.1.0.rc1...v0.1.0
187
257
 
258
+ ## [0.1.0.rc1] - 2017-10-16
188
259
 
260
+ [0.1.0.rc1]: https://github.com/dry-rb/dry-cli/compare/v0.1.0.beta3...v0.1.0.rc1
189
261
 
190
- [Compare v0.1.0.rc1...v0.1.0](https://github.com/dry-rb/dry-cli/compare/v0.1.0.rc1...v0.1.0)
262
+ ## [0.1.0.beta3] - 2017-10-04
191
263
 
192
- ## 0.1.0.rc1 2017-10-16
193
-
194
-
195
-
196
- [Compare v0.1.0.beta3...v0.1.0.rc1](https://github.com/dry-rb/dry-cli/compare/v0.1.0.beta3...v0.1.0.rc1)
197
-
198
- ## 0.1.0.beta3 2017-10-04
199
-
200
-
201
-
202
- [Compare v0.1.0.beta2...v0.1.0.beta3](https://github.com/dry-rb/dry-cli/compare/v0.1.0.beta2...v0.1.0.beta3)
203
-
204
- ## 0.1.0.beta2 2017-10-03
264
+ [0.1.0.beta3]: https://github.com/dry-rb/dry-cli/compare/v0.1.0.beta2...v0.1.0.beta3
205
265
 
266
+ ## [0.1.0.beta2] - 2017-10-03
206
267
 
207
268
  ### Added
208
269
 
209
270
  - Allow default value for arguments (@AlfonsoUceda)
210
271
 
272
+ [0.1.0.beta2]: https://github.com/dry-rb/dry-cli/compare/v0.1.0.beta1...v0.1.0.beta2
211
273
 
212
- [Compare v0.1.0.beta1...v0.1.0.beta2](https://github.com/dry-rb/dry-cli/compare/v0.1.0.beta1...v0.1.0.beta2)
213
-
214
- ## 0.1.0.beta1 2017-08-11
215
-
274
+ ## [0.1.0.beta1] - 2017-08-11
216
275
 
217
276
  ### Added
218
277
 
data/LICENSE CHANGED
@@ -1,6 +1,6 @@
1
1
  The MIT License (MIT)
2
2
 
3
- Copyright (c) 2015-2021 dry-rb team
3
+ Copyright (c) 2015-2026 Hanakai team
4
4
 
5
5
  Permission is hereby granted, free of charge, to any person obtaining a copy of
6
6
  this software and associated documentation files (the "Software"), to deal in
data/README.md CHANGED
@@ -1,30 +1,17 @@
1
- <!--- this file is synced from dry-rb/template-gem project -->
2
- [gem]: https://rubygems.org/gems/dry-cli
3
- [actions]: https://github.com/dry-rb/dry-cli/actions
4
- [codacy]: https://www.codacy.com/gh/dry-rb/dry-cli
5
- [chat]: https://dry-rb.zulipchat.com
6
- [inchpages]: http://inch-ci.org/github/dry-rb/dry-cli
1
+ <!--- This file is synced from hanakai-rb/repo-sync -->
7
2
 
8
- # dry-cli [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat]
3
+ [rubygem]: https://rubygems.org/gems/dry-cli
4
+ [actions]: https://github.com/dry-rb/dry-cli/actions
9
5
 
10
- [![Gem Version](https://badge.fury.io/rb/dry-cli.svg)][gem]
11
- [![CI Status](https://github.com/dry-rb/dry-cli/workflows/ci/badge.svg)][actions]
12
- [![Codacy Badge](https://api.codacy.com/project/badge/Grade/61dd5d070fc74f0cacf575b19d4930e1)][codacy]
13
- [![Codacy Badge](https://api.codacy.com/project/badge/Coverage/61dd5d070fc74f0cacf575b19d4930e1)][codacy]
14
- [![Inline docs](http://inch-ci.org/github/dry-rb/dry-cli.svg?branch=main)][inchpages]
6
+ # dry-cli [![Gem Version](https://badge.fury.io/rb/dry-cli.svg)][rubygem] [![CI Status](https://github.com/dry-rb/dry-cli/workflows/CI/badge.svg)][actions]
15
7
 
16
8
  ## Links
17
9
 
18
- * [User documentation](https://dry-rb.org/gems/dry-cli)
19
- * [API documentation](http://rubydoc.info/gems/dry-cli)
20
-
21
- ## Supported Ruby versions
22
-
23
- This library officially supports the following Ruby versions:
24
-
25
- * MRI `>= 2.4.0`
26
- * jruby `>= 9.3` (postponed until 2.7 is supported)
10
+ - [User documentation](https://dry-rb.org/gems/dry-cli)
11
+ - [API documentation](http://rubydoc.info/gems/dry-cli)
12
+ - [Forum](https://discourse.dry-rb.org)
27
13
 
28
14
  ## License
29
15
 
30
16
  See `LICENSE` file.
17
+
data/dry-cli.gemspec CHANGED
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- # this file is synced from dry-rb/template-gem project
3
+ # This file is synced from hanakai-rb/repo-sync. To update it, edit repo-sync.yml.
4
4
 
5
5
  lib = File.expand_path("lib", __dir__)
6
6
  $LOAD_PATH.unshift(lib) unless $LOAD_PATH.include?(lib)
@@ -8,8 +8,8 @@ require "dry/cli/version"
8
8
 
9
9
  Gem::Specification.new do |spec|
10
10
  spec.name = "dry-cli"
11
- spec.authors = ["Luca Guidi"]
12
- spec.email = ["me@lucaguidi.com"]
11
+ spec.authors = ["Hanakai team"]
12
+ spec.email = ["info@hanakai.org"]
13
13
  spec.license = "MIT"
14
14
  spec.version = Dry::CLI::VERSION.dup
15
15
 
@@ -21,18 +21,18 @@ Gem::Specification.new do |spec|
21
21
  spec.executables = []
22
22
  spec.require_paths = ["lib"]
23
23
 
24
+ spec.extra_rdoc_files = ["README.md", "CHANGELOG.md", "LICENSE"]
25
+
24
26
  spec.metadata["allowed_push_host"] = "https://rubygems.org"
25
27
  spec.metadata["changelog_uri"] = "https://github.com/dry-rb/dry-cli/blob/main/CHANGELOG.md"
26
28
  spec.metadata["source_code_uri"] = "https://github.com/dry-rb/dry-cli"
27
29
  spec.metadata["bug_tracker_uri"] = "https://github.com/dry-rb/dry-cli/issues"
30
+ spec.metadata["funding_uri"] = "https://github.com/sponsors/hanami"
28
31
 
29
- spec.required_ruby_version = ">= 2.7.0"
30
-
31
- # to update dependencies edit project.yml
32
+ spec.required_ruby_version = ">= 3.2"
32
33
 
33
- spec.add_development_dependency "bundler", ">= 1.6", "< 3"
34
- spec.add_development_dependency "rake", "~> 13.0"
35
- spec.add_development_dependency "rspec", "~> 3.7"
36
- spec.add_development_dependency "rubocop", "~> 0.82"
37
- spec.add_development_dependency "simplecov", "~> 0.17.1"
34
+ spec.add_development_dependency "bundler"
35
+ spec.add_development_dependency "rake"
36
+ spec.add_development_dependency "rspec"
38
37
  end
38
+
@@ -86,8 +86,8 @@ module Dry
86
86
  required_arguments = command.required_arguments
87
87
  optional_arguments = command.optional_arguments
88
88
 
89
- required = required_arguments.map { |arg| arg.name.upcase }.join(" ") if required_arguments.any? # rubocop:disable Metrics/LineLength
90
- optional = optional_arguments.map { |arg| "[#{arg.name.upcase}]" }.join(" ") if optional_arguments.any? # rubocop:disable Metrics/LineLength
89
+ required = required_arguments.map { |arg| arg.name.upcase }.join(" ") if required_arguments.any?
90
+ optional = optional_arguments.map { |arg| "[#{arg.name.upcase}]" }.join(" ") if optional_arguments.any?
91
91
  result = [required, optional].compact
92
92
 
93
93
  " #{result.join(" ")}" unless result.empty?
@@ -97,7 +97,7 @@ module Dry
97
97
  # @api private
98
98
  def self.extended_command_arguments(command)
99
99
  command.arguments.map do |argument|
100
- " #{argument.name.to_s.upcase.ljust(32)} # #{"REQUIRED " if argument.required?}#{argument.desc}" # rubocop:disable Metrics/LineLength
100
+ " #{argument.name.to_s.upcase.ljust(32)} # #{"REQUIRED " if argument.required?}#{argument.desc}"
101
101
  end.join("\n")
102
102
  end
103
103
 
@@ -109,6 +109,8 @@ module Dry
109
109
  name = Inflector.dasherize(option.name)
110
110
  name = if option.boolean?
111
111
  "[no-]#{name}"
112
+ elsif option.flag?
113
+ name
112
114
  elsif option.array?
113
115
  "#{name}=VALUE1,VALUE2,.."
114
116
  else
@@ -45,11 +45,7 @@ module Dry
45
45
 
46
46
  # @since 0.7.0
47
47
  # @api private
48
- attr_reader :subcommands
49
-
50
- # @since 0.7.0
51
- # @api private
52
- attr_writer :subcommands
48
+ attr_accessor :subcommands
53
49
  end
54
50
 
55
51
  # Set the description of the command
@@ -199,7 +195,12 @@ module Dry
199
195
  # # Options:
200
196
  # # --help, -h # Print this help
201
197
  def self.argument(name, options = {})
202
- @arguments << Argument.new(name, options)
198
+ new_arg = Argument.new(name, options)
199
+
200
+ duplicate_index = @arguments.find_index { _1.name == new_arg.name }
201
+ @arguments.delete_at(duplicate_index) unless duplicate_index.nil?
202
+
203
+ @arguments << new_arg
203
204
  end
204
205
 
205
206
  # Command line option (aka optional argument)
@@ -313,7 +314,12 @@ module Dry
313
314
  # # Options:
314
315
  # # --port=VALUE, -p VALUE
315
316
  def self.option(name, options = {})
316
- @options << Option.new(name, options)
317
+ new_op = Option.new(name, options)
318
+
319
+ duplicate_index = @options.find_index { _1.name == new_op.name }
320
+ @options.delete_at(duplicate_index) unless duplicate_index.nil?
321
+
322
+ @options << new_op
317
323
  end
318
324
 
319
325
  # @since 0.1.0
@@ -383,6 +389,39 @@ module Dry
383
389
  optional_arguments
384
390
  subcommands
385
391
  ] => "self.class"
392
+
393
+ protected
394
+
395
+ # The error output used to print error messaging
396
+ #
397
+ # @example
398
+ # class MyCommand
399
+ # def call
400
+ # out.puts "Hello World!"
401
+ # exit(0)
402
+ # rescue StandardError => e
403
+ # err.puts "Uh oh: #{e.message}"
404
+ # exit(1)
405
+ # end
406
+ # end
407
+ #
408
+ # @since unreleased
409
+ # @return [IO]
410
+ attr_reader :err
411
+
412
+ # The standard output object used to print messaging
413
+ #
414
+ # @example
415
+ # class MyCommand
416
+ # def call
417
+ # out.puts "Hello World!"
418
+ # exit(0)
419
+ # end
420
+ # end
421
+ #
422
+ # @since unreleased
423
+ # @return [IO]
424
+ attr_reader :out
386
425
  end
387
426
  end
388
427
  end
@@ -1,7 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require "set"
4
-
5
3
  module Dry
6
4
  class CLI
7
5
  # Command registry
@@ -18,7 +16,7 @@ module Dry
18
16
 
19
17
  # @since 0.1.0
20
18
  # @api private
21
- def set(name, command, aliases)
19
+ def set(name, command, aliases, hidden)
22
20
  @_mutex.synchronize do
23
21
  node = @root
24
22
  name.split(/[[:space:]]/).each do |token|
@@ -26,6 +24,7 @@ module Dry
26
24
  end
27
25
 
28
26
  node.aliases!(aliases)
27
+ node.hidden!(hidden)
29
28
  if command
30
29
  node.leaf!(command)
31
30
  node.subcommands!(command)
@@ -37,7 +36,7 @@ module Dry
37
36
 
38
37
  # @since 0.1.0
39
38
  # @api private
40
- #
39
+ # rubocop:disable Metrics/AbcSize
41
40
  def get(arguments)
42
41
  @_mutex.synchronize do
43
42
  node = @root
@@ -56,7 +55,7 @@ module Dry
56
55
  result = LookupResult.new(node, args, names, false)
57
56
  break
58
57
  elsif tmp.leaf?
59
- args = arguments[i + 1..-1]
58
+ args = arguments[i + 1..]
60
59
  names = arguments[0..i]
61
60
  node = tmp
62
61
  result = LookupResult.new(node, args, names, true)
@@ -72,6 +71,7 @@ module Dry
72
71
  result
73
72
  end
74
73
  end
74
+ # rubocop:enable Metrics/AbcSize
75
75
 
76
76
  # Node of the registry
77
77
  #
@@ -90,6 +90,10 @@ module Dry
90
90
  # @api private
91
91
  attr_reader :aliases
92
92
 
93
+ # @since 1.1.1
94
+ # @api private
95
+ attr_reader :hidden
96
+
93
97
  # @since 0.1.0
94
98
  # @api private
95
99
  attr_reader :command
@@ -108,6 +112,7 @@ module Dry
108
112
  @parent = parent
109
113
  @children = {}
110
114
  @aliases = {}
115
+ @hidden = hidden
111
116
  @command = nil
112
117
 
113
118
  @before_callbacks = Chain.new
@@ -153,6 +158,12 @@ module Dry
153
158
  end
154
159
  end
155
160
 
161
+ # @since 1.1.1
162
+ # @api private
163
+ def hidden!(hidden)
164
+ @hidden = hidden
165
+ end
166
+
156
167
  # @since 0.1.0
157
168
  # @api private
158
169
  def leaf?
@@ -242,9 +253,9 @@ module Dry
242
253
 
243
254
  # @since 0.4.0
244
255
  # @api private
245
- def run(context, *args)
256
+ def run(context, **args)
246
257
  chain.each do |callback|
247
- context.instance_exec(*args, &callback)
258
+ context.instance_exec(**args, &callback)
248
259
  end
249
260
  end
250
261
  end
@@ -9,6 +9,10 @@ module Dry
9
9
  class Error < StandardError
10
10
  end
11
11
 
12
+ # @since 1.4.0
13
+ class ValueError < Error
14
+ end
15
+
12
16
  # @since 0.2.1
13
17
  class UnknownCommandError < Error
14
18
  # @since 0.2.1
@@ -13,7 +13,7 @@ module Dry
13
13
  # methods below
14
14
  #
15
15
  # DSL consists of 5 methods:
16
- # `desc`, `example`, `argument`, `option` 
16
+ # `desc`, `example`, `argument`, `option`
17
17
  # — are similar to methods from Command class
18
18
  #
19
19
  # `run` accepts a block to execute
@@ -59,6 +59,11 @@ module Dry
59
59
  type == :boolean
60
60
  end
61
61
 
62
+ # @api private
63
+ def flag?
64
+ type == :flag
65
+ end
66
+
62
67
  # @since 0.3.0
63
68
  # @api private
64
69
  def array?
@@ -92,6 +97,8 @@ module Dry
92
97
 
93
98
  if boolean?
94
99
  parser_options << "--[no-]#{dasherized_name}"
100
+ elsif flag?
101
+ parser_options << "--#{dasherized_name}"
95
102
  else
96
103
  parser_options << "--#{dasherized_name}=#{name}"
97
104
  parser_options << "--#{dasherized_name} #{name}"
@@ -112,7 +119,19 @@ module Dry
112
119
  .compact
113
120
  .uniq
114
121
  .map { |name| name.size == 1 ? "-#{name}" : "--#{name}" }
115
- .map { |name| boolean? ? name : "#{name} VALUE" }
122
+ .map { |name| boolean? || flag? ? name : "#{name} VALUE" }
123
+ end
124
+
125
+ # @api private
126
+ def valid_value?(value)
127
+ available_values = values
128
+ return true if available_values.nil?
129
+
130
+ if array?
131
+ (value - available_values).empty?
132
+ else
133
+ available_values.map(&:to_s).include?(value.to_s)
134
+ end
116
135
  end
117
136
  end
118
137
 
@@ -31,25 +31,25 @@ module Dry
31
31
 
32
32
  parsed_options = command.default_params.merge(parsed_options)
33
33
  parse_required_params(command, arguments, prog_name, parsed_options)
34
- rescue ::OptionParser::ParseError
35
- Result.failure("ERROR: \"#{prog_name}\" was called with arguments \"#{original_arguments.join(" ")}\"") # rubocop:disable Metrics/LineLength
34
+ rescue ::OptionParser::ParseError, ValueError
35
+ Result.failure("ERROR: \"#{prog_name}\" was called with arguments \"#{original_arguments.join(" ")}\"")
36
36
  end
37
37
 
38
38
  # @since 0.1.0
39
39
  # @api private
40
40
  #
41
- # rubocop:disable Metrics/AbcSize
41
+ # rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Layout/LineLength
42
42
  def self.parse_required_params(command, arguments, prog_name, parsed_options)
43
- parsed_params = match_arguments(command.arguments, arguments)
44
- parsed_required_params = match_arguments(command.required_arguments, arguments)
45
- all_required_params_satisfied = command.required_arguments.all? { |param| !parsed_required_params[param.name].nil? } # rubocop:disable Metrics/LineLength
43
+ parsed_params = match_arguments(command.arguments, arguments, parsed_options)
44
+ parsed_required_params = match_arguments(command.required_arguments, arguments, parsed_options)
45
+ all_required_params_satisfied = command.required_arguments.all? { |param| !parsed_required_params[param.name].nil? }
46
46
 
47
47
  unused_arguments = arguments.drop(command.required_arguments.length)
48
48
 
49
49
  unless all_required_params_satisfied
50
50
  parsed_required_params_values = parsed_required_params.values.compact
51
51
 
52
- usage = "\nUsage: \"#{prog_name} #{command.required_arguments.map(&:description_name).join(" ")}" # rubocop:disable Metrics/LineLength
52
+ usage = "\nUsage: \"#{prog_name} #{command.required_arguments.map(&:description_name).join(" ")}"
53
53
 
54
54
  usage += " | #{prog_name} SUBCOMMAND" if command.subcommands.any?
55
55
 
@@ -58,7 +58,7 @@ module Dry
58
58
  if parsed_required_params_values.empty?
59
59
  return Result.failure("ERROR: \"#{prog_name}\" was called with no arguments#{usage}")
60
60
  else
61
- return Result.failure("ERROR: \"#{prog_name}\" was called with arguments #{parsed_required_params_values}#{usage}") # rubocop:disable Metrics/LineLength
61
+ return Result.failure("ERROR: \"#{prog_name}\" was called with arguments #{parsed_required_params_values}#{usage}")
62
62
  end
63
63
  end
64
64
 
@@ -67,17 +67,24 @@ module Dry
67
67
  parsed_options = parsed_options.merge(args: unused_arguments) if unused_arguments.any?
68
68
  Result.success(parsed_options)
69
69
  end
70
- # rubocop:enable Metrics/AbcSize
70
+ # rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity, Layout/LineLength
71
71
 
72
- def self.match_arguments(command_arguments, arguments)
72
+ def self.match_arguments(command_arguments, arguments, default_values)
73
73
  result = {}
74
74
 
75
+ arg = nil
75
76
  command_arguments.each_with_index do |cmd_arg, index|
76
77
  if cmd_arg.array?
77
- result[cmd_arg.name] = arguments[index..-1]
78
+ arg = arguments[index..] || default_values[cmd_arg.name]
79
+ raise ValueError unless cmd_arg.valid_value?(arg)
80
+
81
+ result[cmd_arg.name] = arg
78
82
  break
79
83
  else
80
- result[cmd_arg.name] = arguments.at(index)
84
+ arg = arguments.at(index) || default_values[cmd_arg.name]
85
+ raise ValueError unless cmd_arg.valid_value?(arg)
86
+
87
+ result[cmd_arg.name] = arg
81
88
  end
82
89
  end
83
90
 
@@ -75,11 +75,11 @@ module Dry
75
75
  # end
76
76
  # end
77
77
  # end
78
- def register(name, command = nil, aliases: [], &block)
79
- @commands.set(name, command, aliases)
78
+ def register(name, command = nil, aliases: [], hidden: false, &block)
79
+ @commands.set(name, command, aliases, hidden)
80
80
 
81
81
  if block_given?
82
- prefix = Prefix.new(@commands, name, aliases)
82
+ prefix = Prefix.new(@commands, name, aliases, hidden)
83
83
  if block.arity.zero?
84
84
  prefix.instance_eval(&block)
85
85
  else
@@ -308,19 +308,28 @@ module Dry
308
308
  class Prefix
309
309
  # @since 0.1.0
310
310
  # @api private
311
- def initialize(registry, prefix, aliases)
311
+ def initialize(registry, prefix, aliases, hidden)
312
312
  @registry = registry
313
313
  @prefix = prefix
314
314
 
315
- registry.set(prefix, nil, aliases)
315
+ registry.set(prefix, nil, aliases, hidden)
316
316
  end
317
317
 
318
318
  # @since 0.1.0
319
319
  #
320
320
  # @see Dry::CLI::Registry#register
321
- def register(name, command, aliases: [])
321
+ def register(name, command = nil, aliases: [], hidden: false, &block)
322
322
  command_name = "#{prefix} #{name}"
323
- registry.set(command_name, command, aliases)
323
+ registry.set(command_name, command, aliases, hidden)
324
+
325
+ if block_given?
326
+ prefix = self.class.new(registry, command_name, aliases, hidden)
327
+ if block.arity.zero?
328
+ prefix.instance_eval(&block)
329
+ else
330
+ yield(prefix)
331
+ end
332
+ end
324
333
  end
325
334
 
326
335
  private
@@ -0,0 +1,38 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "dry/cli/program_name"
4
+ require "did_you_mean"
5
+
6
+ module Dry
7
+ class CLI
8
+ # Command(s) usage
9
+ #
10
+ # @since 1.1.1
11
+ # @api private
12
+ module SpellChecker
13
+ # @since 1.1.1
14
+ # @api private
15
+ def self.call(result, arguments)
16
+ commands = result.children.keys
17
+ cmd = cmd_to_spell(arguments, result.names)
18
+
19
+ suggestions = DidYouMean::SpellChecker.new(dictionary: commands).correct(cmd.first)
20
+ if suggestions.any?
21
+ "I don't know how to '#{cmd.join(" ")}'. Did you mean: '#{suggestions.first}' ?"
22
+ end
23
+ end
24
+
25
+ # @since 1.1.1
26
+ # @api private
27
+ def self.cmd_to_spell(arguments, result_names)
28
+ arguments - result_names
29
+ end
30
+
31
+ # @since 1.1.1
32
+ # @api private
33
+ def self.ignore?(cmd)
34
+ cmd.empty? || cmd.first.start_with?("-")
35
+ end
36
+ end
37
+ end
38
+ end
data/lib/dry/cli/usage.rb CHANGED
@@ -21,9 +21,11 @@ module Dry
21
21
  max_length, commands = commands_and_arguments(result)
22
22
 
23
23
  commands.map do |banner, node|
24
+ next if node.hidden
25
+
24
26
  usage = description(node.command) if node.leaf?
25
27
  "#{justify(banner, max_length, usage)}#{usage}"
26
- end.unshift(header).join("\n")
28
+ end.compact.unshift(header).join("\n")
27
29
  end
28
30
 
29
31
  # @since 0.1.0
@@ -55,8 +57,8 @@ module Dry
55
57
  required_arguments = command.required_arguments
56
58
  optional_arguments = command.optional_arguments
57
59
 
58
- required = required_arguments.map { |arg| arg.name.upcase }.join(" ") if required_arguments.any? # rubocop:disable Metrics/LineLength
59
- optional = optional_arguments.map { |arg| "[#{arg.name.upcase}]" }.join(" ") if optional_arguments.any? # rubocop:disable Metrics/LineLength
60
+ required = required_arguments.map { |arg| arg.name.upcase }.join(" ") if required_arguments.any?
61
+ optional = optional_arguments.map { |arg| "[#{arg.name.upcase}]" }.join(" ") if optional_arguments.any?
60
62
  result = [required, optional].compact
61
63
 
62
64
  " #{result.join(" ")}" unless result.empty?
@@ -3,6 +3,6 @@
3
3
  module Dry
4
4
  class CLI
5
5
  # @since 0.1.0
6
- VERSION = "1.0.0"
6
+ VERSION = "1.4.1"
7
7
  end
8
8
  end
data/lib/dry/cli.rb CHANGED
@@ -14,6 +14,7 @@ module Dry
14
14
  require "dry/cli/registry"
15
15
  require "dry/cli/parser"
16
16
  require "dry/cli/usage"
17
+ require "dry/cli/spell_checker"
17
18
  require "dry/cli/banner"
18
19
  require "dry/cli/inflector"
19
20
 
@@ -63,8 +64,8 @@ module Dry
63
64
  def call(arguments: ARGV, out: $stdout, err: $stderr)
64
65
  @out, @err = out, err
65
66
  kommand ? perform_command(arguments) : perform_registry(arguments)
66
- rescue SignalException => e
67
- signal_exception(e)
67
+ rescue SignalException => exception
68
+ signal_exception(exception)
68
69
  rescue Errno::EPIPE
69
70
  # no op
70
71
  end
@@ -96,6 +97,10 @@ module Dry
96
97
  # @api private
97
98
  def perform_command(arguments)
98
99
  command, args = parse(kommand, arguments, [])
100
+
101
+ command.instance_variable_set(:@err, err) unless command.instance_variable_defined?(:@err)
102
+ command.instance_variable_set(:@out, out) unless command.instance_variable_defined?(:@out)
103
+
99
104
  command.call(**args)
100
105
  end
101
106
 
@@ -108,13 +113,16 @@ module Dry
108
113
  # @api private
109
114
  def perform_registry(arguments)
110
115
  result = registry.get(arguments)
111
- return usage(result) unless result.found?
116
+ return spell_checker(result, arguments) unless result.found?
112
117
 
113
118
  command, args = parse(result.command, result.arguments, result.names)
114
119
 
115
- result.before_callbacks.run(command, args)
120
+ command.instance_variable_set(:@err, err) unless command.instance_variable_defined?(:@err)
121
+ command.instance_variable_set(:@out, out) unless command.instance_variable_defined?(:@out)
122
+
123
+ result.before_callbacks.run(command, **args)
116
124
  command.call(**args)
117
- result.after_callbacks.run(command, args)
125
+ result.after_callbacks.run(command, **args)
118
126
  end
119
127
 
120
128
  # Parse arguments for a command.
@@ -161,9 +169,10 @@ module Dry
161
169
  exit(1)
162
170
  end
163
171
 
164
- # @since 0.1.0
165
- # @api private
166
- def usage(result)
172
+ # @since 1.1.1
173
+ def spell_checker(result, arguments)
174
+ spell_checker = SpellChecker.call(result, arguments)
175
+ err.puts "#{spell_checker}\n\n" if spell_checker
167
176
  err.puts Usage.call(result)
168
177
  exit(1)
169
178
  end
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: dry-cli
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.0
4
+ version: 1.4.1
5
5
  platform: ruby
6
6
  authors:
7
- - Luca Guidi
8
- autorequire:
7
+ - Hanakai team
9
8
  bindir: bin
10
9
  cert_chain: []
11
- date: 2022-11-05 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: bundler
@@ -16,82 +15,51 @@ dependencies:
16
15
  requirements:
17
16
  - - ">="
18
17
  - !ruby/object:Gem::Version
19
- version: '1.6'
20
- - - "<"
21
- - !ruby/object:Gem::Version
22
- version: '3'
18
+ version: '0'
23
19
  type: :development
24
20
  prerelease: false
25
21
  version_requirements: !ruby/object:Gem::Requirement
26
22
  requirements:
27
23
  - - ">="
28
24
  - !ruby/object:Gem::Version
29
- version: '1.6'
30
- - - "<"
31
- - !ruby/object:Gem::Version
32
- version: '3'
25
+ version: '0'
33
26
  - !ruby/object:Gem::Dependency
34
27
  name: rake
35
28
  requirement: !ruby/object:Gem::Requirement
36
29
  requirements:
37
- - - "~>"
30
+ - - ">="
38
31
  - !ruby/object:Gem::Version
39
- version: '13.0'
32
+ version: '0'
40
33
  type: :development
41
34
  prerelease: false
42
35
  version_requirements: !ruby/object:Gem::Requirement
43
36
  requirements:
44
- - - "~>"
37
+ - - ">="
45
38
  - !ruby/object:Gem::Version
46
- version: '13.0'
39
+ version: '0'
47
40
  - !ruby/object:Gem::Dependency
48
41
  name: rspec
49
42
  requirement: !ruby/object:Gem::Requirement
50
43
  requirements:
51
- - - "~>"
52
- - !ruby/object:Gem::Version
53
- version: '3.7'
54
- type: :development
55
- prerelease: false
56
- version_requirements: !ruby/object:Gem::Requirement
57
- requirements:
58
- - - "~>"
59
- - !ruby/object:Gem::Version
60
- version: '3.7'
61
- - !ruby/object:Gem::Dependency
62
- name: rubocop
63
- requirement: !ruby/object:Gem::Requirement
64
- requirements:
65
- - - "~>"
66
- - !ruby/object:Gem::Version
67
- version: '0.82'
68
- type: :development
69
- prerelease: false
70
- version_requirements: !ruby/object:Gem::Requirement
71
- requirements:
72
- - - "~>"
73
- - !ruby/object:Gem::Version
74
- version: '0.82'
75
- - !ruby/object:Gem::Dependency
76
- name: simplecov
77
- requirement: !ruby/object:Gem::Requirement
78
- requirements:
79
- - - "~>"
44
+ - - ">="
80
45
  - !ruby/object:Gem::Version
81
- version: 0.17.1
46
+ version: '0'
82
47
  type: :development
83
48
  prerelease: false
84
49
  version_requirements: !ruby/object:Gem::Requirement
85
50
  requirements:
86
- - - "~>"
51
+ - - ">="
87
52
  - !ruby/object:Gem::Version
88
- version: 0.17.1
53
+ version: '0'
89
54
  description: Common framework to build command line interfaces with Ruby
90
55
  email:
91
- - me@lucaguidi.com
56
+ - info@hanakai.org
92
57
  executables: []
93
58
  extensions: []
94
- extra_rdoc_files: []
59
+ extra_rdoc_files:
60
+ - CHANGELOG.md
61
+ - LICENSE
62
+ - README.md
95
63
  files:
96
64
  - CHANGELOG.md
97
65
  - LICENSE
@@ -108,6 +76,7 @@ files:
108
76
  - lib/dry/cli/parser.rb
109
77
  - lib/dry/cli/program_name.rb
110
78
  - lib/dry/cli/registry.rb
79
+ - lib/dry/cli/spell_checker.rb
111
80
  - lib/dry/cli/usage.rb
112
81
  - lib/dry/cli/version.rb
113
82
  homepage: https://dry-rb.org/gems/dry-cli
@@ -118,7 +87,7 @@ metadata:
118
87
  changelog_uri: https://github.com/dry-rb/dry-cli/blob/main/CHANGELOG.md
119
88
  source_code_uri: https://github.com/dry-rb/dry-cli
120
89
  bug_tracker_uri: https://github.com/dry-rb/dry-cli/issues
121
- post_install_message:
90
+ funding_uri: https://github.com/sponsors/hanami
122
91
  rdoc_options: []
123
92
  require_paths:
124
93
  - lib
@@ -126,15 +95,14 @@ required_ruby_version: !ruby/object:Gem::Requirement
126
95
  requirements:
127
96
  - - ">="
128
97
  - !ruby/object:Gem::Version
129
- version: 2.7.0
98
+ version: '3.2'
130
99
  required_rubygems_version: !ruby/object:Gem::Requirement
131
100
  requirements:
132
101
  - - ">="
133
102
  - !ruby/object:Gem::Version
134
103
  version: '0'
135
104
  requirements: []
136
- rubygems_version: 3.1.6
137
- signing_key:
105
+ rubygems_version: 3.6.9
138
106
  specification_version: 4
139
107
  summary: Common framework to build command line interfaces with Ruby
140
108
  test_files: []