-
Notifications
You must be signed in to change notification settings - Fork 2.5k
/
Copy pathmarkdown-preprocess
executable file
·184 lines (163 loc) · 7.32 KB
/
markdown-preprocess
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
#!/usr/bin/env python3
#
# markdown-preprocess - filter *.md.in files, convert to .md
#
"""
Simpleminded include mechanism for podman man pages.
"""
import filecmp
import glob
import os
import re
import sys
class Preprocessor():
"""
Doesn't really merit a whole OO approach, except we have a lot
of state variables to pass around, and self is a convenient
way to do that. Better than globals, anyway.
"""
def __init__(self):
self.infile = ''
self.pod_or_container = ''
self.used_by = {}
def process(self, infile:str):
"""
Main calling point: preprocesses one file
"""
self.infile = infile
# Some options are the same between containers and pods; determine
# which description to use from the name of the source man page.
self.pod_or_container = 'container'
if '-pod-' in infile or '-kube-' in infile:
self.pod_or_container = 'pod'
# foo.md.in -> foo.md -- but always write to a tmpfile
outfile = os.path.splitext(infile)[0]
outfile_tmp = outfile + '.tmp.' + str(os.getpid())
with open(infile, 'r', encoding='utf-8') as fh_in, open(outfile_tmp, 'w', encoding='utf-8', newline='\n') as fh_out:
for line in fh_in:
# '@@option foo' -> include file options/foo.md
if line.startswith('@@option '):
_, optionname = line.strip().split(" ")
optionfile = os.path.join("options", optionname + '.md')
self.track_optionfile(optionfile)
self.insert_file(fh_out, optionfile)
# '@@include relative-path/must-exist.md'
elif line.startswith('@@include '):
_, path = line.strip().split(" ")
self.insert_file(fh_out, path)
else:
fh_out.write(line)
os.chmod(outfile_tmp, 0o444)
os.rename(outfile_tmp, outfile)
def track_optionfile(self, optionfile: str):
"""
Keep track of which man pages use which option files
"""
if optionfile not in self.used_by:
self.used_by[optionfile] = []
self.used_by[optionfile].append(self.podman_subcommand('full'))
def rewrite_optionfiles(self):
"""
Rewrite all option files, such that they include header comments
cross-referencing all the man pages in which they're used.
"""
for optionfile in self.used_by:
tmpfile = optionfile + '.tmp.' + str(os.getpid())
with open(optionfile, 'r', encoding='utf-8') as fh_in, open(tmpfile, 'w', encoding='utf-8', newline='\n') as fh_out:
fh_out.write("####> This option file is used in:\n")
used_by = ', '.join(x for x in self.used_by[optionfile])
fh_out.write(f"####> podman {used_by}\n")
fh_out.write("####> If file is edited, make sure the changes\n")
fh_out.write("####> are applicable to all of those.\n")
for line in fh_in:
if not line.startswith('####>'):
fh_out.write(line)
# Compare files; only rewrite if the new one differs
if not filecmp.cmp(optionfile, tmpfile):
os.unlink(optionfile)
os.rename(tmpfile, optionfile)
else:
os.unlink(tmpfile)
def insert_file(self, fh_out, path: str):
"""
Reads one option file, writes it out to the given output filehandle
"""
# Comment intended to help someone viewing the .md file.
# Leading newline is important because if two lines are
# consecutive without a break, sphinx (but not go-md2man)
# treats them as one line and will unwantedly render the
# comment in its output.
fh_out.write("\n[//]: # (BEGIN included file " + path + ")\n")
with open(path, 'r', encoding='utf-8') as fh_included:
for opt_line in fh_included:
if opt_line.startswith('####>'):
continue
opt_line = self.replace_type(opt_line)
opt_line = opt_line.replace('<<subcommand>>', self.podman_subcommand())
opt_line = opt_line.replace('<<fullsubcommand>>', self.podman_subcommand('full'))
fh_out.write(opt_line)
fh_out.write("\n[//]: # (END included file " + path + ")\n")
def podman_subcommand(self, full=None) -> str:
"""
Returns the string form of the podman command, based on man page name;
e.g., 'foo bar' for podman-foo-bar.1.md.in
"""
subcommand = self.infile
# Special case: 'podman-pod-start' becomes just 'start'
if not full:
if subcommand.startswith("podman-pod-"):
subcommand = subcommand[len("podman-pod-"):]
if subcommand.startswith("podman-"):
subcommand = subcommand[len("podman-"):]
if subcommand.endswith(".1.md.in"):
subcommand = subcommand[:-len(".1.md.in")]
return subcommand.replace("-", " ")
def replace_type(self, line: str) -> str:
"""
Replace instances of '<<pod string|container string>>' with the
appropriate one based on whether this is a pod-related man page
or not.
"""
# Internal helper function: determines the desired half of the <a|b> string
def replwith(matchobj):
lhs, rhs = matchobj[0].split('|')
# Strip off '<<' and '>>'
lhs = lhs[2:]
rhs = rhs[:len(rhs)-2]
# Check both sides for 'pod' followed by (non-"m" or end-of-string).
# The non-m prevents us from triggering on 'podman', which could
# conceivably be present in both sides. And we check for 'pod',
# not 'container', because it's possible to have something like
# <<container in pod|container>>.
if re.match('.*pod([^m]|$)', lhs, re.IGNORECASE):
if re.match('.*pod([^m]|$)', rhs, re.IGNORECASE):
raise Exception(f"'{matchobj[0]}' matches 'pod' in both left and right sides")
# Only left-hand side has "pod"
if self.pod_or_container == 'pod':
return lhs
return rhs
# 'pod' not in lhs, must be in rhs
if not re.match('.*pod([^m]|$)', rhs, re.IGNORECASE):
raise Exception(f"'{matchobj[0]}' does not match 'pod' in either side")
if self.pod_or_container == 'pod':
return rhs
return lhs
return re.sub(r'<<[^\|>]*\|[^\|>]*>>', replwith, line)
def main():
"script entry point"
script_dir = os.path.abspath(os.path.dirname(__file__))
man_dir = os.path.join(script_dir,"../docs/source/markdown")
try:
os.chdir(man_dir)
except FileNotFoundError as ex:
raise Exception("Please invoke me from the base repo dir") from ex
# No longer possible to invoke us with args: reject any such invocation
if len(sys.argv) > 1:
raise Exception("This script accepts no arguments")
preprocessor = Preprocessor()
for infile in sorted(glob.glob('*.md.in')):
preprocessor.process(infile)
# Now rewrite all option files
preprocessor.rewrite_optionfiles()
if __name__ == "__main__":
main()