| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  | # typed: true | 
					
						
							|  |  |  | # frozen_string_literal: true | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  | require "cli/parser" | 
					
						
							|  |  |  | require "erb" | 
					
						
							| 
									
										
										
										
											2023-03-08 09:50:04 -08:00
										 |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  | 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 | 
					
						
							| 
									
										
										
										
											2023-02-28 12:24:22 -08:00
										 |  |  |     Variables = Struct.new( | 
					
						
							|  |  |  |       :alumni, | 
					
						
							|  |  |  |       :commands, | 
					
						
							|  |  |  |       :developer_commands, | 
					
						
							|  |  |  |       :environment_variables, | 
					
						
							|  |  |  |       :global_cask_options, | 
					
						
							|  |  |  |       :global_options, | 
					
						
							|  |  |  |       :lead, | 
					
						
							|  |  |  |       :maintainers, | 
					
						
							|  |  |  |       :official_external_commands, | 
					
						
							|  |  |  |       :plc, | 
					
						
							|  |  |  |       :tsc, | 
					
						
							|  |  |  |       keyword_init: true, | 
					
						
							|  |  |  |     ) | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-17 10:37:59 -07:00
										 |  |  |     def self.regenerate_man_pages(quiet:) | 
					
						
							| 
									
										
										
										
											2023-09-04 21:52:51 +01:00
										 |  |  |       Homebrew.install_bundler_gems!(groups: ["man"]) | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |       markup = build_man_page(quiet: quiet) | 
					
						
							|  |  |  |       convert_man_page(markup, TARGET_DOC_PATH/"Manpage.md") | 
					
						
							|  |  |  |       convert_man_page(markup, TARGET_MAN_PATH/"brew.1") | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-17 10:37:59 -07:00
										 |  |  |     def self.build_man_page(quiet:) | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |       template = (SOURCE_PATH/"brew.1.md.erb").read | 
					
						
							|  |  |  |       readme = HOMEBREW_REPOSITORY/"README.md" | 
					
						
							| 
									
										
										
										
											2023-02-28 12:24:22 -08:00
										 |  |  |       variables = Variables.new( | 
					
						
							|  |  |  |         commands:                   generate_cmd_manpages(Commands.internal_commands_paths), | 
					
						
							|  |  |  |         developer_commands:         generate_cmd_manpages(Commands.internal_developer_commands_paths), | 
					
						
							|  |  |  |         official_external_commands: generate_cmd_manpages(Commands.official_external_commands_paths(quiet: quiet)), | 
					
						
							|  |  |  |         global_cask_options:        global_cask_options_manpage, | 
					
						
							|  |  |  |         global_options:             global_options_manpage, | 
					
						
							|  |  |  |         environment_variables:      env_vars_manpage, | 
					
						
							|  |  |  |         lead:                       readme.read[/(Homebrew's \[Project Leader.*\.)/, 1] | 
					
						
							|  |  |  |                                       .gsub(/\[([^\]]+)\]\([^)]+\)/, '\1'), | 
					
						
							|  |  |  |         plc:                        readme.read[/(Homebrew's \[Project Leadership Committee.*\.)/, 1] | 
					
						
							|  |  |  |                                       .gsub(/\[([^\]]+)\]\([^)]+\)/, '\1'), | 
					
						
							|  |  |  |         tsc:                        readme.read[/(Homebrew's \[Technical Steering Committee.*\.)/, 1] | 
					
						
							|  |  |  |                                       .gsub(/\[([^\]]+)\]\([^)]+\)/, '\1'), | 
					
						
							|  |  |  |         maintainers:                readme.read[/(Homebrew's maintainers .*\.)/, 1] | 
					
						
							|  |  |  |                                       .gsub(/\[([^\]]+)\]\([^)]+\)/, '\1'), | 
					
						
							|  |  |  |         alumni:                     readme.read[/(Former maintainers .*\.)/, 1] | 
					
						
							|  |  |  |                                       .gsub(/\[([^\]]+)\]\([^)]+\)/, '\1'), | 
					
						
							|  |  |  |       ) | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |       ERB.new(template, trim_mode: ">").result(variables.instance_eval { binding }) | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-17 10:37:59 -07:00
										 |  |  |     def self.sort_key_for_path(path) | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |       # Options after regular commands (`~` comes after `z` in ASCII table). | 
					
						
							|  |  |  |       path.basename.to_s.sub(/\.(rb|sh)$/, "").sub(/^--/, "~~") | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-17 10:37:59 -07:00
										 |  |  |     def self.convert_man_page(markup, target) | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |       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" | 
					
						
							| 
									
										
										
										
											2022-11-08 21:03:20 +09:00
										 |  |  |           ronn_output.gsub(%r{<var>(.*?)</var>}, "*`\\1`*") | 
					
						
							| 
									
										
										
										
											2022-11-08 21:11:10 +09:00
										 |  |  |                      .gsub(/\n\n\n+/, "\n\n") | 
					
						
							|  |  |  |                      .gsub(/^(- `[^`]+`):/, "\\1") # drop trailing colons from definition lists | 
					
						
							|  |  |  |                      .gsub(/(?<=\n\n)([\[`].+):\n/, "\\1\n<br>") # replace colons with <br> on subcommands | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |         when "--roff" | 
					
						
							|  |  |  |           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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-17 10:37:59 -07:00
										 |  |  |     def self.target_path_to_format(target) | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |       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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-17 10:37:59 -07:00
										 |  |  |     def self.generate_cmd_manpages(cmd_paths) | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |       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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-17 10:37:59 -07:00
										 |  |  |     def self.cmd_parser_manpage_lines(cmd_parser) | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |       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]) | 
					
						
							| 
									
										
										
										
											2023-11-05 00:54:59 +00:00
										 |  |  |           next if Homebrew::CLI::Parser.global_cask_options.any? do |_, option, kwargs| | 
					
						
							|  |  |  |                     [long, "#{long}="].include?(option) && kwargs.fetch(:description) == desc | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |                   end | 
					
						
							|  |  |  |         end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         generate_option_doc(short, long, desc) | 
					
						
							|  |  |  |       end.compact | 
					
						
							|  |  |  |       lines | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-17 10:37:59 -07:00
										 |  |  |     def self.cmd_comment_manpage_lines(cmd_path) | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |       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) } | 
					
						
							| 
									
										
										
										
											2023-04-17 10:37:59 -07:00
										 |  |  |     def self.global_cask_options_manpage | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |       lines = ["These options are applicable to the `install`, `reinstall`, and `upgrade` " \ | 
					
						
							| 
									
										
										
										
											2023-09-08 14:46:15 -04:00
										 |  |  |                "subcommands with the `--cask` switch.\n"] | 
					
						
							| 
									
										
										
										
											2023-11-05 00:54:59 +00:00
										 |  |  |       lines += Homebrew::CLI::Parser.global_cask_options.map do |_, long, kwargs| | 
					
						
							|  |  |  |         generate_option_doc(nil, long.chomp("="), kwargs.fetch(:description)) | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |       end | 
					
						
							|  |  |  |       lines.join("\n") | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sig { returns(String) } | 
					
						
							| 
									
										
										
										
											2023-04-17 10:37:59 -07:00
										 |  |  |     def self.global_options_manpage | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |       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) } | 
					
						
							| 
									
										
										
										
											2023-04-17 10:37:59 -07:00
										 |  |  |     def self.env_vars_manpage | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |       lines = Homebrew::EnvConfig::ENVS.flat_map do |env, hash| | 
					
						
							|  |  |  |         entry = "- `#{env}`:\n  <br>#{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 | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-17 10:37:59 -07:00
										 |  |  |     def self.format_opt(opt) | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |       "`#{opt}`" unless opt.nil? | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-17 10:37:59 -07:00
										 |  |  |     def self.generate_option_doc(short, long, desc) | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |       comma = (short && long) ? ", " : "" | 
					
						
							|  |  |  |       <<~EOS | 
					
						
							|  |  |  |         * #{format_opt(short)}#{comma}#{format_opt(long)}: | 
					
						
							|  |  |  |           #{desc} | 
					
						
							|  |  |  |       EOS | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2023-04-17 10:37:59 -07:00
										 |  |  |     def self.format_usage_banner(usage_banner) | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |       usage_banner&.sub(/^(#: *\* )?/, "### ") | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end |