Skip to content

Commit

Permalink
Create Dry::CLI::Namespace, a class to group commands
Browse files Browse the repository at this point in the history
  • Loading branch information
gustavothecoder committed Sep 21, 2024
1 parent 252a7c2 commit 6163287
Show file tree
Hide file tree
Showing 6 changed files with 152 additions and 16 deletions.
32 changes: 29 additions & 3 deletions lib/dry/cli.rb
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ module Dry
class CLI
require "dry/cli/version"
require "dry/cli/errors"
require "dry/cli/namespace"
require "dry/cli/command"
require "dry/cli/registry"
require "dry/cli/parser"
Expand All @@ -26,11 +27,36 @@ class CLI
# @since 0.1.0
# @api private
def self.command?(command)
case command
inherits?(command, Command)
end

# Check if namespace
#
# @param namespace [Object] the namespace to check
#
# @return [TrueClass,FalseClass] true if instance of `Dry::CLI::Namespace`
#
# @since 1.1.1
# @api private
def self.namespace?(namespace)
inherits?(namespace, Namespace)
end

# Check if `obj` inherits from `klass`
#
# @param obj [Object] object to check
# @param klass [Object] class that should be inherited
#
# @return [TrueClass,FalseClass] true if `obj` inherits from `klass`
#
# @since 1.1.1
# @api private
def self.inherits?(obj, klass)
case obj
when Class
command.ancestors.include?(Command)
obj.ancestors.include?(klass)
else
command.is_a?(Command)
obj.is_a?(klass)
end
end

Expand Down
38 changes: 31 additions & 7 deletions lib/dry/cli/banner.rb
Original file line number Diff line number Diff line change
Expand Up @@ -9,14 +9,26 @@ class CLI
# @since 0.1.0
# @api private
module Banner
# Prints command banner
# Prints command/namespace banner
#
# @param command [Dry::CLI::Command] the command
# @param command [Dry::CLI::Command, Dry::CLI::Namespace] the command/namespace
# @param out [IO] standard output
#
# @since 0.1.0
# @api private
def self.call(command, name)
b = if CLI.command?(command)
command_banner(command, name)
else
namespace_banner(command, name)
end

b.compact.join("\n")
end

# @since 1.1.1
# @api private
def self.command_banner(command, name)
[
command_name(name),
command_name_and_arguments(command, name),
Expand All @@ -25,13 +37,25 @@ def self.call(command, name)
command_arguments(command),
command_options(command),
command_examples(command, name)
].compact.join("\n")
]
end

# @since 1.1.1
# @api private
def self.namespace_banner(namespace, name)
[
command_name(name, "Namespace"),
command_name_and_arguments(namespace, name),
command_description(namespace),
command_subcommands(namespace, "Commands"),
command_options(namespace)
]
end

# @since 0.1.0
# @api private
def self.command_name(name)
"Command:\n #{name}"
def self.command_name(name, label = "Command")
"#{label}:\n #{name}"
end

# @since 0.1.0
Expand Down Expand Up @@ -70,10 +94,10 @@ def self.command_description(command)
"\nDescription:\n #{command.description}"
end

def self.command_subcommands(command)
def self.command_subcommands(command, label = "Subcommands")
return if command.subcommands.empty?

"\nSubcommands:\n#{build_subcommands_list(command.subcommands)}"
"\n#{label}:\n#{build_subcommands_list(command.subcommands)}"
end

# @since 0.1.0
Expand Down
86 changes: 86 additions & 0 deletions lib/dry/cli/namespace.rb
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
# frozen_string_literal: true

module Dry
class CLI
# Base class for namespaces
#
# @since 1.1.1
class Namespace
# @since 1.1.1
# @api private
def self.inherited(base)
super
base.class_eval do
@description = nil
@examples = []
@arguments = []
@options = []
@subcommands = []
end
base.extend ClassMethods
end

# @since 1.1.1
# @api private
module ClassMethods
# @since 1.1.1
# @api private
attr_reader :description

# @since 1.1.1
# @api private
attr_reader :examples

# @since 1.1.1
# @api private
attr_reader :arguments

# @since 1.1.1
# @api private
attr_reader :options

# @since 1.1.1
# @api private
attr_accessor :subcommands
end

# Set the description of the namespace
#
# @param description [String] the description
#
# @since 1.1.1
#
# @example
# require "dry/cli"
#
# class YourNamespace < Dry::CLI::Namespace
# desc "Collection of really useful commands"
#
# class YourCommand < Dry::CLI::Command
# # ...
# end
# end
def self.desc(description)
@description = description
end

# @since 1.1.1
# @api private
def self.default_params
{}
end

# @since 1.1.1
# @api private
def self.required_arguments
[]
end

# @since 1.1.1
# @api private
def self.subcommands
subcommands
end
end
end
end
2 changes: 1 addition & 1 deletion lib/dry/cli/usage.rb
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ def self.arguments(command)
# @since 0.1.0
# @api private
def self.description(command)
return unless CLI.command?(command)
return unless CLI.command?(command) || CLI.namespace?(command)

" # #{command.description}" unless command.description.nil?
end
Expand Down
2 changes: 1 addition & 1 deletion spec/support/fixtures/shared_commands.rb
Original file line number Diff line number Diff line change
Expand Up @@ -438,7 +438,7 @@ def call(**params)
end
end

class Namespace < Dry::CLI::Command
class Namespace < Dry::CLI::Namespace
desc "This is a namespace"

class SubCommand < Dry::CLI::Command
Expand Down
8 changes: 4 additions & 4 deletions spec/support/shared_examples/inherited_commands.rb
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
expect(error).to eq(expected)
end

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

it "shows root command help considering if it implements #call" do
it "shows namespace help when using --help" do
output = capture_output { cli.call(arguments: %w[namespace --help]) }
expected = <<~DESC
Command:
Namespace:
#{cmd} namespace
Usage:
Expand All @@ -39,7 +39,7 @@
Description:
This is a namespace
Subcommands:
Commands:
sub-command # I'm a concrete command
Options:
Expand Down

0 comments on commit 6163287

Please sign in to comment.