| 
									
										
										
										
											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. | 
					
						
							|  |  |  |   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:) | 
					
						
							| 
									
										
										
										
											2024-04-05 12:14:28 -04:00
										 |  |  |       require "kramdown" | 
					
						
							|  |  |  |       require "manpages/parser/ronn" | 
					
						
							|  |  |  |       require "manpages/converter/kramdown" | 
					
						
							|  |  |  |       require "manpages/converter/roff" | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2024-03-07 16:20:20 +00:00
										 |  |  |       markup = build_man_page(quiet:) | 
					
						
							| 
									
										
										
										
											2024-03-10 03:22:53 +00:00
										 |  |  |       root, warnings = Parser::Ronn.parse(markup) | 
					
						
							|  |  |  |       $stderr.puts(warnings) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       roff, warnings = Converter::Kramdown.convert(root) | 
					
						
							|  |  |  |       $stderr.puts(warnings) | 
					
						
							|  |  |  |       File.write(TARGET_DOC_PATH/"Manpage.md", roff) | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       roff, warnings = Converter::Roff.convert(root) | 
					
						
							|  |  |  |       $stderr.puts(warnings) | 
					
						
							|  |  |  |       File.write(TARGET_MAN_PATH/"brew.1", roff) | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |     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), | 
					
						
							| 
									
										
										
										
											2024-03-07 16:20:20 +00:00
										 |  |  |         official_external_commands: generate_cmd_manpages(Commands.official_external_commands_paths(quiet:)), | 
					
						
							| 
									
										
										
										
											2023-02-28 12:24:22 -08:00
										 |  |  |         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.generate_cmd_manpages(cmd_paths) | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |       man_page_lines = [] | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |       # preserve existing manpage order | 
					
						
							| 
									
										
										
										
											2024-04-08 09:47:06 -07:00
										 |  |  |       cmd_paths.sort_by { sort_key_for_path(_1) } | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |                .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 | 
					
						
							| 
									
										
										
										
											2024-03-10 03:22:53 +00:00
										 |  |  |           cmd_comment_manpage_lines(cmd_path)&.join("\n") | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |         end | 
					
						
							| 
									
										
										
										
											2024-03-10 03:22:53 +00:00
										 |  |  |         # Convert subcommands to definition lists | 
					
						
							|  |  |  |         cmd_man_page_lines&.gsub!(/(?<=\n\n)([\\?\[`].+):\n/, "\\1\n\n: ") | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |         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)] | 
					
						
							| 
									
										
										
										
											2024-02-22 23:29:55 +00:00
										 |  |  |       lines += cmd_parser.processed_options.filter_map do |short, long, _, desc, hidden| | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |         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) | 
					
						
							| 
									
										
										
										
											2024-02-22 23:29:55 +00:00
										 |  |  |       end | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |       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. | 
					
						
							| 
									
										
										
										
											2024-03-10 03:22:53 +00:00
										 |  |  |         line.gsub!(/^ +(-+[a-z-]+), (-+[a-z-]+) +(.*)$/, "`\\1`, `\\2`\n\n: \\3\n") | 
					
						
							|  |  |  |         line.gsub!(/^ +(-+[a-z-]+) +(.*)$/, "`\\1`\n\n: \\2\n") | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         lines << line | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |       end | 
					
						
							|  |  |  |       lines.last << "\n" | 
					
						
							|  |  |  |       lines | 
					
						
							|  |  |  |     end | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |     sig { returns(String) } | 
					
						
							| 
									
										
										
										
											2023-04-17 10:37:59 -07:00
										 |  |  |     def self.global_cask_options_manpage | 
					
						
							| 
									
										
										
										
											2024-04-30 11:10:23 +02: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| | 
					
						
							| 
									
										
										
										
											2024-03-10 03:22:53 +00:00
										 |  |  |         entry = "`#{env}`\n\n: #{hash[:description]}\n" | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |         default = hash[:default_text] | 
					
						
							|  |  |  |         default ||= "`#{hash[:default]}`." if hash[:default] | 
					
						
							| 
									
										
										
										
											2024-03-10 03:22:53 +00:00
										 |  |  |         entry += "\n\n    *Default:* #{default}\n" if default | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  | 
 | 
					
						
							|  |  |  |         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 | 
					
						
							| 
									
										
										
										
											2024-03-10 03:22:53 +00:00
										 |  |  |         #{format_opt(short)}#{comma}#{format_opt(long)} | 
					
						
							|  |  |  | 
 | 
					
						
							|  |  |  |         : #{desc} | 
					
						
							|  |  |  | 
 | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |       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(/^(#: *\* )?/, "### ") | 
					
						
							| 
									
										
										
										
											2024-03-10 03:22:53 +00:00
										 |  |  |                   &.gsub(/(?<!`)\[([^\[\]]*)\](?!`)/, "\\[\\1\\]") # escape [] character (except those in code spans) | 
					
						
							| 
									
										
										
										
											2022-11-08 20:33:30 +09:00
										 |  |  |     end | 
					
						
							|  |  |  |   end | 
					
						
							|  |  |  | end |