From d89344b41de6f9a51840869300924bd59962620a Mon Sep 17 00:00:00 2001
From: hyuraku <32809703+hyuraku@users.noreply.github.com>
Date: Tue, 8 Nov 2022 20:33:30 +0900
Subject: [PATCH] add manpage.rb to generating homebrew manual
---
.../dev-cmd/generate-man-completions.rb | 223 +----------------
Library/Homebrew/manpages.rb | 228 ++++++++++++++++++
Library/Homebrew/manpages.rbi | 7 +
3 files changed, 237 insertions(+), 221 deletions(-)
create mode 100644 Library/Homebrew/manpages.rb
create mode 100644 Library/Homebrew/manpages.rbi
diff --git a/Library/Homebrew/dev-cmd/generate-man-completions.rb b/Library/Homebrew/dev-cmd/generate-man-completions.rb
index dfdd8dbebe..cacae0ff9e 100644
--- a/Library/Homebrew/dev-cmd/generate-man-completions.rb
+++ b/Library/Homebrew/dev-cmd/generate-man-completions.rb
@@ -2,20 +2,15 @@
# frozen_string_literal: true
require "formula"
-require "erb"
require "ostruct"
-require "cli/parser"
require "completions"
+require "manpages"
module Homebrew
extend T::Sig
module_function
- SOURCE_PATH = (HOMEBREW_LIBRARY_PATH/"manpages").freeze
- TARGET_MAN_PATH = (HOMEBREW_REPOSITORY/"manpages").freeze
- TARGET_DOC_PATH = (HOMEBREW_REPOSITORY/"docs").freeze
-
sig { returns(CLI::Parser) }
def generate_man_completions_args
Homebrew::CLI::Parser.new do
@@ -38,7 +33,7 @@ module Homebrew
odeprecated "brew generate-man-completions --fail-if-not-changed" if args.fail_if_not_changed?
Commands.rebuild_internal_commands_completion_list
- regenerate_man_pages(quiet: args.quiet?)
+ Manpages.regenerate_man_pages(quiet: args.quiet?)
Completions.update_shell_completions!
diff = system_command "git", args: [
@@ -50,218 +45,4 @@ module Homebrew
puts "Manpage and completions updated."
end
end
-
- # TODO: move this method and all called functions to manpages.rb
- def regenerate_man_pages(quiet:)
- Homebrew.install_bundler_gems!
-
- markup = build_man_page(quiet: quiet)
- convert_man_page(markup, TARGET_DOC_PATH/"Manpage.md")
- markup = I18n.transliterate(markup, locale: :en)
- convert_man_page(markup, TARGET_MAN_PATH/"brew.1")
- end
-
- def build_man_page(quiet:)
- template = (SOURCE_PATH/"brew.1.md.erb").read
- variables = OpenStruct.new
-
- variables[:commands] = generate_cmd_manpages(Commands.internal_commands_paths)
- variables[:developer_commands] = generate_cmd_manpages(Commands.internal_developer_commands_paths)
- variables[:official_external_commands] =
- generate_cmd_manpages(Commands.official_external_commands_paths(quiet: quiet))
- variables[:global_cask_options] = global_cask_options_manpage
- variables[:global_options] = global_options_manpage
- variables[:environment_variables] = env_vars_manpage
-
- readme = HOMEBREW_REPOSITORY/"README.md"
- variables[:lead] =
- readme.read[/(Homebrew's \[Project Leader.*\.)/, 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[:maintainers] =
- readme.read[/(Homebrew's other current maintainers .*\.)/, 1]
- .gsub(/\[([^\]]+)\]\([^)]+\)/, '\1')
- variables[:alumni] =
- readme.read[/(Former maintainers .*\.)/, 1]
- .gsub(/\[([^\]]+)\]\([^)]+\)/, '\1')
-
- ERB.new(template, trim_mode: ">").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 updating.
- # This avoids the only change being e.g. a new date.
- date = if 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!" if ronn_output.blank?
- case format_flag
- when "--markdown"
- ronn_output = ronn_output.gsub(%r{(.*?)}, "*`\\1`*")
- .gsub(/\n\n\n+/, "\n\n")
- .gsub(/^(- `[^`]+`):/, "\\1") # drop trailing colons from definition lists
- .gsub(/(?<=\n\n)([\[`].+):\n/, "\\1\n
") # replace colons with
on subcommands
- when "--roff"
- ronn_output = ronn_output.gsub(%r{(.*?)}, "\\fB\\1\\fR")
- .gsub(%r{(.*?)}, "\\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(cmd_paths)
- man_page_lines = []
-
- # preserve existing manpage order
- cmd_paths.sort_by(&method(:sort_key_for_path))
- .each do |cmd_path|
- cmd_man_page_lines = if (cmd_parser = CLI::Parser.from_cmd_path(cmd_path))
- next if cmd_parser.hide_from_man_page
-
- cmd_parser_manpage_lines(cmd_parser).join
- else
- cmd_comment_manpage_lines(cmd_path)
- end
-
- man_page_lines << cmd_man_page_lines
- end
-
- man_page_lines.compact.join("\n")
- 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, hidden|
- next if hidden
-
- if long.present?
- next if Homebrew::CLI::Parser.global_options.include?([short, long, desc])
- next if Homebrew::CLI::Parser.global_cask_options.any? do |_, option, description:, **|
- [long, "#{long}="].include?(option) && description == desc
- end
- end
-
- generate_option_doc(short, long, desc)
- end.compact
- 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..-2)
- unless line
- lines.last << "\n"
- next
- end
-
- # Omit the common global_options documented separately in the man page.
- next if line.match?(/--(debug|help|quiet|verbose) /)
-
- # Format one option or a comma-separated pair of short and long options.
- lines << line.gsub(/^ +(-+[a-z-]+), (-+[a-z-]+) +/, "* `\\1`, `\\2`:\n ")
- .gsub(/^ +(-+[a-z-]+) +/, "* `\\1`:\n ")
- end
- lines.last << "\n"
- lines
- end
-
- sig { returns(String) }
- def global_cask_options_manpage
- lines = ["These options are applicable to the `install`, `reinstall`, and `upgrade` " \
- "subcommands with the `--cask` flag.\n"]
- lines += Homebrew::CLI::Parser.global_cask_options.map do |_, long, description:, **|
- generate_option_doc(nil, long.chomp("="), description)
- end
- lines.join("\n")
- end
-
- sig { returns(String) }
- def global_options_manpage
- lines = ["These options are applicable across multiple subcommands.\n"]
- lines += Homebrew::CLI::Parser.global_options.map do |short, long, desc|
- generate_option_doc(short, long, desc)
- end
- lines.join("\n")
- end
-
- sig { returns(String) }
- def env_vars_manpage
- lines = Homebrew::EnvConfig::ENVS.flat_map do |env, hash|
- entry = "- `#{env}`:\n
#{hash[:description]}\n"
- default = hash[:default_text]
- default ||= "`#{hash[:default]}`." if hash[:default]
- entry += "\n\n *Default:* #{default}\n" if default
-
- entry
- end
- lines.join("\n")
- end
-
- def generate_option_doc(short, long, desc)
- comma = (short && long) ? ", " : ""
- <<~EOS
- * #{format_short_opt(short)}#{comma}#{format_long_opt(long)}:
- #{desc}
- EOS
- 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
diff --git a/Library/Homebrew/manpages.rb b/Library/Homebrew/manpages.rb
new file mode 100644
index 0000000000..9b222573ac
--- /dev/null
+++ b/Library/Homebrew/manpages.rb
@@ -0,0 +1,228 @@
+# typed: true
+# frozen_string_literal: true
+
+require "cli/parser"
+require "erb"
+
+SOURCE_PATH = (HOMEBREW_LIBRARY_PATH/"manpages").freeze
+TARGET_MAN_PATH = (HOMEBREW_REPOSITORY/"manpages").freeze
+TARGET_DOC_PATH = (HOMEBREW_REPOSITORY/"docs").freeze
+module Homebrew
+ # Helper functions for generating homebrew manual.
+ #
+ # @api private
+ module Manpages
+ extend T::Sig
+
+ module_function
+
+ def regenerate_man_pages(quiet:)
+ Homebrew.install_bundler_gems!
+
+ markup = build_man_page(quiet: quiet)
+ convert_man_page(markup, TARGET_DOC_PATH/"Manpage.md")
+ markup = I18n.transliterate(markup, locale: :en)
+ convert_man_page(markup, TARGET_MAN_PATH/"brew.1")
+ end
+
+ def build_man_page(quiet:)
+ template = (SOURCE_PATH/"brew.1.md.erb").read
+ variables = OpenStruct.new
+
+ variables[:commands] = generate_cmd_manpages(Commands.internal_commands_paths)
+ variables[:developer_commands] = generate_cmd_manpages(Commands.internal_developer_commands_paths)
+ variables[:official_external_commands] =
+ generate_cmd_manpages(Commands.official_external_commands_paths(quiet: quiet))
+ variables[:global_cask_options] = global_cask_options_manpage
+ variables[:global_options] = global_options_manpage
+ variables[:environment_variables] = env_vars_manpage
+
+ readme = HOMEBREW_REPOSITORY/"README.md"
+ variables[:lead] =
+ readme.read[/(Homebrew's \[Project Leader.*\.)/, 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[:maintainers] =
+ readme.read[/(Homebrew's other current maintainers .*\.)/, 1]
+ .gsub(/\[([^\]]+)\]\([^)]+\)/, '\1')
+ variables[:alumni] =
+ readme.read[/(Former maintainers .*\.)/, 1]
+ .gsub(/\[([^\]]+)\]\([^)]+\)/, '\1')
+
+ ERB.new(template, trim_mode: ">").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 updating.
+ # This avoids the only change being e.g. a new date.
+ date = if 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!" if ronn_output.blank?
+ ronn_output = case format_flag
+ when "--markdown"
+ onn_output.gsub(%r{(.*?)}, "*`\\1`*")
+ .gsub(/\n\n\n+/, "\n\n")
+ .gsub(/^(- `[^`]+`):/, "\\1") # drop trailing colons from definition lists
+ .gsub(/(?<=\n\n)([\[`].+):\n/, "\\1\n
") # replace colons with
on subcommands
+ when "--roff"
+ ronn_output.gsub(%r{(.*?)}, "\\fB\\1\\fR")
+ .gsub(%r{(.*?)}, "\\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(cmd_paths)
+ man_page_lines = []
+
+ # preserve existing manpage order
+ cmd_paths.sort_by(&method(:sort_key_for_path))
+ .each do |cmd_path|
+ cmd_man_page_lines = if (cmd_parser = Homebrew::CLI::Parser.from_cmd_path(cmd_path))
+ next if cmd_parser.hide_from_man_page
+
+ cmd_parser_manpage_lines(cmd_parser).join
+ else
+ cmd_comment_manpage_lines(cmd_path)
+ end
+
+ man_page_lines << cmd_man_page_lines
+ end
+
+ man_page_lines.compact.join("\n")
+ 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, hidden|
+ next if hidden
+
+ if long.present?
+ next if Homebrew::CLI::Parser.global_options.include?([short, long, desc])
+ next if Homebrew::CLI::Parser.global_cask_options.any? do |_, option, description:, **|
+ [long, "#{long}="].include?(option) && description == desc
+ end
+ end
+
+ generate_option_doc(short, long, desc)
+ end.compact
+ 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..-2)
+ unless line
+ lines.last << "\n"
+ next
+ end
+
+ # Omit the common global_options documented separately in the man page.
+ next if line.match?(/--(debug|help|quiet|verbose) /)
+
+ # Format one option or a comma-separated pair of short and long options.
+ lines << line.gsub(/^ +(-+[a-z-]+), (-+[a-z-]+) +/, "* `\\1`, `\\2`:\n ")
+ .gsub(/^ +(-+[a-z-]+) +/, "* `\\1`:\n ")
+ end
+ lines.last << "\n"
+ lines
+ end
+
+ sig { returns(String) }
+ def global_cask_options_manpage
+ lines = ["These options are applicable to the `install`, `reinstall`, and `upgrade` " \
+ "subcommands with the `--cask` flag.\n"]
+ lines += Homebrew::CLI::Parser.global_cask_options.map do |_, long, description:, **|
+ generate_option_doc(nil, long.chomp("="), description)
+ end
+ lines.join("\n")
+ end
+
+ sig { returns(String) }
+ def global_options_manpage
+ lines = ["These options are applicable across multiple subcommands.\n"]
+ lines += Homebrew::CLI::Parser.global_options.map do |short, long, desc|
+ generate_option_doc(short, long, desc)
+ end
+ lines.join("\n")
+ end
+
+ sig { returns(String) }
+ def env_vars_manpage
+ lines = Homebrew::EnvConfig::ENVS.flat_map do |env, hash|
+ entry = "- `#{env}`:\n
#{hash[:description]}\n"
+ default = hash[:default_text]
+ default ||= "`#{hash[:default]}`." if hash[:default]
+ entry += "\n\n *Default:* #{default}\n" if default
+
+ entry
+ end
+ lines.join("\n")
+ end
+
+ def format_opt(opt)
+ "`#{opt}`" unless opt.nil?
+ end
+
+ def generate_option_doc(short, long, desc)
+ comma = (short && long) ? ", " : ""
+ <<~EOS
+ * #{format_opt(short)}#{comma}#{format_opt(long)}:
+ #{desc}
+ EOS
+ end
+
+ def format_usage_banner(usage_banner)
+ usage_banner&.sub(/^(#: *\* )?/, "### ")
+ end
+ end
+end
diff --git a/Library/Homebrew/manpages.rbi b/Library/Homebrew/manpages.rbi
new file mode 100644
index 0000000000..d3765f73a3
--- /dev/null
+++ b/Library/Homebrew/manpages.rbi
@@ -0,0 +1,7 @@
+# typed: strict
+
+module Homebrew
+ module Manpages
+ include Kernel
+ end
+end