Refactor CLI to remove unless args_parsed

Refactor the CLI::Args module so it doesn't have different paths to
check arguments depending on whether the arguments have been parsed or
not. Instead, set the values we need from the global ARGV at
first, global initialisation time where they will be thrown away when
the actual arguments are parsed.

To do this some other general refactoring was needed:
- more methods made private when possible
- e.g. `HEAD?` used consistently instead of `head` before arguments
  are parsed.
- formula options are only parsed after named arguments are extracted
This commit is contained in:
Mike McQuaid 2020-05-05 12:50:41 +01:00
parent a5a5a1a83a
commit 20a1199375
No known key found for this signature in database
GPG Key ID: 48A898132FD8EE70
10 changed files with 104 additions and 117 deletions

View File

@ -5,56 +5,44 @@ require "ostruct"
module Homebrew module Homebrew
module CLI module CLI
class Args < OpenStruct class Args < OpenStruct
attr_reader :processed_options, :args_parsed
# undefine tap to allow --tap argument # undefine tap to allow --tap argument
undef tap undef tap
def initialize def initialize(argv = ARGV.dup.freeze, set_default_args: false)
super super()
self[:remaining] = []
self[:argv] = ARGV.dup.freeze
@args_parsed = false
@processed_options = [] @processed_options = []
# Set values needed before Parser#parse has been run.
return unless set_default_args
self[:build_from_source?] = argv.include?("--build-from-source") || argv.include?("-s")
self[:build_bottle?] = argv.include?("--build-bottle")
self[:force_bottle?] = argv.include?("--force-bottle")
self[:HEAD?] = argv.include?("--HEAD")
self[:devel?] = argv.include?("--devel")
self[:universal?] = argv.include?("--universal")
self[:named_args] = argv.reject { |arg| arg.start_with?("-") }
end
def freeze_named_args!(named_args)
self[:named_args] = named_args
self[:named_args].freeze
end end
def freeze_processed_options!(processed_options) def freeze_processed_options!(processed_options)
@processed_options += processed_options @processed_options += processed_options
@processed_options.freeze @processed_options.freeze
@args_parsed = true
end
def option_to_name(option)
option.sub(/\A--?/, "")
.tr("-", "_")
end
def cli_args
return @cli_args if @cli_args
@cli_args = []
processed_options.each do |short, long|
option = long || short
switch = "#{option_to_name(option)}?".to_sym
flag = option_to_name(option).to_sym
if @table[switch] == true || @table[flag] == true
@cli_args << option
elsif @table[flag].instance_of? String
@cli_args << option + "=" + @table[flag]
elsif @table[flag].instance_of? Array
@cli_args << option + "=" + @table[flag].join(",")
end
end
@cli_args
end end
def options_only def options_only
@options_only ||= cli_args.select { |arg| arg.start_with?("-") } @options_only ||= cli_args.select { |arg| arg.start_with?("-") }
.freeze
end end
def flags_only def flags_only
@flags_only ||= cli_args.select { |arg| arg.start_with?("--") } @flags_only ||= cli_args.select { |arg| arg.start_with?("--") }
.freeze
end end
def passthrough def passthrough
@ -62,7 +50,7 @@ module Homebrew
end end
def named def named
remaining named_args || []
end end
def no_named? def no_named?
@ -74,16 +62,17 @@ module Homebrew
def collect_build_args def collect_build_args
build_flags = [] build_flags = []
build_flags << "--HEAD" if head build_flags << "--HEAD" if HEAD?
build_flags << "--universal" if build_universal build_flags << "--universal" if build_universal?
build_flags << "--build-bottle" if build_bottle build_flags << "--build-bottle" if build_bottle?
build_flags << "--build-from-source" if build_from_source build_flags << "--build-from-source" if build_from_source?
build_flags build_flags
end end
def formulae def formulae
require "formula" require "formula"
@formulae ||= (downcased_unique_named - casks).map do |name| @formulae ||= (downcased_unique_named - casks).map do |name|
if name.include?("/") || File.exist?(name) if name.include?("/") || File.exist?(name)
Formulary.factory(name, spec) Formulary.factory(name, spec)
@ -91,29 +80,35 @@ module Homebrew
Formulary.find_with_priority(name, spec) Formulary.find_with_priority(name, spec)
end end
end.uniq(&:name) end.uniq(&:name)
.freeze
end end
def resolved_formulae def resolved_formulae
require "formula" require "formula"
@resolved_formulae ||= (downcased_unique_named - casks).map do |name| @resolved_formulae ||= (downcased_unique_named - casks).map do |name|
Formulary.resolve(name, spec: spec(nil)) Formulary.resolve(name, spec: spec(nil))
end.uniq(&:name) end.uniq(&:name)
.freeze
end end
def formulae_paths def formulae_paths
@formulae_paths ||= (downcased_unique_named - casks).map do |name| @formulae_paths ||= (downcased_unique_named - casks).map do |name|
Formulary.path(name) Formulary.path(name)
end.uniq end.uniq
.freeze
end end
def casks def casks
@casks ||= downcased_unique_named.grep HOMEBREW_CASK_TAP_CASK_REGEX @casks ||= downcased_unique_named.grep(HOMEBREW_CASK_TAP_CASK_REGEX)
.freeze
end end
def kegs def kegs
require "keg" require "keg"
require "formula" require "formula"
require "missing_formula" require "missing_formula"
@kegs ||= downcased_unique_named.map do |name| @kegs ||= downcased_unique_named.map do |name|
raise UsageError if name.empty? raise UsageError if name.empty?
@ -158,7 +153,7 @@ module Homebrew
Please delete (with rm -rf!) all but one and then try again. Please delete (with rm -rf!) all but one and then try again.
EOS EOS
end end
end end.freeze
end end
def build_stable? def build_stable?
@ -168,39 +163,40 @@ module Homebrew
# Whether a given formula should be built from source during the current # Whether a given formula should be built from source during the current
# installation run. # installation run.
def build_formula_from_source?(f) def build_formula_from_source?(f)
return false if !build_from_source && !build_bottle return false if !build_from_source? && !build_bottle?
formulae.any? { |args_f| args_f.full_name == f.full_name } formulae.any? { |args_f| args_f.full_name == f.full_name }
end end
def build_from_source
return argv.include?("--build-from-source") || argv.include?("-s") unless args_parsed
build_from_source? || s?
end
def build_bottle
return argv.include?("--build-bottle") unless args_parsed
build_bottle?
end
def force_bottle
return argv.include?("--force-bottle") unless args_parsed
force_bottle?
end
private private
def option_to_name(option)
option.sub(/\A--?/, "")
.tr("-", "_")
end
def cli_args
return @cli_args if @cli_args
@cli_args = []
@processed_options.each do |short, long|
option = long || short
switch = "#{option_to_name(option)}?".to_sym
flag = option_to_name(option).to_sym
if @table[switch] == true || @table[flag] == true
@cli_args << option
elsif @table[flag].instance_of? String
@cli_args << option + "=" + @table[flag]
elsif @table[flag].instance_of? Array
@cli_args << option + "=" + @table[flag].join(",")
end
end
@cli_args.freeze
end
def downcased_unique_named def downcased_unique_named
# Only lowercase names, not paths, bottle filenames or URLs # Only lowercase names, not paths, bottle filenames or URLs
arguments = if args_parsed named.map do |arg|
named
else
argv.reject { |arg| arg.start_with?("-") }
end
arguments.map do |arg|
if arg.include?("/") || arg.end_with?(".tar.gz") || File.exist?(arg) if arg.include?("/") || arg.end_with?(".tar.gz") || File.exist?(arg)
arg arg
else else
@ -209,28 +205,10 @@ module Homebrew
end.uniq end.uniq
end end
def head
return argv.include?("--HEAD") unless args_parsed
HEAD?
end
def devel
return argv.include?("--devel") unless args_parsed
devel?
end
def build_universal
return argv.include?("--universal") unless args_parsed
universal?
end
def spec(default = :stable) def spec(default = :stable)
if head if HEAD?
:head :head
elsif devel elsif devel?
:devel :devel
else else
default default

View File

@ -12,8 +12,8 @@ module Homebrew
class Parser class Parser
attr_reader :processed_options, :hide_from_man_page attr_reader :processed_options, :hide_from_man_page
def self.parse(args = ARGV, allow_no_named_args: false, &block) def self.parse(argv = ARGV.dup.freeze, allow_no_named_args: false, &block)
new(args, &block).parse(args, allow_no_named_args: allow_no_named_args) new(argv, &block).parse(allow_no_named_args: allow_no_named_args)
end end
def self.from_cmd_path(cmd_path) def self.from_cmd_path(cmd_path)
@ -37,9 +37,10 @@ module Homebrew
} }
end end
def initialize(&block) def initialize(argv = ARGV.dup.freeze, &block)
@parser = OptionParser.new @parser = OptionParser.new
@args = Homebrew::CLI::Args.new @argv = argv
@args = Homebrew::CLI::Args.new(@argv)
@constraints = [] @constraints = []
@conflicts = [] @conflicts = []
@ -152,7 +153,7 @@ module Homebrew
@parser.to_s @parser.to_s
end end
def parse(argv = ARGV, allow_no_named_args: false) def parse(argv = @argv, allow_no_named_args: false)
raise "Arguments were already parsed!" if @args_parsed raise "Arguments were already parsed!" if @args_parsed
begin begin
@ -161,12 +162,14 @@ module Homebrew
$stderr.puts generate_help_text $stderr.puts generate_help_text
raise e raise e
end end
check_constraint_violations check_constraint_violations
check_named_args(named_args, allow_no_named_args: allow_no_named_args) check_named_args(named_args, allow_no_named_args: allow_no_named_args)
@args[:remaining] = named_args @args.freeze_named_args!(named_args)
parse_formula_options
@args.freeze_processed_options!(@processed_options) @args.freeze_processed_options!(@processed_options)
Homebrew.args = @args Homebrew.args = @args
argv.freeze
@args_parsed = true @args_parsed = true
@parser @parser
end end
@ -186,21 +189,7 @@ module Homebrew
end end
def formula_options def formula_options
@args.formulae.each do |f| @parse_formula_options = true
next if f.options.empty?
f.options.each do |o|
name = o.flag
description = "`#{f.name}`: #{o.description}"
if name.end_with? "="
flag name, description: description
else
switch name, description: description
end
end
end
rescue FormulaUnavailableError
[]
end end
def max_named(count) def max_named(count)
@ -239,6 +228,26 @@ module Homebrew
private private
def parse_formula_options
return unless @parse_formula_options
@args.formulae.each do |f|
next if f.options.empty?
f.options.each do |o|
name = o.flag
description = "`#{f.name}`: #{o.description}"
if name.end_with? "="
flag name, description: description
else
switch name, description: description
end
end
end
rescue FormulaUnavailableError
[]
end
def enable_switch(*names, from:) def enable_switch(*names, from:)
names.each do |name| names.each do |name|
@switch_sources[option_to_name(name)] = from @switch_sources[option_to_name(name)] = from

View File

@ -152,7 +152,7 @@ module Homebrew
end end
# --HEAD, fail with no head defined # --HEAD, fail with no head defined
raise "No head is defined for #{f.full_name}" if args.head? && f.head.nil? raise "No head is defined for #{f.full_name}" if args.HEAD? && f.head.nil?
# --devel, fail with no devel defined # --devel, fail with no devel defined
raise "No devel block is defined for #{f.full_name}" if args.devel? && f.devel.nil? raise "No devel block is defined for #{f.full_name}" if args.devel? && f.devel.nil?

View File

@ -18,7 +18,8 @@ module Homebrew
module_function module_function
def irb_args def irb_args
Homebrew::CLI::Parser.new do # work around IRB modifying ARGV.
Homebrew::CLI::Parser.new(ARGV.dup) do
usage_banner <<~EOS usage_banner <<~EOS
`irb` [<options>] `irb` [<options>]
@ -33,8 +34,7 @@ module Homebrew
end end
def irb def irb
# work around IRB modifying ARGV. irb_args.parse
irb_args.parse(ARGV.dup)
if args.examples? if args.examples?
puts "'v8'.f # => instance of the v8 formula" puts "'v8'.f # => instance of the v8 formula"

View File

@ -268,7 +268,7 @@ module SharedEnvExtension
# @private # @private
def effective_arch def effective_arch
if Homebrew.args.build_bottle && ARGV.bottle_arch if Homebrew.args.build_bottle? && ARGV.bottle_arch
ARGV.bottle_arch ARGV.bottle_arch
else else
Hardware.oldest_cpu Hardware.oldest_cpu

View File

@ -5,7 +5,7 @@ module Homebrew
module_function module_function
def fetch_bottle?(f) def fetch_bottle?(f)
return true if Homebrew.args.force_bottle && f.bottle return true if Homebrew.args.force_bottle? && f.bottle
return false unless f.bottle && f.pour_bottle? return false unless f.bottle && f.pour_bottle?
return false if Homebrew.args.build_formula_from_source?(f) return false if Homebrew.args.build_formula_from_source?(f)
return false unless f.bottle.compatible_cellar? return false unless f.bottle.compatible_cellar?

View File

@ -49,9 +49,9 @@ class FormulaInstaller
@show_header = false @show_header = false
@ignore_deps = false @ignore_deps = false
@only_deps = false @only_deps = false
@build_from_source = Homebrew.args.build_from_source @build_from_source = Homebrew.args.build_from_source?
@build_bottle = false @build_bottle = false
@force_bottle = Homebrew.args.force_bottle @force_bottle = Homebrew.args.force_bottle?
@include_test = ARGV.include?("--include-test") @include_test = ARGV.include?("--include-test")
@interactive = false @interactive = false
@git = false @git = false

View File

@ -85,7 +85,7 @@ module Homebrew
end end
def args def args
@args ||= CLI::Args.new @args ||= CLI::Args.new(set_default_args: true)
end end
def messages def messages

View File

@ -25,7 +25,7 @@ module Homebrew
fi = FormulaInstaller.new(f) fi = FormulaInstaller.new(f)
fi.options = options fi.options = options
fi.build_bottle = Homebrew.args.build_bottle fi.build_bottle = Homebrew.args.build_bottle?
fi.interactive = Homebrew.args.interactive? fi.interactive = Homebrew.args.interactive?
fi.git = Homebrew.args.git? fi.git = Homebrew.args.git?
fi.link_keg ||= keg_was_linked if keg_had_linked_opt fi.link_keg ||= keg_was_linked if keg_had_linked_opt

View File

@ -95,7 +95,7 @@ class SoftwareSpec
def bottled? def bottled?
bottle_specification.tag?(Utils::Bottles.tag) && \ bottle_specification.tag?(Utils::Bottles.tag) && \
(bottle_specification.compatible_cellar? || Homebrew.args.force_bottle) (bottle_specification.compatible_cellar? || Homebrew.args.force_bottle?)
end end
def bottle(disable_type = nil, disable_reason = nil, &block) def bottle(disable_type = nil, disable_reason = nil, &block)