Add named_args DSL for commands

This commit is contained in:
Rylan Polster 2021-01-10 14:26:40 -05:00
parent 701989968d
commit da811373d3
84 changed files with 229 additions and 75 deletions

View File

@ -188,6 +188,18 @@ module Homebrew
.map(&:freeze).freeze
end
sig { returns(T::Array[Tap]) }
def to_taps
@to_taps ||= downcased_unique_named.map { |name| Tap.fetch name }.uniq.freeze
end
sig { returns(T::Array[Tap]) }
def to_installed_taps
@to_installed_taps ||= to_taps.each do |tap|
raise TapUnavailableError, tap.name unless tap.installed?
end.uniq.freeze
end
sig { returns(T::Array[String]) }
def homebrew_tap_cask_names
downcased_unique_named.grep(HOMEBREW_CASK_TAP_CASK_REGEX)

View File

@ -126,9 +126,9 @@ module Homebrew
@conflicts = []
@switch_sources = {}
@processed_options = []
@named_args_type = nil
@max_named_args = nil
@min_named_args = nil
@min_named_type = nil
@hide_from_man_page = false
@formula_options = false
@ -355,33 +355,71 @@ module Homebrew
@formula_options = true
end
sig do
params(
type: T.any(Symbol, T::Array[String], T::Array[Symbol]),
number: T.nilable(Integer),
min: T.nilable(Integer),
max: T.nilable(Integer),
).void
end
def named_args(type = nil, number: nil, min: nil, max: nil)
if number.present? && (min.present? || max.present?)
raise ArgumentError, "Do not specify both `number` and `min` or `max`"
end
if type == :none && (number.present? || min.present? || max.present?)
raise ArgumentError, "Do not specify both `number`, `min` or `max` with `named_args :none`"
end
@named_args_type = type
if type == :none
@max_named_args = 0
elsif number.present?
@min_named_args = @max_named_args = number
elsif min.present? || max.present?
@min_named_args = min
@max_named_args = max
end
end
def max_named(count)
# TODO: (2.8) uncomment for the next major/minor release
# odeprecated "`max_named`", "`named_args max:`"
raise TypeError, "Unsupported type #{count.class.name} for max_named" unless count.is_a?(Integer)
@max_named_args = count
end
def min_named(count_or_type)
# TODO: (2.8) uncomment for the next major/minor release
# odeprecated "`min_named`", "`named_args min:`"
case count_or_type
when Integer
@min_named_args = count_or_type
@min_named_type = nil
@named_args_type = nil
when Symbol
@min_named_args = 1
@min_named_type = count_or_type
@named_args_type = count_or_type
else
raise TypeError, "Unsupported type #{count_or_type.class.name} for min_named"
end
end
def named(count_or_type)
# TODO: (2.8) uncomment for the next major/minor release
# odeprecated "`named`", "`named_args`"
case count_or_type
when Integer
@max_named_args = @min_named_args = count_or_type
@min_named_type = nil
@named_args_type = nil
when Symbol
@max_named_args = @min_named_args = 1
@min_named_type = count_or_type
@named_args_type = count_or_type
else
raise TypeError, "Unsupported type #{count_or_type.class.name} for named"
end
@ -480,15 +518,15 @@ module Homebrew
def check_named_args(args)
exception = if @min_named_args && args.size < @min_named_args
case @min_named_type
when :cask
Cask::CaskUnspecifiedError
when :formula
FormulaUnspecifiedError
when :formula_or_cask
FormulaOrCaskUnspecifiedError
when :keg
KegUnspecifiedError
if @named_args_type.present?
types = @named_args_type.is_a?(Array) ? @named_args_type : [@named_args_type]
if types.any? { |arg| arg.is_a? String }
MinNamedArgumentsError.new(@min_named_args)
else
list = types.map { |type| type.to_s.tr("_", " ") }
list = list.to_sentence two_words_connector: " or ", last_word_connector: " or "
UsageError.new("this command requires a #{list} argument")
end
else
MinNamedArgumentsError.new(@min_named_args)
end

View File

@ -32,6 +32,8 @@ module Homebrew
description: "Only show cache files for casks."
conflicts "--build-from-source", "--force-bottle"
conflicts "--formula", "--cask"
named_args [:formula, :cask]
end
end

View File

@ -17,6 +17,8 @@ module Homebrew
If <cask> is provided, display the location in the Caskroom where <cask>
would be installed, without any sort of versioned directory as the last path.
EOS
named_args :cask
end
end

View File

@ -17,6 +17,8 @@ module Homebrew
If <formula> is provided, display the location in the Cellar where <formula>
would be installed, without any sort of versioned directory as the last path.
EOS
named_args :formula
end
end

View File

@ -27,6 +27,8 @@ module Homebrew
"or `--shell=auto` to detect the current shell."
switch "--plain",
description: "Generate plain output even when piped."
named_args :formula
end
end

View File

@ -25,6 +25,8 @@ module Homebrew
EOS
switch "--unbrewed",
description: "List files in Homebrew's prefix not installed by Homebrew."
named_args :formula
end
end

View File

@ -18,6 +18,8 @@ module Homebrew
If <user>`/`<repo> are provided, display where tap <user>`/`<repo>'s directory is located.
EOS
named_args :tap
end
end
@ -27,7 +29,7 @@ module Homebrew
if args.no_named?
puts HOMEBREW_REPOSITORY
else
puts args.named.map { |tap| Tap.fetch(tap).path }
puts args.named.to_taps.map(&:path)
end
end
end

View File

@ -18,7 +18,7 @@ module Homebrew
(if tapped) to standard output.
EOS
max_named 0
named_args :none
end
end

View File

@ -27,7 +27,7 @@ module Homebrew
Regenerate the UUID used for Homebrew's analytics.
EOS
max_named 1
named_args %w[state on off regenerate-uuid], max: 1
end
end

View File

@ -18,7 +18,7 @@ module Homebrew
switch "-n", "--dry-run",
description: "List what would be uninstalled, but do not actually uninstall anything."
max_named 0
named_args :none
end
end

View File

@ -31,6 +31,8 @@ module Homebrew
"If you want to delete those too: `rm -rf \"$(brew --cache)\"`"
switch "--prune-prefix",
description: "Only prune the symlinks and directories from the prefix and remove no other files."
named_args [:formula, :cask]
end
end

View File

@ -22,7 +22,7 @@ module Homebrew
depends_on: "--quiet",
description: "Include aliases of internal commands."
max_named 0
named_args :none
end
end

View File

@ -25,7 +25,7 @@ module Homebrew
Link or unlink Homebrew's completions.
EOS
max_named 1
named_args %w[state link unlink], max: 1
end
end

View File

@ -19,7 +19,7 @@ module Homebrew
a bug report, you will be required to provide this information.
EOS
max_named 0
named_args :none
end
end

View File

@ -66,6 +66,8 @@ module Homebrew
conflicts "--installed", "--all"
conflicts "--formula", "--cask"
formula_options
named_args [:formula, :cask]
end
end

View File

@ -34,7 +34,8 @@ module Homebrew
"it is interpreted as a regular expression."
conflicts "--search", "--name", "--description"
min_named 1
named_args :formula
end
end
@ -50,10 +51,14 @@ module Homebrew
end
results = if search_type.nil?
raise FormulaUnspecifiedError if args.no_named?
desc = {}
args.named.to_formulae.each { |f| desc[f.full_name] = f.desc }
Descriptions.new(desc)
else
raise UsageError, "this command requires a search term" if args.no_named?
query = args.named.join(" ")
string_or_regex = query_regexp(query)
CacheStoreDatabase.use(:descriptions) do |db|

View File

@ -27,6 +27,8 @@ module Homebrew
"if provided as arguments."
switch "-D", "--audit-debug",
description: "Enable debugging and profiling of audit methods."
named_args :diagnostic_check
end
end

View File

@ -58,7 +58,7 @@ module Homebrew
conflicts "--cask", "--build-bottle"
conflicts "--cask", "--force-bottle"
min_named :formula_or_cask
named_args [:formula, :cask], min: 1
end
end

View File

@ -33,7 +33,7 @@ module Homebrew
description: "The Gist will be marked private and will not appear in listings but will "\
"be accessible with its link."
named :formula
named_args :formula, number: 1
end
end

View File

@ -23,6 +23,8 @@ module Homebrew
switch "--cask", "--casks",
description: "Treat all named arguments as casks."
conflicts "--formula", "--cask"
named_args [:formula, :cask]
end
end

View File

@ -68,6 +68,8 @@ module Homebrew
conflicts "--formula", "--cask"
conflicts "--installed", "--all"
named_args [:formula, :cask]
end
end

View File

@ -125,7 +125,8 @@ module Homebrew
conflicts "--ignore-dependencies", "--only-dependencies"
conflicts "--build-from-source", "--build-bottle", "--force-bottle"
min_named :formula_or_cask
named_args [:formula, :cask], min: 1
end
end

View File

@ -18,7 +18,7 @@ module Homebrew
List installed formulae that are not dependencies of another installed formula.
EOS
max_named 0
named_args :none
end
end

View File

@ -29,7 +29,7 @@ module Homebrew
switch "-f", "--force",
description: "Allow keg-only formulae to be linked."
min_named :keg
named_args :installed_formula, min: 1
end
end

View File

@ -72,6 +72,8 @@ module Homebrew
conflicts "--full-name", flag
conflicts "--cask", flag
end
named_args [:installed_formula, :installed_cask]
end
end

View File

@ -30,7 +30,8 @@ module Homebrew
description: "Print only a specified number of commits."
conflicts "-1", "--max-count"
max_named 1
named_args :formula, max: 1
end
end

View File

@ -22,7 +22,7 @@ module Homebrew
description: "Treat installed <formula> and provided <formula> as if they are from "\
"the same taps and migrate them anyway."
min_named :formula
named_args :installed_formula, min: 1
end
end

View File

@ -24,6 +24,8 @@ module Homebrew
comma_array "--hide",
description: "Act as if none of the specified <hidden> are installed. <hidden> should be "\
"a comma-separated list of formulae."
named_args :formula
end
end

View File

@ -29,6 +29,8 @@ module Homebrew
description: "Show options for the specified <command>."
conflicts "--installed", "--all", "--command"
named_args :formula
end
end

View File

@ -42,6 +42,8 @@ module Homebrew
conflicts "--quiet", "--verbose", "--json"
conflicts "--formula", "--cask"
named_args [:formula, :cask]
end
end

View File

@ -19,7 +19,7 @@ module Homebrew
issuing the `brew upgrade` <formula> command. See also `unpin`.
EOS
min_named :formula
named_args :installed_formula, min: 1
end
end

View File

@ -19,7 +19,7 @@ module Homebrew
Rerun the post-install steps for <formula>.
EOS
min_named :keg
named_args :installed_formula, min: 1
end
end

View File

@ -24,6 +24,8 @@ module Homebrew
description: "Verify any alias symlinks in each tap."
switch "--syntax",
description: "Syntax-check all of Homebrew's Ruby files (if no `<tap>` is passed)."
named_args :tap
end
end
@ -41,7 +43,7 @@ module Homebrew
taps = if args.no_named?
Tap
else
args.named.map { |t| Tap.fetch(t) }
args.named.to_installed_taps
end
taps.each do |tap|
Homebrew.failed = true unless Readall.valid_tap?(tap, options)

View File

@ -75,7 +75,8 @@ module Homebrew
cask_options
conflicts "--build-from-source", "--force-bottle"
min_named :formula_or_cask
named_args [:formula, :cask], min: 1
end
end

View File

@ -66,6 +66,9 @@ module Homebrew
conflicts("--desc", "--pull-request")
conflicts(*package_manager_switches)
# TODO: (2.9) uncomment when the `odeprecated`/`odisabled` for `brew search` with no arguments is removed
# named_args min: 1
end
end

View File

@ -24,6 +24,8 @@ module Homebrew
description: "Print a JSON representation of <tap>. Currently the default and only accepted "\
"value for <version> is `v1`. See the docs for examples of using the JSON "\
"output: <https://docs.brew.sh/Querying-Brew>"
named_args :tap
end
end
@ -33,9 +35,7 @@ module Homebrew
taps = if args.installed?
Tap
else
args.named.sort.map do |name|
Tap.fetch(name)
end
args.named.to_taps
end
if args.json

View File

@ -41,7 +41,7 @@ module Homebrew
switch "--list-pinned",
description: "List all pinned taps."
max_named 2
named_args :tap, max: 2
end
end

View File

@ -40,7 +40,7 @@ module Homebrew
description: "Treat all named arguments as casks."
conflicts "--formula", "--cask"
min_named :formula_or_cask
named_args [:installed_formula, :installed_cask], min: 1
end
end

View File

@ -24,7 +24,7 @@ module Homebrew
description: "List files which would be unlinked without actually unlinking or "\
"deleting any files."
min_named :keg
named_args :installed_formula, min: 1
end
end

View File

@ -19,7 +19,7 @@ module Homebrew
See also `pin`.
EOS
min_named :formula
named_args :installed_formula, min: 1
end
end

View File

@ -17,15 +17,14 @@ module Homebrew
Remove a tapped formula repository.
EOS
min_named 1
named_args :tap, min: 1
end
end
def untap
args = untap_args.parse
args.named.each do |tapname|
tap = Tap.fetch(tapname)
args.named.to_installed_taps.each do |tap|
odie "Untapping #{tap} is not allowed" if tap.core_tap?
installed_tap_formulae = Formula.installed.select { |formula| formula.tap == tap }

View File

@ -89,6 +89,8 @@ module Homebrew
cask_options
conflicts "--build-from-source", "--force-bottle"
named_args [:installed_formula, :installed_cask]
end
end

View File

@ -46,7 +46,8 @@ module Homebrew
description: "Include only casks."
conflicts "--formula", "--cask"
min_named :formula
named_args :formula, min: 1
end
end

View File

@ -90,6 +90,8 @@ module Homebrew
conflicts "--display-cop-names", "--skip-style"
conflicts "--display-cop-names", "--only-cops"
conflicts "--display-cop-names", "--except-cops"
named_args [:formula, :cask]
end
end

View File

@ -86,7 +86,8 @@ module Homebrew
description: "Use the specified <URL> as the root of the bottle's URL instead of Homebrew's default."
conflicts "--no-rebuild", "--keep-old"
min_named 1
named_args :installed_formula, min: 1
end
end

View File

@ -52,7 +52,8 @@ module Homebrew
conflicts "--dry-run", "--write"
conflicts "--no-audit", "--online"
named 1
named_args :cask, number: 1
end
end

View File

@ -79,7 +79,8 @@ module Homebrew
conflicts "--no-audit", "--strict"
conflicts "--no-audit", "--online"
conflicts "--url", "--tag"
max_named 1
named_args :formula, max: 1
end
end

View File

@ -23,7 +23,7 @@ module Homebrew
flag "--message=",
description: "Append <message> to the default commit message."
min_named :formula
named_args :formula, min: 1
end
end

View File

@ -29,7 +29,7 @@ module Homebrew
flag "--state-file=",
description: "File for caching state."
min_named 1
named_args [:cask, :tap], min: 1
end
end

View File

@ -21,6 +21,8 @@ module Homebrew
description: "Limit number of package results returned."
switch :verbose
switch :debug
named_args :formula
end
end

View File

@ -23,7 +23,7 @@ module Homebrew
description: "Treat all named arguments as casks."
conflicts "--formula", "--cask"
named :formula_or_cask
named_args [:formula, :cask], number: 1
end
end

View File

@ -18,7 +18,7 @@ module Homebrew
Display the path to the file being used when invoking `brew` <cmd>.
EOS
min_named 1
named_args :command, min: 1
end
end

View File

@ -69,7 +69,7 @@ module Homebrew
conflicts "--cask", "--HEAD"
conflicts "--cask", "--set-license"
named 1
named_args number: 1
end
end

View File

@ -28,7 +28,7 @@ module Homebrew
switch "--upload",
description: "Upload built bottles to Bintray."
min_named :formula
named_args :formula, min: 1
end
end

View File

@ -24,6 +24,8 @@ module Homebrew
switch "--cask", "--casks",
description: "Treat all named arguments as casks."
conflicts "--formula", "--cask"
named_args [:formula, :cask]
end
end

View File

@ -97,7 +97,7 @@ module Homebrew
switch "-f", "--force",
description: "Overwrite the destination formula if it already exists."
named 2
named_args :formula, number: 2
end
end

View File

@ -18,7 +18,7 @@ module Homebrew
Display the path where <formula> is located.
EOS
min_named :formula
named_args :formula, min: 1
end
end

View File

@ -18,7 +18,7 @@ module Homebrew
Install Homebrew's Bundler gems.
EOS
max_named 0
named_args :none
end
end

View File

@ -28,6 +28,8 @@ module Homebrew
switch "--cached",
description: "Print the cached linkage values stored in `HOMEBREW_CACHE`, set by a previous "\
"`brew linkage` run."
named_args :installed_formula
end
end

View File

@ -49,6 +49,8 @@ module Homebrew
conflicts "--debug", "--json"
conflicts "--tap=", "--all", "--installed"
conflicts "--cask", "--formula"
named_args [:formula, :cask]
end
end

View File

@ -31,7 +31,7 @@ module Homebrew
switch "--link",
description: "This is now done automatically by `brew update`."
max_named 0
named_args :none
end
end

View File

@ -24,7 +24,7 @@ module Homebrew
switch "--no-publish",
description: "Upload to Bintray, but don't publish."
min_named :formula
named_args :formula, min: 1
end
end

View File

@ -34,7 +34,7 @@ module Homebrew
switch "--ignore-failures",
description: "Include pull requests that have failing status checks."
max_named 0
named_args :none
end
end

View File

@ -29,7 +29,7 @@ module Homebrew
flag "--workflow=",
description: "Target workflow filename (default: `publish-commit-bottles.yml`)."
min_named 1
named_args number: 1
end
end

View File

@ -70,7 +70,8 @@ module Homebrew
description: "Comma-separated list of workflows which can be ignored if they have not been run."
conflicts "--clean", "--autosquash"
min_named 1
named_args number: 1
end
end

View File

@ -33,6 +33,8 @@ module Homebrew
description: "Upload to the specified Bintray organisation (default: `homebrew`)."
flag "--root-url=",
description: "Use the specified <URL> as the root of the bottle's URL instead of Homebrew's default."
named_args :none
end
end

View File

@ -18,6 +18,8 @@ module Homebrew
EOS
switch "--stackprof",
description: "Use `stackprof` instead of `ruby-prof` (the default)."
named_args :command
end
end

View File

@ -25,7 +25,7 @@ module Homebrew
switch "--markdown",
description: "Print as a Markdown list."
max_named 2
named_args max: 2
end
end

View File

@ -27,7 +27,7 @@ module Homebrew
flag "-c=", "--cmd=",
description: "Execute commands in a non-interactive shell."
max_named 1
named_args max: 1
end
end

View File

@ -17,6 +17,8 @@ module Homebrew
Print a Markdown summary of Homebrew's GitHub Sponsors, suitable for pasting into a README.
EOS
named_args :none
end
end

View File

@ -42,6 +42,8 @@ module Homebrew
conflicts "--formula", "--cask"
conflicts "--only-cops", "--except-cops"
named_args [:formula, :cask, :tap]
end
end

View File

@ -26,7 +26,8 @@ module Homebrew
description: "Initialize Git repository with the specified branch name (default: `main`)."
conflicts "--no-git", "--branch"
named 1
named_args :tap, number: 1
end
end
@ -36,8 +37,7 @@ module Homebrew
label = args.pull_label || "pr-pull"
branch = args.branch || "main"
tap_name = args.named.first
tap = Tap.fetch(tap_name)
tap = args.named.to_taps.first
raise "Invalid tap name '#{tap_name}'" unless tap.path.to_s.match?(HOMEBREW_TAP_PATH_REGEX)
titleized_user = tap.user.dup

View File

@ -30,7 +30,7 @@ module Homebrew
switch "--retry",
description: "Retry if a testing fails."
min_named :formula
named_args :installed_formula, min: 1
end
end

View File

@ -34,7 +34,7 @@ module Homebrew
flag "--seed=",
description: "Randomise tests with the specified <value> instead of a random seed."
max_named 0
named_args :none
end
end

View File

@ -37,7 +37,8 @@ module Homebrew
"in their paths (relative to the input path passed to Sorbet)."
conflicts "--dir", "--file"
max_named 0
named_args :none
end
end

View File

@ -24,6 +24,8 @@ module Homebrew
switch "--total",
description: "Output the number of unbottled and total formulae."
conflicts "--dependents", "--total"
named_args :formula
end
end

View File

@ -30,7 +30,8 @@ module Homebrew
description: "Overwrite the destination directory if it already exists."
conflicts "--git", "--patch"
min_named :formula
named_args :formula, min: 1
end
end

View File

@ -21,7 +21,7 @@ module Homebrew
description: "Return a failing status code if current license data's version is the same as " \
"the upstream. This can be used to notify CI when the SPDX license data is out of date."
max_named 0
named_args :none
end
end

View File

@ -34,7 +34,7 @@ module Homebrew
comma_array "--exclude-packages=",
description: "Exclude these packages when finding resources."
min_named :formula
named_args :formula, number: 1
end
end

View File

@ -26,7 +26,7 @@ module Homebrew
flag "--before=",
description: "Use the commit at the specified <date> as the start commit."
max_named 0
named_args :none
end
end

View File

@ -21,7 +21,7 @@ module Homebrew
comma_array "--update",
description: "Update all vendored Gems to the latest version."
max_named 0
named_args :none
end
end

View File

@ -6,7 +6,7 @@ shared_examples "a command that requires a Cask token" do
it "raises an exception " do
expect {
described_class.run
}.to raise_error(Cask::CaskUnspecifiedError, "This command requires a Cask token.")
}.to raise_error(UsageError, /this command requires a .*cask.* argument/)
end
end
end

View File

@ -162,4 +162,37 @@ describe Homebrew::CLI::NamedArgs do
expect(described_class.new("foo", "baz").to_paths(only: :cask)).to eq [cask_path, Cask::CaskLoader.path("baz")]
end
end
describe "#to_taps" do
it "returns taps" do
taps = described_class.new("homebrew/foo", "bar/baz")
expect(taps.to_taps.map(&:name)).to eq %w[homebrew/foo bar/baz]
end
it "raises an error for invalid tap" do
taps = described_class.new("homebrew/foo", "barbaz")
expect { taps.to_taps }.to raise_error(RuntimeError, /Invalid tap name/)
end
end
describe "#to_installed_taps" do
before do
(HOMEBREW_REPOSITORY/"Library/Taps/homebrew/homebrew-foo").mkpath
end
it "returns installed taps" do
taps = described_class.new("homebrew/foo")
expect(taps.to_installed_taps.map(&:name)).to eq %w[homebrew/foo]
end
it "raises an error for uninstalled tap" do
taps = described_class.new("homebrew/foo", "bar/baz")
expect { taps.to_installed_taps }.to raise_error(TapUnavailableError)
end
it "raises an error for invalid tap" do
taps = described_class.new("homebrew/foo", "barbaz")
expect { taps.to_installed_taps }.to raise_error(RuntimeError, /Invalid tap name/)
end
end
end