2018-02-04 22:09:35 +05:30
|
|
|
require "optparse"
|
|
|
|
require "ostruct"
|
|
|
|
|
|
|
|
module Homebrew
|
|
|
|
module CLI
|
|
|
|
class Parser
|
2018-03-25 17:48:22 +05:30
|
|
|
def self.parse(&block)
|
|
|
|
new(&block).parse
|
|
|
|
end
|
|
|
|
|
2018-02-04 22:09:35 +05:30
|
|
|
def initialize(&block)
|
|
|
|
@parser = OptionParser.new
|
|
|
|
@parsed_args = OpenStruct.new
|
2018-04-08 16:40:02 -07:00
|
|
|
# undefine tap to allow --tap argument
|
|
|
|
@parsed_args.instance_eval { undef tap }
|
2018-04-01 22:01:06 +05:30
|
|
|
@depends = []
|
|
|
|
@conflicts = []
|
2018-02-04 22:09:35 +05:30
|
|
|
instance_eval(&block)
|
|
|
|
end
|
|
|
|
|
2018-03-25 11:04:18 +05:30
|
|
|
def switch(*names, description: nil, env: nil)
|
2018-02-04 22:09:35 +05:30
|
|
|
description = option_to_description(*names) if description.nil?
|
2018-04-01 16:47:30 +05:30
|
|
|
global_switch = names.first.is_a?(Symbol)
|
|
|
|
names, env = common_switch(*names) if global_switch
|
2018-02-04 22:09:35 +05:30
|
|
|
@parser.on(*names, description) do
|
2018-04-01 16:47:30 +05:30
|
|
|
enable_switch(*names, global_switch)
|
2018-02-04 22:09:35 +05:30
|
|
|
end
|
2018-04-01 16:47:30 +05:30
|
|
|
enable_switch(*names, global_switch) if !env.nil? &&
|
|
|
|
!ENV["HOMEBREW_#{env.to_s.upcase}"].nil?
|
2018-02-04 22:09:35 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def comma_array(name, description: nil)
|
|
|
|
description = option_to_description(name) if description.nil?
|
|
|
|
@parser.on(name, OptionParser::REQUIRED_ARGUMENT, Array, description) do |list|
|
|
|
|
@parsed_args[option_to_name(name)] = list
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-04-14 19:04:24 +05:30
|
|
|
def flag(name, description: nil)
|
|
|
|
if name.end_with? "="
|
|
|
|
required = OptionParser::REQUIRED_ARGUMENT
|
|
|
|
name.chomp! "="
|
2018-02-04 22:09:35 +05:30
|
|
|
else
|
2018-04-14 19:04:24 +05:30
|
|
|
required = OptionParser::OPTIONAL_ARGUMENT
|
2018-02-04 22:09:35 +05:30
|
|
|
end
|
|
|
|
description = option_to_description(name) if description.nil?
|
2018-04-14 19:04:24 +05:30
|
|
|
@parser.on(name, description, required) do |option_value|
|
2018-02-04 22:09:35 +05:30
|
|
|
@parsed_args[option_to_name(name)] = option_value
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
2018-04-01 22:01:06 +05:30
|
|
|
def depends(primary, secondary, mandatory: false)
|
|
|
|
@depends << [primary, secondary, mandatory]
|
|
|
|
end
|
|
|
|
|
|
|
|
def conflicts(primary, secondary)
|
|
|
|
@conflicts << [primary, secondary]
|
|
|
|
end
|
|
|
|
|
2018-02-04 22:09:35 +05:30
|
|
|
def option_to_name(name)
|
|
|
|
name.sub(/\A--?/, "").tr("-", "_")
|
|
|
|
end
|
|
|
|
|
|
|
|
def option_to_description(*names)
|
2018-03-29 03:20:14 +05:30
|
|
|
names.map { |name| name.to_s.sub(/\A--?/, "").tr("-", " ") }.sort.last
|
2018-02-04 22:09:35 +05:30
|
|
|
end
|
|
|
|
|
|
|
|
def parse(cmdline_args = ARGV)
|
2018-04-01 16:47:30 +05:30
|
|
|
@parser.parse(cmdline_args)
|
2018-04-01 22:01:06 +05:30
|
|
|
check_constraint_violations
|
2018-02-04 22:09:35 +05:30
|
|
|
@parsed_args
|
|
|
|
end
|
2018-03-25 11:04:18 +05:30
|
|
|
|
|
|
|
private
|
|
|
|
|
2018-04-01 16:47:30 +05:30
|
|
|
def enable_switch(*names, global_switch)
|
2018-03-25 11:04:18 +05:30
|
|
|
names.each do |name|
|
2018-04-01 16:47:30 +05:30
|
|
|
if global_switch
|
|
|
|
Homebrew.args["#{option_to_name(name)}?"] = true
|
|
|
|
next
|
|
|
|
end
|
2018-03-25 11:04:18 +05:30
|
|
|
@parsed_args["#{option_to_name(name)}?"] = true
|
|
|
|
end
|
|
|
|
end
|
2018-03-29 03:20:14 +05:30
|
|
|
|
2018-04-01 16:47:30 +05:30
|
|
|
# These are common/global switches accessible throughout Homebrew
|
2018-03-29 03:20:14 +05:30
|
|
|
def common_switch(name)
|
|
|
|
case name
|
|
|
|
when :quiet then [["-q", "--quiet"], :quiet]
|
|
|
|
when :verbose then [["-v", "--verbose"], :verbose]
|
|
|
|
when :debug then [["-d", "--debug"], :debug]
|
2018-04-01 16:47:30 +05:30
|
|
|
when :force then [["-f", "--force"], :force]
|
2018-03-29 03:20:14 +05:30
|
|
|
else name
|
|
|
|
end
|
|
|
|
end
|
2018-04-01 22:01:06 +05:30
|
|
|
|
|
|
|
def option_passed?(name)
|
|
|
|
@parsed_args.respond_to?(name) || @parsed_args.respond_to?("#{name}?")
|
|
|
|
end
|
|
|
|
|
|
|
|
def check_depends
|
|
|
|
@depends.each do |primary, secondary, required|
|
|
|
|
primary_passed = option_passed?(primary)
|
|
|
|
secondary_passed = option_passed?(secondary)
|
|
|
|
raise OptionDependencyError.new(primary, secondary) if required && primary_passed &&
|
|
|
|
!secondary_passed
|
|
|
|
raise OptionDependencyError.new(primary, secondary, missing: true) if secondary_passed &&
|
|
|
|
!primary_passed
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def check_conflicts
|
|
|
|
@conflicts.each do |primary, secondary|
|
|
|
|
primary_passed = option_passed?(primary)
|
|
|
|
secondary_passed = option_passed?(secondary)
|
|
|
|
raise OptionConflictError.new(primary, secondary) if primary_passed && secondary_passed
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
def check_constraint_violations
|
|
|
|
check_conflicts
|
|
|
|
check_depends
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class OptionDependencyError < RuntimeError
|
|
|
|
def initialize(arg1, arg2, missing: false)
|
|
|
|
if !missing
|
|
|
|
message = <<~EOS
|
|
|
|
`#{arg1}` and `#{arg2}` should be passed together
|
|
|
|
EOS
|
|
|
|
else
|
|
|
|
message = <<~EOS
|
|
|
|
`#{arg2}` cannot be passed without `#{arg1}`
|
|
|
|
EOS
|
|
|
|
end
|
|
|
|
super message
|
|
|
|
end
|
|
|
|
end
|
|
|
|
|
|
|
|
class OptionConflictError < RuntimeError
|
|
|
|
def initialize(arg1, arg2)
|
|
|
|
super <<~EOS
|
|
|
|
`#{arg1}` and `#{arg2}` should not be passed together
|
|
|
|
EOS
|
|
|
|
end
|
2018-02-04 22:09:35 +05:30
|
|
|
end
|
|
|
|
end
|
|
|
|
end
|