dry-cli 1.0.0 → 1.2.0

Sign up to get free protection for your applications and to get access to all the features.
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: []