Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

cli_parser: man-pages and help text generation using Homebrew::CLI::Parser #4383

Merged
merged 18 commits into from
Oct 3, 2018
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
77 changes: 64 additions & 13 deletions Library/Homebrew/cli_parser.rb
Original file line number Diff line number Diff line change
Expand Up @@ -5,25 +5,47 @@
module Homebrew
module CLI
class Parser
attr_reader :processed_options

def self.parse(args = ARGV, &block)
new(&block).parse(args)
end

def self.global_options
{
quiet: [["-q", "--quiet"], :quiet, "Suppress any warnings."],
verbose: [["-v", "--verbose"], :verbose, "Make some output more verbose."],
debug: [["-d", "--debug"], :debug, "Display any debugging information."],
force: [["-f", "--force"], :force, "Override warnings and enable potentially unsafe operations."],
}
end

def initialize(&block)
@parser = OptionParser.new
Homebrew.args = OpenStruct.new
# undefine tap to allow --tap argument
Homebrew.args.instance_eval { undef tap }
@constraints = []
@conflicts = []
@processed_options = []
@desc_line_length = 48
instance_eval(&block)
post_initialize
end

def post_initialize
@parser.on_tail("-h", "--help", "Show this message") do
puts generate_help_text
exit 0
end
end

def switch(*names, description: nil, env: nil, required_for: nil, depends_on: nil)
description = option_to_description(*names) if description.nil?
global_switch = names.first.is_a?(Symbol)
names, env = common_switch(*names) if global_switch
@parser.on(*names, description) do
names, env, description = common_switch(*names) if global_switch
description = option_to_description(*names) if description.nil?
process_option(*names, description)
@parser.on(*names, *wrap_option_desc(description)) do
enable_switch(*names)
end

Expand All @@ -34,9 +56,18 @@ def switch(*names, description: nil, env: nil, required_for: nil, depends_on: ni
enable_switch(*names) if !env.nil? && !ENV["HOMEBREW_#{env.to_s.upcase}"].nil?
end

def usage_banner(text)
@parser.banner = "#{text}\n"
end

def usage_banner_text
@parser.banner
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|
process_option(name, description)
@parser.on(name, OptionParser::REQUIRED_ARGUMENT, Array, *wrap_option_desc(description)) do |list|
Homebrew.args[option_to_name(name)] = list
end
end
Expand All @@ -49,7 +80,8 @@ def flag(name, description: nil, required_for: nil, depends_on: nil)
required = OptionParser::OPTIONAL_ARGUMENT
end
description = option_to_description(name) if description.nil?
@parser.on(name, description, required) do |option_value|
process_option(name, description)
@parser.on(name, *wrap_option_desc(description), required) do |option_value|
Homebrew.args[option_to_name(name)] = option_value
end

Expand Down Expand Up @@ -78,10 +110,26 @@ def option_to_description(*names)
names.map { |name| name.to_s.sub(/\A--?/, "").tr("-", " ") }.max
end

def parse(cmdline_args)
def summary
@parser.to_s
end

def parse(cmdline_args = ARGV)
remaining_args = @parser.parse(cmdline_args)
check_constraint_violations
Homebrew.args[:remaining] = remaining_args
@parser
end

def global_option?(name)
Homebrew::CLI::Parser.global_options.key?(name.to_sym)
end

def generate_help_text
@parser.to_s.sub(/^/, "#{Tty.bold}Usage: brew#{Tty.reset} ")
.gsub(/`(.*?)`/, "#{Tty.bold}\\1#{Tty.reset}")
.gsub(%r{<([^\s]+?://[^\s]+?)>}) { |url| Formatter.url(url) }
.gsub(/<(.*?)>/, "#{Tty.underline}\\1#{Tty.reset}")
end

private
Expand All @@ -94,19 +142,17 @@ def enable_switch(*names)

# These are common/global switches accessible throughout Homebrew
def common_switch(name)
case name
when :quiet then [["-q", "--quiet"], :quiet]
when :verbose then [["-v", "--verbose"], :verbose]
when :debug then [["-d", "--debug"], :debug]
when :force then [["-f", "--force"], :force]
else name
end
Homebrew::CLI::Parser.global_options.fetch(name, name)
end

def option_passed?(name)
Homebrew.args.respond_to?(name) || Homebrew.args.respond_to?("#{name}?")
end

def wrap_option_desc(desc)
Formatter.wrap(desc, @desc_line_length).split("\n")
end

def set_constraints(name, depends_on:, required_for:)
secondary = option_to_name(name)
unless required_for.nil?
Expand Down Expand Up @@ -160,6 +206,11 @@ def check_constraint_violations
check_conflicts
check_constraints
end

def process_option(*args)
option, = @parser.make_switch(args)
@processed_options << [option.short.first, option.long.first, option.arg, option.desc.first]
end
end

class OptionConstraintError < RuntimeError
Expand Down
59 changes: 44 additions & 15 deletions Library/Homebrew/dev-cmd/audit.rb
Original file line number Diff line number Diff line change
Expand Up @@ -52,22 +52,51 @@
module Homebrew
module_function

def audit
Homebrew::CLI::Parser.parse do
switch "--strict"
switch "--online"
switch "--new-formula"
switch "--fix"
switch "--display-cop-names"
switch "--display-filename"
switch "-D", "--audit-debug", description: "Activates debugging and profiling"
switch :verbose
switch :debug
comma_array "--only"
comma_array "--except"
comma_array "--only-cops"
comma_array "--except-cops"
def audit_args
Homebrew::CLI::Parser.new do
usage_banner <<~EOS
`audit` [<options>] <formulae>:

Check <formulae> for Homebrew coding style violations. This should be
run before submitting a new formula.
If no <formulae> are provided, all of them are checked.
EOS
switch "--strict",
description: "Run additional style checks, including Rubocop style checks."
switch "--online",
description: "Run additional slower style checks that require a network connection."
switch "--new-formula",
description: "Run various additional style checks to determine if a new formula is eligible "\
"for Homebrew. This should be used when creating new formula and implies "\
"`--strict` and `--online`."
switch "--fix",
description: "Fix style violations automatically using RuboCop's auto-correct feature."
switch "--display-cop-names",
description: "Include the RuboCop cop name for each violation in the output."
switch "--display-filename",
description: "Prefix everyline of output with name of the file or formula being audited, to "\
"make output easy to grep."
switch "-D", "--audit-debug",
description: "Activates debugging and profiling"
comma_array "--only",
description: "Passing `--only`=<method> will run only the methods named audit_<method>, `method` "\
"should be a comma-separated list."
comma_array "--except",
description: "Passing `--except`=<method> will run only the methods named audit_<method>, "\
"`method` should be a comma-separated list."
comma_array "--only-cops",
description: "Passing `--only-cops`=<cops> will check for violations of only the listed "\
"RuboCop cops. `cops` should be a comma-separated list of cop names."
comma_array "--except-cops",
description: "Passing `--except-cops`=<cops> will skip checking the listed RuboCop cops "\
"violations. `cops` should be a comma-separated list of cop names."
switch :verbose
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a difference between switch "--strict" and switch :verbose? If not, we should only use either strings or symbols.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Symbols indicate that its a global option, and CLI::Parser would pick up predefined attributes related to that option.

switch :debug
end
end

def audit
audit_args.parse

Homebrew.auditing = true
inject_dump_stats!(FormulaAuditor, /^audit_/) if args.audit_debug?
Expand Down
54 changes: 42 additions & 12 deletions Library/Homebrew/dev-cmd/bottle.rb
Original file line number Diff line number Diff line change
Expand Up @@ -69,21 +69,51 @@
module Homebrew
module_function

def bottle
Homebrew::CLI::Parser.parse do
switch "--merge"
switch "--skip-relocation"
switch "--force-core-tap"
switch "--no-rebuild"
switch "--keep-old"
switch "--write"
switch "--no-commit"
switch "--json"
switch "--or-later"
def bottle_args
Homebrew::CLI::Parser.new do
usage_banner <<~EOS
`bottle` [<options>] <formulae>:

Generate a bottle (binary package) from a formula installed with
`--build-bottle`.
If the formula specifies a rebuild version, it will be incremented in the
generated DSL. Passing `--keep-old` will attempt to keep it at its
original value, while `--no-rebuild` will remove it.
EOS
switch "--skip-relocation",
description: "Do not check if the bottle can be marked as relocatable."
switch "--or-later",
description: "Append `_or_later` to the bottle tag."
switch "--force-core-tap",
description: "Build a bottle even if <formula> is not in homebrew/core or any installed taps."
switch "--no-rebuild",
description: "If the formula specifies a rebuild version, it will be removed in the generated DSL."
switch "--keep-old",
description: "If the formula specifies a rebuild version, it will attempted to be kept in the "\
" generated DSL."
switch "--merge",
description: "Generate a bottle from a formula and print the new DSL merged into the "\
"existing formula."
switch "--write",
description: "Changes will be written to the formula file. A new commit will be generated unless "\
"`--no-commit` is passed."
switch "--no-commit",
description: "When passed with `--write`, a new commit will not generated while writing changes "\
"to the formula file.",
depends_on: "--write"
switch "--json",
description: "Write bottle information to a JSON file, which can be used as the argument for "\
"`--merge`."
flag "--root-url",
description: "Use the specified <URL> as the root of the bottle's URL instead of Homebrew's "\
"default."
switch :verbose
switch :debug
flag "--root-url"
end
end

def bottle
bottle_args.parse

return merge if args.merge?

Expand Down
79 changes: 59 additions & 20 deletions Library/Homebrew/dev-cmd/bump-formula-pr.rb
Original file line number Diff line number Diff line change
Expand Up @@ -47,29 +47,68 @@
module Homebrew
module_function

def bump_formula_pr
Homebrew::CLI::Parser.parse do
switch "--devel"
switch "-n", "--dry-run"
switch "--write"
switch "--audit"
switch "--strict"
switch "--no-browse"
switch :quiet
switch :force
switch :verbose
switch :debug

flag "--url="
flag "--revision="
flag "--tag=", required_for: "--revision="
flag "--sha256=", depends_on: "--url="
flag "--mirror="
flag "--version="
flag "--message="
def bump_formula_pr_args
Homebrew::CLI::Parser.new do
usage_banner <<~EOS
`bump-formula-pr` [<options>] <formula>:

Creates a pull request to update the formula with a new URL or a new tag.

If a <URL> is specified, the <sha-256> checksum of the new download must
also be specified. A best effort to determine the <sha-256> and <formula>
name will be made if either or both values are not supplied by the user.

If a <tag> is specified, the git commit <revision> corresponding to that
tag must also be specified.

Note that this command cannot be used to transition a formula from a
URL-and-sha256 style specification into a tag-and-revision style
specification, nor vice versa. It must use whichever style specification
the preexisting formula already uses.
EOS
switch "--devel",
description: "Bump the development rather than stable version. The development spec must already exist."
switch "-n", "--dry-run",
description: "Print what would be done rather than doing it."
switch "--write",
description: "When passed along with `--dry-run`, perform a not-so-dry run making the expected "\
"file modifications but not taking any git actions."
switch "--audit",
description: "Run `brew audit` before opening the PR."
switch "--strict",
description: "Run `brew audit --strict` before opening the PR."
switch "--no-browse",
description: "Output the pull request URL instead of opening in a browser"
flag "--url=",
description: "Provide new <URL> for the formula. If a <URL> is specified, the <sha-256> "\
"checksum of the new download must also be specified."
flag "--revision=",
description: "Specify the new git commit <revision> corresponding to a specified <tag>."
flag "--tag=",
required_for: "--revision=",
description: "Specify the new git commit <tag> for the formula."
flag "--sha256=",
depends_on: "--url=",
description: "Specify the <sha-256> checksum of new download."
flag "--mirror=",
description: "Use the provided <URL> as a mirror URL."
flag "--version=",
description: "Use the provided <version> to override the value parsed from the URL or tag. Note "\
"that `--version=0` can be used to delete an existing `version` override from a "\
"formula if it has become redundant."
flag "--message=",
description: "Append provided <message> to the default PR message."

switch :quiet
switch :force
switch :verbose
switch :debug
conflicts "--url", "--tag"
end
end

def bump_formula_pr
bump_formula_pr_args.parse

# As this command is simplifying user run commands then let's just use a
# user path, too.
Expand Down