mirror of
https://github.com/Homebrew/brew.git
synced 2025-07-14 16:09:03 +08:00

Today the Homebrew maintainers elected a new PLC, TSC and project lead. Documentation is to follow detailing exactly the responsibilities for each of these roles but adjust these to reflect the current state until that point.
226 lines
7.3 KiB
Ruby
226 lines
7.3 KiB
Ruby
require "formula"
|
|
require "erb"
|
|
require "ostruct"
|
|
require "cli_parser"
|
|
# Require all commands
|
|
Dir.glob("#{HOMEBREW_LIBRARY_PATH}/{dev-,}cmd/*.rb").each { |cmd| require cmd }
|
|
|
|
module Homebrew
|
|
module_function
|
|
|
|
SOURCE_PATH = HOMEBREW_LIBRARY_PATH/"manpages"
|
|
TARGET_MAN_PATH = HOMEBREW_REPOSITORY/"manpages"
|
|
TARGET_DOC_PATH = HOMEBREW_REPOSITORY/"docs"
|
|
|
|
def man_args
|
|
Homebrew::CLI::Parser.new do
|
|
usage_banner <<~EOS
|
|
`man` [<options>]
|
|
|
|
Generate Homebrew's manpages.
|
|
EOS
|
|
switch "--fail-if-changed",
|
|
description: "Return a failing status code if changes are detected in the manpage outputs. This "\
|
|
"can be used for CI to be notified when the manpages are out of date. Additionally, "\
|
|
"the date used in new manpages will match those in the existing manpages (to allow "\
|
|
"comparison without factoring in the date)."
|
|
switch "--link",
|
|
description: "This is now done automatically by `brew update`."
|
|
end
|
|
end
|
|
|
|
def man
|
|
man_args.parse
|
|
|
|
raise UsageError unless ARGV.named.empty?
|
|
|
|
if args.link?
|
|
odie "`brew man --link` is now done automatically by `brew update`."
|
|
end
|
|
|
|
regenerate_man_pages
|
|
|
|
if system "git", "-C", HOMEBREW_REPOSITORY, "diff", "--quiet", "docs/Manpage.md", "manpages"
|
|
puts "No changes to manpage output detected."
|
|
elsif args.fail_if_changed?
|
|
Homebrew.failed = true
|
|
end
|
|
end
|
|
|
|
def regenerate_man_pages
|
|
Homebrew.install_bundler_gems!
|
|
|
|
markup = build_man_page
|
|
convert_man_page(markup, TARGET_DOC_PATH/"Manpage.md")
|
|
convert_man_page(markup, TARGET_MAN_PATH/"brew.1")
|
|
|
|
cask_markup = (SOURCE_PATH/"brew-cask.1.md").read
|
|
convert_man_page(cask_markup, TARGET_MAN_PATH/"brew-cask.1")
|
|
end
|
|
|
|
def build_man_page
|
|
template = (SOURCE_PATH/"brew.1.md.erb").read
|
|
variables = OpenStruct.new
|
|
|
|
variables[:commands] = generate_cmd_manpages("#{HOMEBREW_LIBRARY_PATH}/cmd/*.{rb,sh}")
|
|
variables[:developer_commands] = generate_cmd_manpages("#{HOMEBREW_LIBRARY_PATH}/dev-cmd/{*.rb,sh}")
|
|
variables[:global_options] = global_options_manpage
|
|
|
|
readme = HOMEBREW_REPOSITORY/"README.md"
|
|
variables[:lead] =
|
|
readme.read[/(Homebrew's project lead .*\.)/, 1]
|
|
.gsub(/\[([^\]]+)\]\([^)]+\)/, '\1')
|
|
variables[:plc] =
|
|
readme.read[/(Homebrew's project leadership committee .*\.)/, 1]
|
|
.gsub(/\[([^\]]+)\]\([^)]+\)/, '\1')
|
|
variables[:tsc] =
|
|
readme.read[/(Homebrew's technical steering committee .*\.)/, 1]
|
|
.gsub(/\[([^\]]+)\]\([^)]+\)/, '\1')
|
|
variables[:linux] =
|
|
readme.read[%r{(Homebrew/brew's Linux support \(and Linuxbrew\) maintainers are .*\.)}, 1]
|
|
.gsub(/\[([^\]]+)\]\([^)]+\)/, '\1')
|
|
variables[:maintainers] =
|
|
readme.read[/(Homebrew's other current maintainers .*\.)/, 1]
|
|
.gsub(/\[([^\]]+)\]\([^)]+\)/, '\1')
|
|
variables[:alumni] =
|
|
readme.read[/(Former maintainers .*\.)/, 1]
|
|
.gsub(/\[([^\]]+)\]\([^)]+\)/, '\1')
|
|
|
|
ERB.new(template, nil, ">").result(variables.instance_eval { binding })
|
|
end
|
|
|
|
def sort_key_for_path(path)
|
|
# Options after regular commands (`~` comes after `z` in ASCII table).
|
|
path.basename.to_s.sub(/\.(rb|sh)$/, "").sub(/^--/, "~~")
|
|
end
|
|
|
|
def convert_man_page(markup, target)
|
|
manual = target.basename(".1")
|
|
organisation = "Homebrew"
|
|
|
|
# Set the manpage date to the existing one if we're checking for changes.
|
|
# This avoids the only change being e.g. a new date.
|
|
date = if args.fail_if_changed? &&
|
|
target.extname == ".1" && target.exist?
|
|
/"(\d{1,2})" "([A-Z][a-z]+) (\d{4})" "#{organisation}" "#{manual}"/ =~ target.read
|
|
Date.parse("#{Regexp.last_match(1)} #{Regexp.last_match(2)} #{Regexp.last_match(3)}")
|
|
else
|
|
Date.today
|
|
end
|
|
date = date.strftime("%Y-%m-%d")
|
|
|
|
shared_args = %W[
|
|
--pipe
|
|
--organization=#{organisation}
|
|
--manual=#{target.basename(".1")}
|
|
--date=#{date}
|
|
]
|
|
|
|
format_flag, format_desc = target_path_to_format(target)
|
|
|
|
puts "Writing #{format_desc} to #{target}"
|
|
Utils.popen(["ronn", format_flag] + shared_args, "rb+") do |ronn|
|
|
ronn.write markup
|
|
ronn.close_write
|
|
ronn_output = ronn.read
|
|
odie "Got no output from ronn!" unless ronn_output
|
|
if format_flag == "--markdown"
|
|
ronn_output = ronn_output.gsub(%r{<var>(.*?)</var>}, "*`\\1`*")
|
|
.gsub(/\n\n\n+/, "\n\n")
|
|
elsif format_flag == "--roff"
|
|
ronn_output = ronn_output.gsub(%r{<code>(.*?)</code>}, "\\fB\\1\\fR")
|
|
.gsub(%r{<var>(.*?)</var>}, "\\fI\\1\\fR")
|
|
.gsub(/(^\[?\\fB.+): /, "\\1\n ")
|
|
end
|
|
target.atomic_write ronn_output
|
|
end
|
|
end
|
|
|
|
def target_path_to_format(target)
|
|
case target.basename
|
|
when /\.md$/ then ["--markdown", "markdown"]
|
|
when /\.\d$/ then ["--roff", "man page"]
|
|
else
|
|
odie "Failed to infer output format from '#{target.basename}'."
|
|
end
|
|
end
|
|
|
|
def generate_cmd_manpages(glob)
|
|
cmd_paths = Pathname.glob(glob).sort
|
|
man_page_lines = []
|
|
man_args = Homebrew.args
|
|
# preserve existing manpage order
|
|
cmd_paths.sort_by(&method(:sort_key_for_path))
|
|
.each do |cmd_path|
|
|
cmd_args_method_name = cmd_arg_parser(cmd_path)
|
|
|
|
cmd_man_page_lines = begin
|
|
cmd_parser = Homebrew.send(cmd_args_method_name)
|
|
next if cmd_parser.hide_from_man_page
|
|
cmd_parser_manpage_lines(cmd_parser).join
|
|
rescue NoMethodError => e
|
|
raise if e.name != cmd_args_method_name
|
|
nil
|
|
end
|
|
cmd_man_page_lines ||= cmd_comment_manpage_lines(cmd_path)
|
|
|
|
man_page_lines << cmd_man_page_lines
|
|
end
|
|
Homebrew.args = man_args
|
|
man_page_lines.compact.join("\n")
|
|
end
|
|
|
|
def cmd_arg_parser(cmd_path)
|
|
"#{cmd_path.basename.to_s.gsub(".rb", "").tr("-", "_")}_args".to_sym
|
|
end
|
|
|
|
def cmd_parser_manpage_lines(cmd_parser)
|
|
lines = [format_usage_banner(cmd_parser.usage_banner_text)]
|
|
lines += cmd_parser.processed_options.map do |short, long, _, desc|
|
|
next if !long.nil? && cmd_parser.global_option?(cmd_parser.option_to_name(long))
|
|
generate_option_doc(short, long, desc)
|
|
end.reject(&:blank?)
|
|
lines
|
|
end
|
|
|
|
def cmd_comment_manpage_lines(cmd_path)
|
|
comment_lines = cmd_path.read.lines.grep(/^#:/)
|
|
return if comment_lines.empty?
|
|
return if comment_lines.first.include?("@hide_from_man_page")
|
|
lines = [format_usage_banner(comment_lines.first).chomp]
|
|
comment_lines.slice(1..-1)
|
|
.each do |line|
|
|
line = line.slice(4..-1)
|
|
next unless line
|
|
lines << line.gsub(/^ +(-+[a-z-]+) */, "* `\\1`:\n ")
|
|
end
|
|
lines
|
|
end
|
|
|
|
def global_options_manpage
|
|
lines = ["These options are applicable across all sub-commands.\n"]
|
|
lines += Homebrew::CLI::Parser.global_options.values.map do |names, _, desc|
|
|
short, long = names
|
|
generate_option_doc(short, long, desc)
|
|
end
|
|
lines.join("\n")
|
|
end
|
|
|
|
def generate_option_doc(short, long, desc)
|
|
comma = (short && long) ? ", " : ""
|
|
"* #{format_short_opt(short)}" + comma + "#{format_long_opt(long)}:" + "\n " + desc + "\n"
|
|
end
|
|
|
|
def format_short_opt(opt)
|
|
"`#{opt}`" unless opt.nil?
|
|
end
|
|
|
|
def format_long_opt(opt)
|
|
"`#{opt}`" unless opt.nil?
|
|
end
|
|
|
|
def format_usage_banner(usage_banner)
|
|
usage_banner&.sub(/^(#: *\* )?/, "### ")
|
|
end
|
|
end
|