Replace ronn with Kramdown's converter

This commit is contained in:
Bo Anderson 2024-03-10 03:22:53 +00:00
parent cd1f040949
commit 754d580a91
No known key found for this signature in database
13 changed files with 8208 additions and 4307 deletions

5
.gitignore vendored
View File

@ -82,16 +82,15 @@
**/vendor/bundle/ruby/*/gems/erubi-*/
**/vendor/bundle/ruby/*/gems/hana-*/
**/vendor/bundle/ruby/*/gems/highline-*/
**/vendor/bundle/ruby/*/gems/hpricot-*/
**/vendor/bundle/ruby/*/gems/jaro_winkler-*/
**/vendor/bundle/ruby/*/gems/json-*/
**/vendor/bundle/ruby/*/gems/json_schemer-*/
**/vendor/bundle/ruby/*/gems/kramdown-*/
**/vendor/bundle/ruby/*/gems/language_server-protocol-*/
**/vendor/bundle/ruby/*/gems/method_source-*/
**/vendor/bundle/ruby/*/gems/mini_portile2-*/
**/vendor/bundle/ruby/*/gems/minitest-*/
**/vendor/bundle/ruby/*/gems/msgpack-*/
**/vendor/bundle/ruby/*/gems/mustache-*/
**/vendor/bundle/ruby/*/gems/netrc-*/
**/vendor/bundle/ruby/*/gems/ntlm-http-*/
**/vendor/bundle/ruby/*/gems/parallel-*/
@ -106,10 +105,8 @@
**/vendor/bundle/ruby/*/gems/racc-*/
**/vendor/bundle/ruby/*/gems/rainbow-*/
**/vendor/bundle/ruby/*/gems/rbi-*/
**/vendor/bundle/ruby/*/gems/rdiscount-*/
**/vendor/bundle/ruby/*/gems/regexp_parser-*/
**/vendor/bundle/ruby/*/gems/rexml-*/
**/vendor/bundle/ruby/*/gems/ronn-*/
**/vendor/bundle/ruby/*/gems/rspec-*/
**/vendor/bundle/ruby/*/gems/rspec-core-*/
**/vendor/bundle/ruby/*/gems/rspec-expectations-*/

View File

@ -28,7 +28,7 @@ group :livecheck, optional: true do
gem "ruby-progressbar", require: false
end
group :man, optional: true do
gem "ronn", require: false
gem "kramdown", require: false
end
group :pr_upload, optional: true do
gem "json_schemer", require: false

View File

@ -18,17 +18,17 @@ GEM
erubi (1.12.0)
hana (1.3.7)
highline (2.0.3)
hpricot (0.8.6)
json (2.7.1)
json_schemer (2.1.1)
hana (~> 1.3)
regexp_parser (~> 2.0)
simpleidn (~> 0.2)
kramdown (2.4.0)
rexml
language_server-protocol (3.17.0.3)
method_source (1.0.0)
minitest (5.22.2)
msgpack (1.7.2)
mustache (1.1.1)
netrc (0.11.0)
parallel (1.24.0)
parallel_tests (4.5.2)
@ -55,13 +55,8 @@ GEM
rbi (0.1.9)
prism (>= 0.18.0, < 0.25)
sorbet-runtime (>= 0.5.9204)
rdiscount (2.2.7.3)
regexp_parser (2.9.0)
rexml (3.2.6)
ronn (0.7.3)
hpricot (>= 0.8.2)
mustache (>= 0.7.0)
rdiscount (>= 1.5.8)
rspec (3.13.0)
rspec-core (~> 3.13.0)
rspec-expectations (~> 3.13.0)
@ -177,6 +172,7 @@ DEPENDENCIES
bootsnap
byebug
json_schemer
kramdown
method_source
minitest
parallel_tests
@ -185,7 +181,6 @@ DEPENDENCIES
plist
pry
rexml
ronn
rspec
rspec-github
rspec-its

View File

@ -3,6 +3,10 @@
require "cli/parser"
require "erb"
require "kramdown"
require "manpages/parser/ronn"
require "manpages/converter/kramdown"
require "manpages/converter/roff"
SOURCE_PATH = (HOMEBREW_LIBRARY_PATH/"manpages").freeze
TARGET_MAN_PATH = (HOMEBREW_REPOSITORY/"manpages").freeze
@ -31,8 +35,16 @@ module Homebrew
Homebrew.install_bundler_gems!(groups: ["man"])
markup = build_man_page(quiet:)
convert_man_page(markup, TARGET_DOC_PATH/"Manpage.md")
convert_man_page(markup, TARGET_MAN_PATH/"brew.1")
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)
end
def self.build_man_page(quiet:)
@ -65,59 +77,6 @@ module Homebrew
path.basename.to_s.sub(/\.(rb|sh)$/, "").sub(/^--/, "~~")
end
def self.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"
ronn_output.gsub(%r{<var>(.*?)</var>}, "*`\\1`*")
.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
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
def self.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 self.generate_cmd_manpages(cmd_paths)
man_page_lines = []
@ -129,8 +88,10 @@ module Homebrew
cmd_parser_manpage_lines(cmd_parser).join
else
cmd_comment_manpage_lines(cmd_path)
cmd_comment_manpage_lines(cmd_path)&.join("\n")
end
# Convert subcommands to definition lists
cmd_man_page_lines&.gsub!(/(?<=\n\n)([\\?\[`].+):\n/, "\\1\n\n: ")
man_page_lines << cmd_man_page_lines
end
@ -173,8 +134,10 @@ module Homebrew
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 ")
line.gsub!(/^ +(-+[a-z-]+), (-+[a-z-]+) +(.*)$/, "`\\1`, `\\2`\n\n: \\3\n")
line.gsub!(/^ +(-+[a-z-]+) +(.*)$/, "`\\1`\n\n: \\2\n")
lines << line
end
lines.last << "\n"
lines
@ -202,7 +165,7 @@ module Homebrew
sig { returns(String) }
def self.env_vars_manpage
lines = Homebrew::EnvConfig::ENVS.flat_map do |env, hash|
entry = "- `#{env}`:\n <br>#{hash[:description]}\n"
entry = "`#{env}`\n\n: #{hash[:description]}\n"
default = hash[:default_text]
default ||= "`#{hash[:default]}`." if hash[:default]
entry += "\n\n *Default:* #{default}\n" if default
@ -219,13 +182,16 @@ module Homebrew
def self.generate_option_doc(short, long, desc)
comma = (short && long) ? ", " : ""
<<~EOS
* #{format_opt(short)}#{comma}#{format_opt(long)}:
#{desc}
#{format_opt(short)}#{comma}#{format_opt(long)}
: #{desc}
EOS
end
def self.format_usage_banner(usage_banner)
usage_banner&.sub(/^(#: *\* )?/, "### ")
&.gsub(/(?<!`)\[([^\[\]]*)\](?!`)/, "\\[\\1\\]") # escape [] character (except those in code spans)
end
end
end

View File

@ -20,7 +20,7 @@ brew(1) -- The Missing Package Manager for macOS (or Linux)
## SYNOPSIS
`brew` `--version`<br>
`brew` <command> [`--verbose`|`-v`] [<options>] [<formula>] ...
`brew` <command> \[`--verbose`\|`-v`\] \[<options>\] \[<formula>\] ...
## DESCRIPTION
@ -30,29 +30,53 @@ Linux distribution without requiring `sudo`.
## TERMINOLOGY
**formula**: Homebrew package definition that builds from upstream sources
**formula**
**cask**: Homebrew package definition that installs macOS native applications
: Homebrew package definition that builds from upstream sources
**prefix**: path in which Homebrew is installed, e.g. `/usr/local`
**cask**
**keg**: installation destination directory of a given **formula** version, e.g. `/usr/local/Cellar/foo/0.1`
: Homebrew package definition that installs macOS native applications
**rack**: directory containing one or more versioned **kegs**, e.g. `/usr/local/Cellar/foo`
**prefix**
**keg-only**: a **formula** is *keg-only* if it is not symlinked into Homebrew's prefix
: path in which Homebrew is installed, e.g. `/usr/local`
**opt prefix**: a symlink to the active version of a **keg**, e.g. `/usr/local/opt/foo`
**keg**
**Cellar**: directory containing one or more named **racks**, e.g. `/usr/local/Cellar`
: installation destination directory of a given **formula** version, e.g. `/usr/local/Cellar/foo/0.1`
**Caskroom**: directory containing one or more named **casks**, e.g. `/usr/local/Caskroom`
**rack**
**external command**: `brew` subcommand defined outside of the Homebrew/brew GitHub repository
: directory containing one or more versioned **kegs**, e.g. `/usr/local/Cellar/foo`
**tap**: directory (and usually Git repository) of **formulae**, **casks** and/or **external commands**
**keg-only**
**bottle**: pre-built **keg** poured into a **rack** of the **Cellar** instead of building from upstream sources
: a **formula** is *keg-only* if it is not symlinked into Homebrew's prefix
**opt prefix**
: a symlink to the active version of a **keg**, e.g. `/usr/local/opt/foo`
**Cellar**
: directory containing one or more named **racks**, e.g. `/usr/local/Cellar`
**Caskroom**
: directory containing one or more named **casks**, e.g. `/usr/local/Caskroom`
**external command**
: `brew` subcommand defined outside of the Homebrew/brew GitHub repository
**tap**
: directory (and usually Git repository) of **formulae**, **casks** and/or **external commands**
**bottle**
: pre-built **keg** poured into a **rack** of the **Cellar** instead of building from upstream sources
## ESSENTIAL COMMANDS
@ -79,11 +103,11 @@ Uninstall <formula>.
List all installed formulae.
### `search` [<text>|`/`<text>`/`]
### `search` \[<text>\|`/`<text>`/`\]
Perform a substring search of cask tokens and formula names for <text>. If
<text> is flanked by slashes, it is interpreted as a regular expression.
The search for <text> is extended online to `homebrew/core` and `homebrew/cask`.
<text> is flanked by slashes, it is interpreted as a regular expression. The
search for <text> is extended online to `homebrew/core` and `homebrew/cask`.
If no search term is provided, all locally available formulae are listed.
## COMMANDS
@ -110,8 +134,8 @@ If no search term is provided, all locally available formulae are listed.
Homebrew, like `git`(1), supports external commands. These are executable
scripts that reside somewhere in the `PATH`, named `brew-`<cmdname> or
`brew-`<cmdname>`.rb`, which can be invoked like `brew` <cmdname>. This allows
you to create your own commands without modifying Homebrew's internals.
`brew-`<cmdname>`.rb`, which can be invoked like `brew` <cmdname>. This
allows you to create your own commands without modifying Homebrew's internals.
Instructions for creating your own commands can be found in the docs:
<https://docs.brew.sh/External-Commands>
@ -206,11 +230,14 @@ Homebrew API: <https://rubydoc.brew.sh>
See our issues on GitHub:
* **Homebrew/brew**:
<br><https://github.com/Homebrew/brew/issues>
**Homebrew/brew**
* **Homebrew/homebrew-core**:
<br><https://github.com/Homebrew/homebrew-core/issues>
: <https://github.com/Homebrew/brew/issues>
* **Homebrew/homebrew-cask**:
<br><https://github.com/Homebrew/homebrew-cask/issues>
**Homebrew/homebrew-core**
: <https://github.com/Homebrew/homebrew-core/issues>
**Homebrew/homebrew-cask**
: <https://github.com/Homebrew/homebrew-cask/issues>

View File

@ -0,0 +1,33 @@
# typed: true
# frozen_string_literal: true
require "kramdown/converter/kramdown"
module Homebrew
module Manpages
module Converter
# Converts our Kramdown-like input to pure Kramdown.
#
# @api private
class Kramdown < ::Kramdown::Converter::Kramdown
def initialize(root, options)
super(root, options.merge(line_width: 80))
end
def convert_variable(element, _options)
"*`#{element.value}`*"
end
def convert_a(element, options)
text = inner(element, options)
if element.attr["href"] == text
# Don't duplicate the URL if the link text is the same as the URL.
"<#{text}>"
else
super(element, options)
end
end
end
end
end
end

View File

@ -0,0 +1,56 @@
# typed: true
# frozen_string_literal: true
require "kramdown/converter/man"
module Homebrew
module Manpages
module Converter
# Converts our Kramdown-like input to roff.
#
# @api private
class Roff < ::Kramdown::Converter::Man
# Override that adds Homebrew metadata for the top level header
# and doesn't escape the text inside subheaders.
def convert_header(element, options)
if element.options[:level] == 1
element.attr["data-date"] = Date.today.strftime("%B %Y")
element.attr["data-extra"] = "Homebrew"
return super
end
result = +""
inner(element, options.merge(result:))
result.gsub!(" [", ' \fR[') # make args not bold
options[:result] << if element.options[:level] == 2
macro("SH", quote(result))
else
macro("SS", quote(result))
end
end
def convert_variable(element, options)
options[:result] << "\\fI#{escape(element.value)}\\fP"
end
def convert_a(element, options)
if element.attr["href"].chr == "#"
# Hide internal links - just make them italicised
convert_em(element, options)
else
super
# Remove the space after links if the next character is not a space
if options[:result].end_with?(".UE\n") &&
(next_element = options[:next]) &&
next_element.type == :text &&
next_element.value.chr.present? # i.e. not a space character
options[:result].chomp!
options[:result] << " "
end
end
end
end
end
end
end

View File

@ -0,0 +1,43 @@
# typed: true
# frozen_string_literal: true
require "kramdown/parser/kramdown"
module Homebrew
module Manpages
module Parser
# Kramdown parser with compatiblity for ronn variable syntax.
#
# @api private
class Ronn < ::Kramdown::Parser::Kramdown
def initialize(*)
super
# Disable HTML parsing and replace it with variable parsing.
# Also disable table parsing too because it depends on HTML parsing
# and existing command descriptions may get misinterpreted as tables.
# Typographic symbols is disabled as it detects `--` as en-dash.
@block_parsers.delete(:block_html)
@block_parsers.delete(:table)
@span_parsers.delete(:span_html)
@span_parsers.delete(:typographic_syms)
@span_parsers << :variable
end
# HTML-like tags denote variables instead, except <br>.
VARIABLE_REGEX = /<([\w\-\|]+)>/
def parse_variable
start_line_number = @src.current_line_number
@src.scan(VARIABLE_REGEX)
variable = @src[1]
if variable == "br"
@src.skip(/\n/)
@tree.children << Element.new(:br, nil, nil, location: start_line_number)
else
@tree.children << Element.new(:variable, variable, nil, location: start_line_number)
end
end
define_parser(:variable, VARIABLE_REGEX, "<")
end
end
end
end

File diff suppressed because it is too large Load Diff

View File

@ -13,16 +13,12 @@ gem:
- docile
- hana
- highline
- hpricot
- language_server-protocol
- mustache
- netrc
- parallel
- public_suffix
- racc
- rdiscount
- rexml
- ronn
- rspec-github
- rspec-mocks
- rspec-retry

View File

@ -45,8 +45,6 @@ $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/elftools-1.3.0/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/erubi-1.12.0/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/hana-1.3.7/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/extensions/arm64-darwin-20/#{Gem.extension_api_version}/hpricot-0.8.6")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/hpricot-0.8.6/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/extensions/arm64-darwin-20/#{Gem.extension_api_version}/json-2.7.1")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/json-2.7.1/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/regexp_parser-2.9.0/lib")
@ -55,10 +53,11 @@ $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/unf-0.1.4/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/simpleidn-0.2.1/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/json_schemer-2.1.1/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rexml-3.2.6/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/kramdown-2.4.0/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/language_server-protocol-3.17.0.3/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/method_source-1.0.0/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/minitest-5.22.2/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/mustache-1.1.1/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/netrc-0.11.0/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/parallel-1.24.0/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/parallel_tests-4.5.2/lib")
@ -75,10 +74,6 @@ $:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/prism-0.24.0/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/pry-0.14.2/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rbi-0.1.9/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/extensions/arm64-darwin-20/#{Gem.extension_api_version}/rdiscount-2.2.7.3")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rdiscount-2.2.7.3/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rexml-3.2.6/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/ronn-0.7.3/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rspec-support-3.13.1/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rspec-core-3.13.0/lib")
$:.unshift File.expand_path("#{__dir__}/../#{RUBY_ENGINE}/#{Gem.ruby_api_version}/gems/rspec-expectations-3.13.0/lib")

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff