Skip to content

Commit 6163287

Browse files
Create Dry::CLI::Namespace, a class to group commands
1 parent 252a7c2 commit 6163287

File tree

6 files changed

+152
-16
lines changed

6 files changed

+152
-16
lines changed

lib/dry/cli.rb

Lines changed: 29 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ module Dry
1010
class CLI
1111
require "dry/cli/version"
1212
require "dry/cli/errors"
13+
require "dry/cli/namespace"
1314
require "dry/cli/command"
1415
require "dry/cli/registry"
1516
require "dry/cli/parser"
@@ -26,11 +27,36 @@ class CLI
2627
# @since 0.1.0
2728
# @api private
2829
def self.command?(command)
29-
case command
30+
inherits?(command, Command)
31+
end
32+
33+
# Check if namespace
34+
#
35+
# @param namespace [Object] the namespace to check
36+
#
37+
# @return [TrueClass,FalseClass] true if instance of `Dry::CLI::Namespace`
38+
#
39+
# @since 1.1.1
40+
# @api private
41+
def self.namespace?(namespace)
42+
inherits?(namespace, Namespace)
43+
end
44+
45+
# Check if `obj` inherits from `klass`
46+
#
47+
# @param obj [Object] object to check
48+
# @param klass [Object] class that should be inherited
49+
#
50+
# @return [TrueClass,FalseClass] true if `obj` inherits from `klass`
51+
#
52+
# @since 1.1.1
53+
# @api private
54+
def self.inherits?(obj, klass)
55+
case obj
3056
when Class
31-
command.ancestors.include?(Command)
57+
obj.ancestors.include?(klass)
3258
else
33-
command.is_a?(Command)
59+
obj.is_a?(klass)
3460
end
3561
end
3662

lib/dry/cli/banner.rb

Lines changed: 31 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,14 +9,26 @@ class CLI
99
# @since 0.1.0
1010
# @api private
1111
module Banner
12-
# Prints command banner
12+
# Prints command/namespace banner
1313
#
14-
# @param command [Dry::CLI::Command] the command
14+
# @param command [Dry::CLI::Command, Dry::CLI::Namespace] the command/namespace
1515
# @param out [IO] standard output
1616
#
1717
# @since 0.1.0
1818
# @api private
1919
def self.call(command, name)
20+
b = if CLI.command?(command)
21+
command_banner(command, name)
22+
else
23+
namespace_banner(command, name)
24+
end
25+
26+
b.compact.join("\n")
27+
end
28+
29+
# @since 1.1.1
30+
# @api private
31+
def self.command_banner(command, name)
2032
[
2133
command_name(name),
2234
command_name_and_arguments(command, name),
@@ -25,13 +37,25 @@ def self.call(command, name)
2537
command_arguments(command),
2638
command_options(command),
2739
command_examples(command, name)
28-
].compact.join("\n")
40+
]
41+
end
42+
43+
# @since 1.1.1
44+
# @api private
45+
def self.namespace_banner(namespace, name)
46+
[
47+
command_name(name, "Namespace"),
48+
command_name_and_arguments(namespace, name),
49+
command_description(namespace),
50+
command_subcommands(namespace, "Commands"),
51+
command_options(namespace)
52+
]
2953
end
3054

3155
# @since 0.1.0
3256
# @api private
33-
def self.command_name(name)
34-
"Command:\n #{name}"
57+
def self.command_name(name, label = "Command")
58+
"#{label}:\n #{name}"
3559
end
3660

3761
# @since 0.1.0
@@ -70,10 +94,10 @@ def self.command_description(command)
7094
"\nDescription:\n #{command.description}"
7195
end
7296

73-
def self.command_subcommands(command)
97+
def self.command_subcommands(command, label = "Subcommands")
7498
return if command.subcommands.empty?
7599

76-
"\nSubcommands:\n#{build_subcommands_list(command.subcommands)}"
100+
"\n#{label}:\n#{build_subcommands_list(command.subcommands)}"
77101
end
78102

79103
# @since 0.1.0

lib/dry/cli/namespace.rb

Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
# frozen_string_literal: true
2+
3+
module Dry
4+
class CLI
5+
# Base class for namespaces
6+
#
7+
# @since 1.1.1
8+
class Namespace
9+
# @since 1.1.1
10+
# @api private
11+
def self.inherited(base)
12+
super
13+
base.class_eval do
14+
@description = nil
15+
@examples = []
16+
@arguments = []
17+
@options = []
18+
@subcommands = []
19+
end
20+
base.extend ClassMethods
21+
end
22+
23+
# @since 1.1.1
24+
# @api private
25+
module ClassMethods
26+
# @since 1.1.1
27+
# @api private
28+
attr_reader :description
29+
30+
# @since 1.1.1
31+
# @api private
32+
attr_reader :examples
33+
34+
# @since 1.1.1
35+
# @api private
36+
attr_reader :arguments
37+
38+
# @since 1.1.1
39+
# @api private
40+
attr_reader :options
41+
42+
# @since 1.1.1
43+
# @api private
44+
attr_accessor :subcommands
45+
end
46+
47+
# Set the description of the namespace
48+
#
49+
# @param description [String] the description
50+
#
51+
# @since 1.1.1
52+
#
53+
# @example
54+
# require "dry/cli"
55+
#
56+
# class YourNamespace < Dry::CLI::Namespace
57+
# desc "Collection of really useful commands"
58+
#
59+
# class YourCommand < Dry::CLI::Command
60+
# # ...
61+
# end
62+
# end
63+
def self.desc(description)
64+
@description = description
65+
end
66+
67+
# @since 1.1.1
68+
# @api private
69+
def self.default_params
70+
{}
71+
end
72+
73+
# @since 1.1.1
74+
# @api private
75+
def self.required_arguments
76+
[]
77+
end
78+
79+
# @since 1.1.1
80+
# @api private
81+
def self.subcommands
82+
subcommands
83+
end
84+
end
85+
end
86+
end

lib/dry/cli/usage.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ def self.arguments(command)
6666
# @since 0.1.0
6767
# @api private
6868
def self.description(command)
69-
return unless CLI.command?(command)
69+
return unless CLI.command?(command) || CLI.namespace?(command)
7070

7171
" # #{command.description}" unless command.description.nil?
7272
end

spec/support/fixtures/shared_commands.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -438,7 +438,7 @@ def call(**params)
438438
end
439439
end
440440

441-
class Namespace < Dry::CLI::Command
441+
class Namespace < Dry::CLI::Namespace
442442
desc "This is a namespace"
443443

444444
class SubCommand < Dry::CLI::Command

spec/support/shared_examples/inherited_commands.rb

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@
1818
expect(error).to eq(expected)
1919
end
2020

21-
it "shows subcommands when root command doesn't implement #call" do
21+
it "shows subcommands when calling a namespace" do
2222
error = capture_error { cli.call(arguments: %w[namespace]) }
2323
expected = <<~DESC
2424
Commands:
@@ -27,10 +27,10 @@
2727
expect(error).to eq(expected)
2828
end
2929

30-
it "shows root command help considering if it implements #call" do
30+
it "shows namespace help when using --help" do
3131
output = capture_output { cli.call(arguments: %w[namespace --help]) }
3232
expected = <<~DESC
33-
Command:
33+
Namespace:
3434
#{cmd} namespace
3535
3636
Usage:
@@ -39,7 +39,7 @@
3939
Description:
4040
This is a namespace
4141
42-
Subcommands:
42+
Commands:
4343
sub-command # I'm a concrete command
4444
4545
Options:

0 commit comments

Comments
 (0)