dry-cli 1.0.0 → 1.2.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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 1d37c32bb092f046364d8537259aec4a8f3e233fe57167b8d19c667f8c3421a7
4
- data.tar.gz: dcab3ac00499da55cad199c0ca98854e16109f23d0d257ffa9a0c826c765e476
3
+ metadata.gz: 816b3ffdbf5a84ad4ce91ff6be0bba9424141a7b28b880accb087ba65b63e36d
4
+ data.tar.gz: 6ca3a54ff35dcb634acf68ff6472ddc8737cc15f99d6f7f860ac89e491759cd4
5
5
  SHA512:
6
- metadata.gz: 1e29fb0c9aa6672108b1c4afc8629ab9274655675c95a04cf85503824c1565dcef22fe2da20f6ca8a6a65d8b4b07e626f3c793fbede40a10f7a645cfaf1f7421
7
- data.tar.gz: 8d65f61919776a08c9d6519cf2e6d47c85ec5c4e723234dcee2159b5a3fa6c99d2ecb754299dcd74f9f767e3c0d9e710f099ed16b347571256af082967d8e5cb
6
+ metadata.gz: e1b289e4571b3b658ff402f1c8dd2fa8c7746fc3a17293f2a3316961622adc1570c76b82dfbcf4c74c6510f1994952928d64eb56e99284acfae797f6adeb8037
7
+ data.tar.gz: 4739d57ca298e1c85afcd5d0c499a56ca04db8e802d4c84be905a674e884599d4b2dbedca66927d9ed9a0467a5b1468c063bd57b25109c6a7e1c69068b113b59
data/CHANGELOG.md CHANGED
@@ -1,5 +1,26 @@
1
1
  <!--- DO NOT EDIT THIS FILE - IT'S AUTOMATICALLY GENERATED VIA DEVTOOLS --->
2
2
 
3
+ ## 1.2.0 2024-10-15
4
+
5
+
6
+ ### Added
7
+
8
+ - Added `:hidden` option to register commands that should not be shown in the help output. (@benoittgt in #137)
9
+ - Provide suggestions when there is a typo in a command name. (@benoittgt in #138)
10
+
11
+
12
+ [Compare v1.1.0...v1.2.0](https://github.com/dry-rb/dry-cli/compare/v1.1.0...v1.2.0)
13
+
14
+ ## 1.1.0 2024-07-14
15
+
16
+
17
+ ### Added
18
+
19
+ - 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)
20
+
21
+
22
+ [Compare v1.0.0...v1.1.0](https://github.com/dry-rb/dry-cli/compare/v1.0.0...v1.1.0)
23
+
3
24
  ## 1.0.0 2022-11-05
4
25
 
5
26
 
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-2023 dry-rb 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,29 +1,21 @@
1
1
  <!--- this file is synced from dry-rb/template-gem project -->
2
2
  [gem]: https://rubygems.org/gems/dry-cli
3
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
7
4
 
8
- # dry-cli [![Join the chat at https://dry-rb.zulipchat.com](https://img.shields.io/badge/dry--rb-join%20chat-%23346b7a.svg)][chat]
9
-
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]
5
+ # dry-cli [![Gem Version](https://badge.fury.io/rb/dry-cli.svg)][gem] [![CI Status](https://github.com/dry-rb/dry-cli/workflows/ci/badge.svg)][actions]
15
6
 
16
7
  ## Links
17
8
 
18
9
  * [User documentation](https://dry-rb.org/gems/dry-cli)
19
10
  * [API documentation](http://rubydoc.info/gems/dry-cli)
11
+ * [Forum](https://discourse.dry-rb.org)
20
12
 
21
13
  ## Supported Ruby versions
22
14
 
23
15
  This library officially supports the following Ruby versions:
24
16
 
25
17
  * MRI `>= 2.4.0`
26
- * jruby `>= 9.3` (postponed until 2.7 is supported)
18
+ * jruby `>= 9.4` (not tested on CI)
27
19
 
28
20
  ## License
29
21
 
data/dry-cli.gemspec CHANGED
@@ -26,7 +26,7 @@ Gem::Specification.new do |spec|
26
26
  spec.metadata["source_code_uri"] = "https://github.com/dry-rb/dry-cli"
27
27
  spec.metadata["bug_tracker_uri"] = "https://github.com/dry-rb/dry-cli/issues"
28
28
 
29
- spec.required_ruby_version = ">= 2.7.0"
29
+ spec.required_ruby_version = ">= 2.4.0"
30
30
 
31
31
  # to update dependencies edit project.yml
32
32
 
@@ -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? # rubocop:disable Layout/LineLength
90
+ optional = optional_arguments.map { |arg| "[#{arg.name.upcase}]" }.join(" ") if optional_arguments.any? # rubocop:disable Layout/LineLength
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}" # rubocop:disable Layout/LineLength
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
@@ -18,7 +18,7 @@ module Dry
18
18
 
19
19
  # @since 0.1.0
20
20
  # @api private
21
- def set(name, command, aliases)
21
+ def set(name, command, aliases, hidden)
22
22
  @_mutex.synchronize do
23
23
  node = @root
24
24
  name.split(/[[:space:]]/).each do |token|
@@ -26,6 +26,7 @@ module Dry
26
26
  end
27
27
 
28
28
  node.aliases!(aliases)
29
+ node.hidden!(hidden)
29
30
  if command
30
31
  node.leaf!(command)
31
32
  node.subcommands!(command)
@@ -37,7 +38,7 @@ module Dry
37
38
 
38
39
  # @since 0.1.0
39
40
  # @api private
40
- #
41
+ # rubocop:disable Metrics/AbcSize
41
42
  def get(arguments)
42
43
  @_mutex.synchronize do
43
44
  node = @root
@@ -56,7 +57,7 @@ module Dry
56
57
  result = LookupResult.new(node, args, names, false)
57
58
  break
58
59
  elsif tmp.leaf?
59
- args = arguments[i + 1..-1]
60
+ args = arguments[i + 1..]
60
61
  names = arguments[0..i]
61
62
  node = tmp
62
63
  result = LookupResult.new(node, args, names, true)
@@ -72,6 +73,7 @@ module Dry
72
73
  result
73
74
  end
74
75
  end
76
+ # rubocop:enable Metrics/AbcSize
75
77
 
76
78
  # Node of the registry
77
79
  #
@@ -90,6 +92,10 @@ module Dry
90
92
  # @api private
91
93
  attr_reader :aliases
92
94
 
95
+ # @since 1.1.1
96
+ # @api private
97
+ attr_reader :hidden
98
+
93
99
  # @since 0.1.0
94
100
  # @api private
95
101
  attr_reader :command
@@ -108,6 +114,7 @@ module Dry
108
114
  @parent = parent
109
115
  @children = {}
110
116
  @aliases = {}
117
+ @hidden = hidden
111
118
  @command = nil
112
119
 
113
120
  @before_callbacks = Chain.new
@@ -153,6 +160,12 @@ module Dry
153
160
  end
154
161
  end
155
162
 
163
+ # @since 1.1.1
164
+ # @api private
165
+ def hidden!(hidden)
166
+ @hidden = hidden
167
+ end
168
+
156
169
  # @since 0.1.0
157
170
  # @api private
158
171
  def leaf?
@@ -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,7 @@ 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" }
116
123
  end
117
124
  end
118
125
 
@@ -32,24 +32,24 @@ module Dry
32
32
  parsed_options = command.default_params.merge(parsed_options)
33
33
  parse_required_params(command, arguments, prog_name, parsed_options)
34
34
  rescue ::OptionParser::ParseError
35
- Result.failure("ERROR: \"#{prog_name}\" was called with arguments \"#{original_arguments.join(" ")}\"") # rubocop:disable Metrics/LineLength
35
+ Result.failure("ERROR: \"#{prog_name}\" was called with arguments \"#{original_arguments.join(" ")}\"") # rubocop:disable Layout/LineLength
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
42
42
  def self.parse_required_params(command, arguments, prog_name, parsed_options)
43
43
  parsed_params = match_arguments(command.arguments, arguments)
44
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
45
+ all_required_params_satisfied = command.required_arguments.all? { |param| !parsed_required_params[param.name].nil? } # rubocop:disable Layout/LineLength
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(" ")}" # rubocop:disable Layout/LineLength
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}") # rubocop:disable Layout/LineLength
62
62
  end
63
63
  end
64
64
 
@@ -67,14 +67,14 @@ 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
71
71
 
72
72
  def self.match_arguments(command_arguments, arguments)
73
73
  result = {}
74
74
 
75
75
  command_arguments.each_with_index do |cmd_arg, index|
76
76
  if cmd_arg.array?
77
- result[cmd_arg.name] = arguments[index..-1]
77
+ result[cmd_arg.name] = arguments[index..]
78
78
  break
79
79
  else
80
80
  result[cmd_arg.name] = arguments.at(index)
@@ -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,19 @@ 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, aliases: [], hidden: false)
322
322
  command_name = "#{prefix} #{name}"
323
- registry.set(command_name, command, aliases)
323
+ registry.set(command_name, command, aliases, hidden)
324
324
  end
325
325
 
326
326
  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? # rubocop:disable Layout/LineLength
61
+ optional = optional_arguments.map { |arg| "[#{arg.name.upcase}]" }.join(" ") if optional_arguments.any? # rubocop:disable Layout/LineLength
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.2.0"
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
 
@@ -108,7 +109,7 @@ module Dry
108
109
  # @api private
109
110
  def perform_registry(arguments)
110
111
  result = registry.get(arguments)
111
- return usage(result) unless result.found?
112
+ return spell_checker(result, arguments) unless result.found?
112
113
 
113
114
  command, args = parse(result.command, result.arguments, result.names)
114
115
 
@@ -161,9 +162,11 @@ module Dry
161
162
  exit(1)
162
163
  end
163
164
 
164
- # @since 0.1.0
165
- # @api private
166
- def usage(result)
165
+ # @since 1.1.1
166
+ def spell_checker(result, arguments)
167
+ spell_checker = SpellChecker.call(result, arguments)
168
+ err.puts spell_checker if spell_checker
169
+ puts
167
170
  err.puts Usage.call(result)
168
171
  exit(1)
169
172
  end
metadata CHANGED
@@ -1,14 +1,14 @@
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.2.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Luca Guidi
8
- autorequire:
8
+ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2022-11-05 00:00:00.000000000 Z
11
+ date: 2024-10-15 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bundler
@@ -108,6 +108,7 @@ files:
108
108
  - lib/dry/cli/parser.rb
109
109
  - lib/dry/cli/program_name.rb
110
110
  - lib/dry/cli/registry.rb
111
+ - lib/dry/cli/spell_checker.rb
111
112
  - lib/dry/cli/usage.rb
112
113
  - lib/dry/cli/version.rb
113
114
  homepage: https://dry-rb.org/gems/dry-cli
@@ -118,7 +119,7 @@ metadata:
118
119
  changelog_uri: https://github.com/dry-rb/dry-cli/blob/main/CHANGELOG.md
119
120
  source_code_uri: https://github.com/dry-rb/dry-cli
120
121
  bug_tracker_uri: https://github.com/dry-rb/dry-cli/issues
121
- post_install_message:
122
+ post_install_message:
122
123
  rdoc_options: []
123
124
  require_paths:
124
125
  - lib
@@ -126,15 +127,15 @@ required_ruby_version: !ruby/object:Gem::Requirement
126
127
  requirements:
127
128
  - - ">="
128
129
  - !ruby/object:Gem::Version
129
- version: 2.7.0
130
+ version: 2.4.0
130
131
  required_rubygems_version: !ruby/object:Gem::Requirement
131
132
  requirements:
132
133
  - - ">="
133
134
  - !ruby/object:Gem::Version
134
135
  version: '0'
135
136
  requirements: []
136
- rubygems_version: 3.1.6
137
- signing_key:
137
+ rubygems_version: 3.3.27
138
+ signing_key:
138
139
  specification_version: 4
139
140
  summary: Common framework to build command line interfaces with Ruby
140
141
  test_files: []