Skip to content

Commit 9f22837

Browse files
committed
manpages: prepare for new manpage format
This commit adds a upcoming manpage format to the AsciiDoc backend. The new format changes are: * The synopsis is now a section with a dedicated style. This "synopsis" style allows to automatically format the keywords as monospaced and <placeholders> as italic. * the backticks are now used to format synopsis-like syntax in inline elements. The parsing of synopsis is done with a new AsciiDoc extension that makes use of the PEG parser parslet. All the asciidoc manpages sources are processed with this extension. It may upset the formatting for older manpages, making it not consistent across a page, but this will be a mild side effect, as this was not really consistent before. Signed-off-by: Jean-Noël Avila <[email protected]>
1 parent e5ee9b4 commit 9f22837

File tree

4 files changed

+129
-1
lines changed

4 files changed

+129
-1
lines changed

Gemfile

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,5 +8,5 @@ gem "rss"
88
gem "asciidoctor", "~> 2.0.0"
99
gem "nokogiri"
1010
gem "diffy"
11-
1211
gem "base64", "~> 0.2.0"
12+
gem "parslet"

Gemfile.lock

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ GEM
3737
octokit (9.2.0)
3838
faraday (>= 1, < 3)
3939
sawyer (~> 0.9)
40+
parslet (2.0.0)
4041
public_suffix (6.0.1)
4142
racc (1.8.1)
4243
rexml (3.4.1)
@@ -64,6 +65,7 @@ DEPENDENCIES
6465
faraday-retry
6566
nokogiri
6667
octokit
68+
parslet
6769
rss
6870

6971
CHECKSUMS
@@ -86,6 +88,7 @@ CHECKSUMS
8688
nokogiri (1.18.8-x86_64-linux-gnu) sha256=4a747875db873d18a2985ee2c320a6070c4a414ad629da625fbc58d1a20e5ecc
8789
nokogiri (1.18.8-x86_64-linux-musl) sha256=ddd735fba49475a395b9ea793bb6474e3a3125b89960339604d08a5397de1165
8890
octokit (9.2.0) sha256=4fa47ff35ce654127edf2c836ab9269bcc8829f5542dc1e86871f697ce7f4316
91+
parslet (2.0.0) sha256=d45130695d39b43d7e6a91f4d2ec66b388a8d822bae38de9b4de9a5fbde1f606
8992
public_suffix (6.0.1) sha256=61d44e1cab5cbbbe5b31068481cf16976dd0dc1b6b07bd95617ef8c5e3e00c6f
9093
racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
9194
rexml (3.4.1) sha256=c74527a9a0a04b4ec31dbe0dc4ed6004b960af943d8db42e539edde3a871abca

script/asciidoctor-extensions.rb

Lines changed: 124 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
require 'asciidoctor'
2+
require 'asciidoctor/extensions'
3+
require 'asciidoctor/converter/html5'
4+
require 'parslet'
5+
# for parslet, see https://kschiess.github.io/parslet/parser.html
6+
7+
module Git
8+
module Documentation
9+
class AdocSynopsisQuote < Parslet::Parser
10+
# parse a string like "git add -p [--root=<path>]" as series of
11+
# tokens keywords, grammar signs and placeholders where
12+
# placeholders are UTF-8 words separated by '-', enclosed in '<'
13+
# and '>'. The >> indicates a "simple sequence", for example
14+
# str('...') >> match('\]|$').present? means "first match three
15+
# periods, then ensure that they are either followed by a
16+
# closing bracket or they are at the end.
17+
rule(:space) { match('[\s\t\n ]').repeat(1) }
18+
rule(:space?) { space.maybe }
19+
rule(:keyword) { match('[-a-zA-Z0-9:+=~@,\./_\^\$\'"\*%!{}#]').repeat(1) }
20+
rule(:placeholder) { str('<') >> match('[[:word:]]|-').repeat(1) >> str('>') }
21+
rule(:opt_or_alt) { match('[\[\] |()]') >> space? }
22+
rule(:ellipsis) { str('...') >> match('\]|$').present? }
23+
rule(:grammar) { opt_or_alt | ellipsis }
24+
rule(:ignore) { match('[\'`]') }
25+
26+
rule(:token) do
27+
grammar.as(:grammar) | placeholder.as(:placeholder) | space.as(:space) |
28+
ignore.as(:ignore) | keyword.as(:keyword)
29+
end
30+
rule(:tokens) { token.repeat(1) }
31+
root(:tokens)
32+
end
33+
34+
class EscapedSynopsisQuote < AdocSynopsisQuote
35+
rule(:placeholder) { str('&lt;') >> match('[[:word:]]|-').repeat(1) >> str('&gt;') }
36+
end
37+
38+
class SynopsisQuoteBase < Parslet::Transform
39+
rule(grammar: simple(:grammar)) { grammar.to_s }
40+
rule(space: simple(:space)) { space.to_s }
41+
rule(ignore: simple(:ignore)) { '' }
42+
end
43+
44+
class SynopsisQuoteToAdoc < SynopsisQuoteBase
45+
rule(keyword: simple(:keyword)) { "{empty}`#{keyword}`{empty}" }
46+
rule(placeholder: simple(:placeholder)) { "__#{placeholder}__" }
47+
end
48+
49+
class SynopsisQuoteToHtml5 < SynopsisQuoteBase
50+
rule(keyword: simple(:keyword)) { "<code>#{keyword}</code>" }
51+
rule(placeholder: simple(:placeholder)) { "<em>#{placeholder}</em>" }
52+
end
53+
54+
class SynopsisConverter
55+
def convert(parslet_parser, parslet_transform, reader, logger = nil)
56+
reader.lines.map do |l|
57+
parslet_transform.apply(parslet_parser.parse(l)).join
58+
end.join("\n")
59+
rescue Parslet::ParseFailed
60+
logger&.info "synopsis parsing failed for '#{reader.lines.join(' ')}'"
61+
reader.lines.map do |l|
62+
parslet_transform.apply(placeholder: l)
63+
end.join("\n")
64+
end
65+
end
66+
67+
class SynopsisBlock < Asciidoctor::Extensions::BlockProcessor
68+
use_dsl
69+
named :synopsis
70+
parse_content_as :simple
71+
72+
def process(parent, reader, attrs)
73+
outlines = SynopsisConverter.new.convert(
74+
AdocSynopsisQuote.new,
75+
SynopsisQuoteToAdoc.new,
76+
reader,
77+
parent.document.logger
78+
)
79+
create_block parent, :verse, outlines, attrs
80+
end
81+
end
82+
83+
# register a html5 converter that takes in charge
84+
# to convert monospaced text into Git style synopsis
85+
class GitHTMLConverter < Asciidoctor::Converter::Html5Converter
86+
extend Asciidoctor::Converter::Config
87+
register_for 'html5'
88+
89+
def convert_inline_quoted(node)
90+
if node.type == :monospaced
91+
t = SynopsisConverter.new.convert(
92+
EscapedSynopsisQuote.new,
93+
SynopsisQuoteToHtml5.new,
94+
node.text,
95+
node.document.logger
96+
)
97+
"<span class='synopsis'>#{t}</span>"
98+
else
99+
open, close, tag = QUOTE_TAGS[node.type]
100+
if node.id
101+
class_attr = node.role ? %( class="#{node.role}") : ''
102+
if tag
103+
%(#{open.chop} id="#{node.id}"#{class_attr}>#{node.text}#{close})
104+
else
105+
%(<span id="#{node.id}"#{class_attr}>#{open}#{node.text}#{close}</span>)
106+
end
107+
elsif node.role
108+
if tag
109+
%(#{open.chop} class="#{node.role}">#{node.text}#{close})
110+
else
111+
%(<span class="#{node.role}">#{open}#{node.text}#{close}</span>)
112+
end
113+
else
114+
%(#{open}#{node.text}#{close})
115+
end
116+
end
117+
end
118+
end
119+
end
120+
end
121+
122+
Asciidoctor::Extensions.register do
123+
block Git::Documentation::SynopsisBlock
124+
end

script/update-docs.rb

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
require 'yaml'
1010
require 'diffy'
1111
require_relative "version"
12+
require_relative 'asciidoctor-extensions'
1213

1314
SITE_ROOT = File.join(File.expand_path(File.dirname(__FILE__)), '../')
1415
DOCS_INDEX_FILE = "#{SITE_ROOT}external/docs/content/docs/_index.html"

0 commit comments

Comments
 (0)