Merge pull request #16170 from edsantiago/manpage_generic_include

[CI:DOCS] markdown-preprocess: add generic include mechanism
This commit is contained in:
OpenShift Merge Robot 2022-10-17 09:53:06 -04:00 committed by GitHub
commit 8fef5eb12c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 163 additions and 121 deletions

View File

@ -233,7 +233,7 @@ Pods removed:
`podman kube play --down` will not work with a URL if the YAML file the URL points to `podman kube play --down` will not work with a URL if the YAML file the URL points to
has been changed or altered. has been changed or altered.
@@option tls-verify @@include ../../kubernetes_support.md
## SEE ALSO ## SEE ALSO
**[podman(1)](podman.1.md)**, **[podman-kube(1)](podman-kube.1.md)**, **[podman-kube-down(1)](podman-kube-down.1.md)**, **[podman-network-create(1)](podman-network-create.1.md)**, **[podman-kube-generate(1)](podman-kube-generate.1.md)**, **[containers-certs.d(5)](https://github.com/containers/image/blob/main/docs/containers-certs.d.5.md)** **[podman(1)](podman.1.md)**, **[podman-kube(1)](podman-kube.1.md)**, **[podman-kube-down(1)](podman-kube-down.1.md)**, **[podman-network-create(1)](podman-network-create.1.md)**, **[podman-kube-generate(1)](podman-kube-generate.1.md)**, **[containers-certs.d(5)](https://github.com/containers/image/blob/main/docs/containers-certs.d.5.md)**

View File

@ -2,83 +2,97 @@
# #
# markdown-preprocess - filter *.md.in files, convert to .md # markdown-preprocess - filter *.md.in files, convert to .md
# #
"""
Simpleminded include mechanism for podman man pages.
"""
import glob import glob
import os import os
import re import re
import sys import sys
def main(): class Preprocessor():
script_dir = os.path.abspath(os.path.dirname(__file__)) """
man_dir = os.path.join(script_dir,"../docs/source/markdown") 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 = ''
try: def process(self, infile:str):
os.chdir(man_dir) """
except FileNotFoundError: Main calling point: preprocesses one file
raise Exception("Please invoke me from the base repo dir") """
self.infile = infile
# If called with args, process only those files
infiles = [ os.path.basename(x) for x in sys.argv[1:] ]
if len(infiles) == 0:
# Called without args: process all *.md.in files
infiles = glob.glob('*.md.in')
for infile in infiles:
process(infile)
def process(infile):
# Some options are the same between containers and pods; determine # Some options are the same between containers and pods; determine
# which description to use from the name of the source man page. # which description to use from the name of the source man page.
pod_or_container = 'container' self.pod_or_container = 'container'
if '-pod-' in infile or '-kube-' in infile: if '-pod-' in infile or '-kube-' in infile:
pod_or_container = 'pod' self.pod_or_container = 'pod'
# foo.md.in -> foo.md -- but always write to a tmpfile # foo.md.in -> foo.md -- but always write to a tmpfile
outfile = os.path.splitext(infile)[0] outfile = os.path.splitext(infile)[0]
outfile_tmp = outfile + '.tmp.' + str(os.getpid()) outfile_tmp = outfile + '.tmp.' + str(os.getpid())
# print("got here: ",infile, " -> ", outfile)
with open(infile, 'r') as fh_in, open(outfile_tmp, 'w') as fh_out: with open(infile, 'r') as fh_in, open(outfile_tmp, 'w') as fh_out:
for line in fh_in: for line in fh_in:
# '@@option foo' -> include file options/foo.md # '@@option foo' -> include file options/foo.md
if line.startswith('@@option '): if line.startswith('@@option '):
_, optionname = line.strip().split(" ") _, optionname = line.strip().split(" ")
optionfile = os.path.join("options", optionname + '.md') optionfile = os.path.join("options", optionname + '.md')
self.insert_file(fh_out, optionfile)
# Comment intended to help someone viewing the .md file. # '@@include relative-path/must-exist.md'
# Leading newline is important because if two lines are elif line.startswith('@@include '):
# consecutive without a break, sphinx (but not go-md2man) _, path = line.strip().split(" ")
# treats them as one line and will unwantedly render the self.insert_file(fh_out, path)
# comment in its output.
fh_out.write("\n[//]: # (BEGIN included file " + optionfile + ")\n")
with open(optionfile, 'r') as fh_optfile:
for opt_line in fh_optfile:
opt_line = replace_type(opt_line, pod_or_container)
opt_line = opt_line.replace('<<subcommand>>', podman_subcommand(infile))
opt_line = opt_line.replace('<<fullsubcommand>>', podman_subcommand(infile, 'full'))
fh_out.write(opt_line)
fh_out.write("\n[//]: # (END included file " + optionfile + ")\n")
else: else:
fh_out.write(line) fh_out.write(line)
os.chmod(outfile_tmp, 0o444) os.chmod(outfile_tmp, 0o444)
os.rename(outfile_tmp, outfile) os.rename(outfile_tmp, outfile)
# Given a file path of the form podman-foo-bar.1.md.in, return "foo bar" def insert_file(self, fh_out, path: str):
def podman_subcommand(string: str, full=None) -> 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') as fh_included:
for opt_line in fh_included:
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' # Special case: 'podman-pod-start' becomes just 'start'
if not full: if not full:
if string.startswith("podman-pod-"): if subcommand.startswith("podman-pod-"):
string = string[len("podman-pod-"):] subcommand = subcommand[len("podman-pod-"):]
if string.startswith("podman-"): if subcommand.startswith("podman-"):
string = string[len("podman-"):] subcommand = subcommand[len("podman-"):]
if string.endswith(".1.md.in"): if subcommand.endswith(".1.md.in"):
string = string[:-len(".1.md.in")] subcommand = subcommand[:-len(".1.md.in")]
return string.replace("-", " ") return subcommand.replace("-", " ")
# Replace instances of '<<pod|container>>' with the desired one (based on def replace_type(self, line: str) -> str:
# 'type' which is 'pod' or 'container'). """
def replace_type(line: str, type: 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 # Internal helper function: determines the desired half of the <a|b> string
def replwith(matchobj): def replwith(matchobj):
lhs, rhs = matchobj[0].split('|') lhs, rhs = matchobj[0].split('|')
@ -93,21 +107,41 @@ def replace_type(line: str, type: str) -> str:
# <<container in pod|container>>. # <<container in pod|container>>.
if re.match('.*pod([^m]|$)', lhs, re.IGNORECASE): if re.match('.*pod([^m]|$)', lhs, re.IGNORECASE):
if re.match('.*pod([^m]|$)', rhs, re.IGNORECASE): if re.match('.*pod([^m]|$)', rhs, re.IGNORECASE):
raise Exception("'%s' matches 'pod' in both left and right sides" % matchobj[0]) raise Exception(f"'{matchobj[0]}' matches 'pod' in both left and right sides")
# Only left-hand side has "pod" # Only left-hand side has "pod"
if type == 'pod': if self.pod_or_container == 'pod':
return lhs return lhs
else:
return rhs return rhs
else:
# 'pod' not in lhs, must be in rhs
if not re.match('.*pod([^m]|$)', rhs, re.IGNORECASE): if not re.match('.*pod([^m]|$)', rhs, re.IGNORECASE):
raise Exception("'%s' does not match 'pod' in either side" % matchobj[0]) raise Exception(f"'{matchobj[0]}' does not match 'pod' in either side")
if type == 'pod': if self.pod_or_container == 'pod':
return rhs return rhs
else:
return lhs return lhs
return re.sub('<<[^\|>]*\|[^\|>]*>>', replwith, line) 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
# If called with args, process only those files
infiles = [ os.path.basename(x) for x in sys.argv[1:] ]
if len(infiles) == 0:
# Called without args: process all *.md.in files
infiles = glob.glob('*.md.in')
preprocessor = Preprocessor()
for infile in infiles:
preprocessor.process(infile)
if __name__ == "__main__": if __name__ == "__main__":
main() main()

View File

@ -14,65 +14,73 @@ spec = spec_from_loader("mp", SourceFileLoader("mp", "hack/markdown-preprocess")
mp = module_from_spec(spec) mp = module_from_spec(spec)
spec.loader.exec_module(mp) spec.loader.exec_module(mp)
pp = mp.Preprocessor()
class TestPodReplacer(unittest.TestCase): class TestPodReplacer(unittest.TestCase):
def check_4_way(self, containerstring: str, podstring: str):
types = ['container', 'pod']
strings = [ containerstring, podstring ]
for i in 0, 1:
pp.pod_or_container = types[i]
for j in 0, 1:
s = '<<' + strings[j] + '|' + strings[(j+1)%2] + '>>'
self.assertEqual(pp.replace_type(s), strings[i])
def test_basic(self): def test_basic(self):
"""basic pod|container and vice-versa""" """basic pod|container and vice-versa"""
s = '<<container|pod>>' self.check_4_way('container', 'pod')
self.assertEqual(mp.replace_type(s, 'pod'), 'pod')
self.assertEqual(mp.replace_type(s, 'container'), 'container')
s = '<<container|pod>>'
self.assertEqual(mp.replace_type(s, 'pod'), 'pod')
self.assertEqual(mp.replace_type(s, 'container'), 'container')
def test_case_insensitive(self): def test_case_insensitive(self):
"""test case-insensitive replacement of Pod, Container""" """test case-insensitive replacement of Pod, Container"""
s = '<<Pod|Container>>' self.check_4_way('Container', 'Pod')
self.assertEqual(mp.replace_type(s, 'pod'), 'Pod')
self.assertEqual(mp.replace_type(s, 'container'), 'Container')
s = '<<Container|Pod>>'
self.assertEqual(mp.replace_type(s, 'pod'), 'Pod')
self.assertEqual(mp.replace_type(s, 'container'), 'Container')
def test_dont_care_about_podman(self): def test_dont_care_about_podman(self):
"""we ignore 'podman'""" """we ignore 'podman'"""
self.assertEqual(mp.replace_type('<<podman container|pod in podman>>', 'container'), 'podman container') self.check_4_way('podman container', 'pod in podman')
def test_not_at_beginning(self): def test_not_at_beginning(self):
"""oops - test for 'pod' other than at beginning of string""" """oops - test for 'pod' other than at beginning of string"""
s = '<<container|container or pod>>' self.check_4_way('container', 'container or pod')
self.assertEqual(mp.replace_type(s, 'container'), 'container')
self.assertEqual(mp.replace_type(s, 'pod'), 'container or pod')
s = '<<container or pod|container>>'
self.assertEqual(mp.replace_type(s, 'container'), 'container')
self.assertEqual(mp.replace_type(s, 'pod'), 'container or pod')
def test_blank(self): def test_blank(self):
"""test that either side of '|' can be empty""" """test that either side of '|' can be empty"""
s = 'abc container<<| or pod>> def' s_lblank = 'abc container<<| or pod>> def'
self.assertEqual(mp.replace_type(s, 'container'), 'abc container def') s_rblank = 'abc container<< or pod|>> def'
self.assertEqual(mp.replace_type(s, 'pod'), 'abc container or pod def')
s = 'abc container<< or pod|>> def' pp.pod_or_container = 'container'
self.assertEqual(mp.replace_type(s, 'container'), 'abc container def') self.assertEqual(pp.replace_type(s_lblank), 'abc container def')
self.assertEqual(mp.replace_type(s, 'pod'), 'abc container or pod def') self.assertEqual(pp.replace_type(s_rblank), 'abc container def')
pp.pod_or_container = 'pod'
self.assertEqual(pp.replace_type(s_lblank), 'abc container or pod def')
self.assertEqual(pp.replace_type(s_rblank), 'abc container or pod def')
def test_exception_both(self): def test_exception_both(self):
"""test that 'pod' on both sides raises exception""" """test that 'pod' on both sides raises exception"""
for word in ['pod', 'container']:
pp.pod_or_container = word
with self.assertRaisesRegex(Exception, "in both left and right sides"): with self.assertRaisesRegex(Exception, "in both left and right sides"):
mp.replace_type('<<pod 123|pod 321>>', 'pod') pp.replace_type('<<pod 123|pod 321>>')
def test_exception_neither(self): def test_exception_neither(self):
"""test that 'pod' on neither side raises exception""" """test that 'pod' on neither side raises exception"""
for word in ['pod', 'container']:
pp.pod_or_container = word
with self.assertRaisesRegex(Exception, "in either side"): with self.assertRaisesRegex(Exception, "in either side"):
mp.replace_type('<<container 123|container 321>>', 'pod') pp.replace_type('<<container 123|container 321>>')
class TestPodmanSubcommand(unittest.TestCase): class TestPodmanSubcommand(unittest.TestCase):
def test_basic(self): def test_basic(self):
"""podman subcommand basic test""" """podman subcommand basic test"""
self.assertEqual(mp.podman_subcommand("podman-foo.1.md.in"), "foo") pp.infile = 'podman-foo.1.md.in'
self.assertEqual(mp.podman_subcommand("podman-foo-bar.1.md.in"), "foo bar") self.assertEqual(pp.podman_subcommand(), "foo")
self.assertEqual(mp.podman_subcommand("podman-pod-rm.1.md.in"), "rm")
self.assertEqual(mp.podman_subcommand("podman-pod-rm.1.md.in", "full"), "pod rm")
pp.infile = 'podman-foo-bar.1.md.in'
self.assertEqual(pp.podman_subcommand(), "foo bar")
pp.infile = 'podman-pod-rm.1.md.in'
self.assertEqual(pp.podman_subcommand(), "rm")
self.assertEqual(pp.podman_subcommand("full"), "pod rm")
if __name__ == '__main__': if __name__ == '__main__':
unittest.main() unittest.main()