parser: automatically generate usage banners
This commit is contained in:
parent
893ec3284f
commit
b7977244ea
@ -122,6 +122,8 @@ module Homebrew
|
|||||||
|
|
||||||
@args = Homebrew::CLI::Args.new
|
@args = Homebrew::CLI::Args.new
|
||||||
|
|
||||||
|
@command_name = caller_locations(2, 1).first.label.chomp("_args")
|
||||||
|
|
||||||
@constraints = []
|
@constraints = []
|
||||||
@conflicts = []
|
@conflicts = []
|
||||||
@switch_sources = {}
|
@switch_sources = {}
|
||||||
@ -129,6 +131,8 @@ module Homebrew
|
|||||||
@named_args_type = nil
|
@named_args_type = nil
|
||||||
@max_named_args = nil
|
@max_named_args = nil
|
||||||
@min_named_args = nil
|
@min_named_args = nil
|
||||||
|
@description = nil
|
||||||
|
@usage_banner = nil
|
||||||
@hide_from_man_page = false
|
@hide_from_man_page = false
|
||||||
@formula_options = false
|
@formula_options = false
|
||||||
|
|
||||||
@ -137,6 +141,8 @@ module Homebrew
|
|||||||
end
|
end
|
||||||
|
|
||||||
instance_eval(&block) if block
|
instance_eval(&block) if block
|
||||||
|
|
||||||
|
generate_banner
|
||||||
end
|
end
|
||||||
|
|
||||||
def switch(*names, description: nil, replacement: nil, env: nil, required_for: nil, depends_on: nil,
|
def switch(*names, description: nil, replacement: nil, env: nil, required_for: nil, depends_on: nil,
|
||||||
@ -176,8 +182,12 @@ module Homebrew
|
|||||||
Homebrew::EnvConfig.try(:"#{env}?")
|
Homebrew::EnvConfig.try(:"#{env}?")
|
||||||
end
|
end
|
||||||
|
|
||||||
|
def description(text)
|
||||||
|
@description = text.chomp
|
||||||
|
end
|
||||||
|
|
||||||
def usage_banner(text)
|
def usage_banner(text)
|
||||||
@parser.banner = "#{text}\n"
|
@usage_banner, @description = text.chomp.split("\n\n", 2)
|
||||||
end
|
end
|
||||||
|
|
||||||
def usage_banner_text
|
def usage_banner_text
|
||||||
@ -432,6 +442,70 @@ module Homebrew
|
|||||||
|
|
||||||
private
|
private
|
||||||
|
|
||||||
|
SYMBOL_TO_USAGE_MAPPING = {
|
||||||
|
text_or_regex: "<text>|`/`<regex>`/`",
|
||||||
|
url: "<URL>",
|
||||||
|
}.freeze
|
||||||
|
|
||||||
|
def generate_usage_banner
|
||||||
|
command_names = ["`#{@command_name.tr("_", "-")}`"]
|
||||||
|
aliases_to_skip = %w[instal uninstal]
|
||||||
|
command_names += Commands::HOMEBREW_INTERNAL_COMMAND_ALIASES.map do |command_alias, command|
|
||||||
|
next if aliases_to_skip.include? command_alias
|
||||||
|
|
||||||
|
"`#{command_alias.tr("_", "-")}`" if command == @command_name
|
||||||
|
end.compact.sort
|
||||||
|
|
||||||
|
options = if @processed_options.any?
|
||||||
|
" [<options>]"
|
||||||
|
else
|
||||||
|
""
|
||||||
|
end
|
||||||
|
|
||||||
|
named_args = ""
|
||||||
|
if @named_args_type.present? && @named_args_type != :none
|
||||||
|
arg_type = if @named_args_type.is_a? Array
|
||||||
|
types = @named_args_type.map do |type|
|
||||||
|
next unless type.is_a? Symbol
|
||||||
|
next SYMBOL_TO_USAGE_MAPPING[type] if SYMBOL_TO_USAGE_MAPPING.key?(type)
|
||||||
|
|
||||||
|
"<#{type}>"
|
||||||
|
end.compact
|
||||||
|
types << "<subcommand>" if @named_args_type.any? { |type| type.is_a? String }
|
||||||
|
types.join("|")
|
||||||
|
elsif SYMBOL_TO_USAGE_MAPPING.key? @named_args_type
|
||||||
|
SYMBOL_TO_USAGE_MAPPING[@named_args_type]
|
||||||
|
else
|
||||||
|
"<#{@named_args_type}>"
|
||||||
|
end
|
||||||
|
|
||||||
|
named_args = if @min_named_args.blank? && @max_named_args == 1
|
||||||
|
" [#{arg_type}]"
|
||||||
|
elsif @min_named_args.blank?
|
||||||
|
" [#{arg_type} ...]"
|
||||||
|
elsif @min_named_args == 1 && @max_named_args == 1
|
||||||
|
" #{arg_type}"
|
||||||
|
elsif @min_named_args == 1
|
||||||
|
" #{arg_type} [...]"
|
||||||
|
else
|
||||||
|
" #{arg_type} ..."
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
|
"#{command_names.join(", ")}#{options}#{named_args}"
|
||||||
|
end
|
||||||
|
|
||||||
|
def generate_banner
|
||||||
|
@usage_banner ||= generate_usage_banner
|
||||||
|
|
||||||
|
@parser.banner = <<~BANNER
|
||||||
|
#{@usage_banner}
|
||||||
|
|
||||||
|
#{@description}
|
||||||
|
|
||||||
|
BANNER
|
||||||
|
end
|
||||||
|
|
||||||
def set_switch(*names, value:, from:)
|
def set_switch(*names, value:, from:)
|
||||||
names.each do |name|
|
names.each do |name|
|
||||||
@switch_sources[option_to_name(name)] = from
|
@switch_sources[option_to_name(name)] = from
|
||||||
|
|||||||
@ -318,6 +318,90 @@ describe Homebrew::CLI::Parser do
|
|||||||
end
|
end
|
||||||
end
|
end
|
||||||
|
|
||||||
|
describe "usage banner generation" do
|
||||||
|
it "includes `[options]` if options are available" do
|
||||||
|
parser = described_class.new do
|
||||||
|
switch "--foo"
|
||||||
|
end
|
||||||
|
expect(parser.generate_help_text).to match(/\[options\]/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "doesn't include `[options]` if options are available" do
|
||||||
|
allow(described_class).to receive(:global_options).and_return([])
|
||||||
|
parser = described_class.new
|
||||||
|
expect(parser.generate_help_text).not_to match(/\[options\]/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "includes a description" do
|
||||||
|
parser = described_class.new do
|
||||||
|
description <<~EOS
|
||||||
|
This command does something
|
||||||
|
EOS
|
||||||
|
end
|
||||||
|
expect(parser.generate_help_text).to match(/This command does something/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "allows the usage banner to be overriden" do
|
||||||
|
parser = described_class.new do
|
||||||
|
usage_banner "`test` [foo] <bar>"
|
||||||
|
end
|
||||||
|
expect(parser.generate_help_text).to match(/test \[foo\] bar/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "allows a usage banner and a description to be overriden" do
|
||||||
|
parser = described_class.new do
|
||||||
|
usage_banner "`test` [foo] <bar>"
|
||||||
|
description <<~EOS
|
||||||
|
This command does something
|
||||||
|
EOS
|
||||||
|
end
|
||||||
|
expect(parser.generate_help_text).to match(/test \[foo\] bar/)
|
||||||
|
expect(parser.generate_help_text).to match(/This command does something/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows the correct usage for no named argument" do
|
||||||
|
parser = described_class.new do
|
||||||
|
named_args :none
|
||||||
|
end
|
||||||
|
expect(parser.generate_help_text).to match(/\[options\]\n/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows the correct usage for a single typed argument" do
|
||||||
|
parser = described_class.new do
|
||||||
|
named_args :formula, number: 1
|
||||||
|
end
|
||||||
|
expect(parser.generate_help_text).to match(/\[options\] formula\n/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows the correct usage for a subcommand argument with a maximum" do
|
||||||
|
parser = described_class.new do
|
||||||
|
named_args %w[off on], max: 1
|
||||||
|
end
|
||||||
|
expect(parser.generate_help_text).to match(/\[options\] \[subcommand\]\n/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows the correct usage for multiple typed argument with no maximum or minimum" do
|
||||||
|
parser = described_class.new do
|
||||||
|
named_args [:tap, :command]
|
||||||
|
end
|
||||||
|
expect(parser.generate_help_text).to match(/\[options\] \[tap|command ...\]\n/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows the correct usage for a subcommand argument with a minimum of 1" do
|
||||||
|
parser = described_class.new do
|
||||||
|
named_args :installed_formula, min: 1
|
||||||
|
end
|
||||||
|
expect(parser.generate_help_text).to match(/\[options\] installed_formula \[...\]\n/)
|
||||||
|
end
|
||||||
|
|
||||||
|
it "shows the correct usage for a subcommand argument with a minimum greater than 1" do
|
||||||
|
parser = described_class.new do
|
||||||
|
named_args :installed_formula, min: 2
|
||||||
|
end
|
||||||
|
expect(parser.generate_help_text).to match(/\[options\] installed_formula ...\n/)
|
||||||
|
end
|
||||||
|
end
|
||||||
|
|
||||||
describe "named_args" do
|
describe "named_args" do
|
||||||
let(:parser_none) {
|
let(:parser_none) {
|
||||||
described_class.new do
|
described_class.new do
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user