Skip to content

Commit

Permalink
Merge pull request #10880 from Bo98/cli-stronger-types
Browse files Browse the repository at this point in the history
Stricter handling of CLI args
  • Loading branch information
Bo98 committed Mar 22, 2021
2 parents a045cd7 + 8e98ce6 commit 25373a1
Show file tree
Hide file tree
Showing 19 changed files with 350 additions and 168 deletions.
2 changes: 1 addition & 1 deletion Library/Homebrew/build.rb
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ def initialize(formula, options, args:)
@formula.build = BuildOptions.new(options, formula.options)
@args = args

if args.ignore_deps?
if args.ignore_dependencies?
@deps = []
@reqs = []
else
Expand Down
8 changes: 4 additions & 4 deletions Library/Homebrew/cask/cmd/audit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,15 +13,15 @@ class Audit < AbstractCommand

def self.parser
super do
switch "--download",
switch "--[no-]download",
description: "Audit the downloaded file"
switch "--[no-]appcast",
description: "Audit the appcast"
switch "--token-conflicts",
switch "--[no-]token-conflicts",
description: "Audit for token conflicts"
switch "--strict",
switch "--[no-]strict",
description: "Run additional, stricter style checks"
switch "--online",
switch "--[no-]online",
description: "Run additional, slower style checks that require a network connection"
switch "--new-cask",
description: "Run various additional style checks to determine if a new cask is eligible " \
Expand Down
30 changes: 22 additions & 8 deletions Library/Homebrew/cli/args.rb
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ def initialize
@processed_options = []
@options_only = []
@flags_only = []
@cask_options = false

# Can set these because they will be overwritten by freeze_named_args!
# (whereas other values below will only be overwritten if passed).
Expand All @@ -33,12 +34,13 @@ def freeze_remaining_args!(remaining_args)
self[:remaining] = remaining_args.freeze
end

def freeze_named_args!(named_args)
def freeze_named_args!(named_args, cask_options:)
self[:named] = NamedArgs.new(
*named_args.freeze,
override_spec: spec(nil),
force_bottle: force_bottle?,
force_bottle: self[:force_bottle?],
flags: flags_only,
cask_options: cask_options,
parent: self,
)
end
Expand All @@ -64,12 +66,8 @@ def no_named?
named.blank?
end

def build_stable?
!HEAD?
end

def build_from_source_formulae
if build_from_source? || HEAD? || build_bottle?
if build_from_source? || self[:HEAD?] || self[:build_bottle?]
named.to_formulae_and_casks.select { |f| f.is_a?(Formula) }.map(&:full_name)
else
[]
Expand Down Expand Up @@ -129,12 +127,28 @@ def cli_args
end

def spec(default = :stable)
if HEAD?
if self[:HEAD?]
:head
else
default
end
end

def respond_to_missing?(*)
!frozen?
end

def method_missing(method_name, *args)
return_value = super

# Once we are frozen, verify any arg method calls are already defined in the table.
# The default OpenStruct behaviour is to return nil for anything unknown.
if frozen? && args.empty? && !@table.key?(method_name)
raise NoMethodError, "CLI arg for `#{method_name}` is not declared for this command"
end

return_value
end
end
end
end
102 changes: 51 additions & 51 deletions Library/Homebrew/cli/args.rbi
Original file line number Diff line number Diff line change
Expand Up @@ -3,145 +3,145 @@
module Homebrew
module CLI
class Args < OpenStruct
sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def HEAD?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def include_test?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def build_bottle?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def build_universal?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def build_from_source?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def force_bottle?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def newer_only?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def full_name?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def json?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def debug?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def quiet?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def verbose?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def fetch_HEAD?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def cask?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def dry_run?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def skip_cask_deps?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def greedy?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def force?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def ignore_pinned?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def display_times?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def formula?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def zap?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def ignore_dependencies?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def aliases?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def fix?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def keep_tmp?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def overwrite?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def silent?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def repair?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def prune_prefix?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def upload?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def total?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def dependents?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def installed?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def all?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def full?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def list_pinned?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def display_cop_names?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def syntax?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def ignore_non_pypi_packages?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def test?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def reverse?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def print_only?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def markdown?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def reset_cache?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def major?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def minor?; end

sig { returns(T.nilable(String)) }
Expand All @@ -162,13 +162,13 @@ module Homebrew
sig { returns(T.nilable(String)) }
def name; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def no_publish?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def shallow?; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def fail_if_not_changed?; end

sig { returns(T.nilable(String)) }
Expand Down Expand Up @@ -230,7 +230,7 @@ module Homebrew
sig { returns(T.nilable(T::Array[String])) }
def update; end

sig { returns(T.nilable(T::Boolean)) }
sig { returns(T::Boolean) }
def s?; end

sig { returns(T.nilable(String)) }
Expand Down
6 changes: 4 additions & 2 deletions Library/Homebrew/cli/named_args.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ module CLI
class NamedArgs < Array
extend T::Sig

def initialize(*args, parent: Args.new, override_spec: nil, force_bottle: false, flags: [])
def initialize(*args, parent: Args.new, override_spec: nil, force_bottle: false, flags: [], cask_options: false)
require "cask/cask"
require "cask/cask_loader"
require "formulary"
Expand All @@ -24,6 +24,7 @@ def initialize(*args, parent: Args.new, override_spec: nil, force_bottle: false,
@override_spec = override_spec
@force_bottle = force_bottle
@flags = flags
@cask_options = cask_options
@parent = parent

super(@args)
Expand Down Expand Up @@ -110,7 +111,8 @@ def load_formula_or_cask(name, only: nil, method: nil)

if only != :formula
begin
cask = Cask::CaskLoader.load(name, config: Cask::Config.from_args(@parent))
config = Cask::Config.from_args(@parent) if @cask_options
cask = Cask::CaskLoader.load(name, config: config)

if unreadable_error.present?
onoe <<~EOS
Expand Down
19 changes: 17 additions & 2 deletions Library/Homebrew/cli/parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ def initialize(&block)
@usage_banner = nil
@hide_from_man_page = false
@formula_options = false
@cask_options = false

self.class.global_options.each do |short, long, desc|
switch short, long, description: desc, env: option_to_name(long), method: :on_tail
Expand Down Expand Up @@ -328,9 +329,10 @@ def parse(argv = ARGV.freeze, ignore_invalid_options: false)
check_named_args(named_args)
end

@args.freeze_named_args!(named_args)
@args.freeze_named_args!(named_args, cask_options: @cask_options)
@args.freeze_remaining_args!(non_options.empty? ? remaining : [*remaining, "--", non_options])
@args.freeze_processed_options!(@processed_options)
@args.freeze

@args_parsed = true

Expand All @@ -357,6 +359,7 @@ def cask_options
send(method, *args, **options)
conflicts "--formula", args.last
end
@cask_options = true
end

sig { void }
Expand Down Expand Up @@ -518,7 +521,11 @@ def set_switch(*names, value:, from:)

def disable_switch(*names)
names.each do |name|
@args.delete_field("#{option_to_name(name)}?")
@args["#{option_to_name(name)}?"] = if name.start_with?("--[no-]")
nil
else
false
end
end
end

Expand Down Expand Up @@ -617,6 +624,14 @@ def process_option(*args, type:)
@processed_options.reject! { |existing| existing.second == option.long.first } if option.long.first.present?
@processed_options << [option.short.first, option.long.first, option.arg, option.desc.first]

if type == :switch
disable_switch(*args)
else
args.each do |name|
@args[option_to_name(name)] = nil
end
end

return if self.class.global_options.include? [option.short.first, option.long.first, option.desc.first]

@non_global_processed_options << [option.long.first || option.short.first, type]
Expand Down

0 comments on commit 25373a1

Please sign in to comment.