mirror of https://github.com/containers/podman.git
Merge pull request #16170 from edsantiago/manpage_generic_include
[CI:DOCS] markdown-preprocess: add generic include mechanism
This commit is contained in:
commit
8fef5eb12c
|
@ -233,7 +233,7 @@ Pods removed:
|
|||
`podman kube play --down` will not work with a URL if the YAML file the URL points to
|
||||
has been changed or altered.
|
||||
|
||||
@@option tls-verify
|
||||
@@include ../../kubernetes_support.md
|
||||
|
||||
## 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)**
|
||||
|
|
|
@ -2,112 +2,146 @@
|
|||
#
|
||||
# markdown-preprocess - filter *.md.in files, convert to .md
|
||||
#
|
||||
"""
|
||||
Simpleminded include mechanism for podman man pages.
|
||||
"""
|
||||
|
||||
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 = ''
|
||||
|
||||
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') as fh_in, open(outfile_tmp, 'w') 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.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 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') 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'
|
||||
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:
|
||||
raise Exception("Please invoke me from the base repo 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:
|
||||
process(infile)
|
||||
|
||||
def process(infile):
|
||||
# Some options are the same between containers and pods; determine
|
||||
# which description to use from the name of the source man page.
|
||||
pod_or_container = 'container'
|
||||
if '-pod-' in infile or '-kube-' in infile:
|
||||
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())
|
||||
|
||||
# print("got here: ",infile, " -> ", outfile)
|
||||
|
||||
with open(infile, 'r') as fh_in, open(outfile_tmp, 'w') 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')
|
||||
|
||||
# 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 " + 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:
|
||||
fh_out.write(line)
|
||||
|
||||
os.chmod(outfile_tmp, 0o444)
|
||||
os.rename(outfile_tmp, outfile)
|
||||
|
||||
# Given a file path of the form podman-foo-bar.1.md.in, return "foo bar"
|
||||
def podman_subcommand(string: str, full=None) -> str:
|
||||
# Special case: 'podman-pod-start' becomes just 'start'
|
||||
if not full:
|
||||
if string.startswith("podman-pod-"):
|
||||
string = string[len("podman-pod-"):]
|
||||
if string.startswith("podman-"):
|
||||
string = string[len("podman-"):]
|
||||
if string.endswith(".1.md.in"):
|
||||
string = string[:-len(".1.md.in")]
|
||||
return string.replace("-", " ")
|
||||
|
||||
# Replace instances of '<<pod|container>>' with the desired one (based on
|
||||
# 'type' which is 'pod' or 'container').
|
||||
def replace_type(line: str, type: str) -> str:
|
||||
# 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("'%s' matches 'pod' in both left and right sides" % matchobj[0])
|
||||
# Only left-hand side has "pod"
|
||||
if type == 'pod':
|
||||
return lhs
|
||||
else:
|
||||
return rhs
|
||||
else:
|
||||
if not re.match('.*pod([^m]|$)', rhs, re.IGNORECASE):
|
||||
raise Exception("'%s' does not match 'pod' in either side" % matchobj[0])
|
||||
if type == 'pod':
|
||||
return rhs
|
||||
else:
|
||||
return lhs
|
||||
|
||||
return re.sub('<<[^\|>]*\|[^\|>]*>>', replwith, line)
|
||||
preprocessor.process(infile)
|
||||
|
||||
if __name__ == "__main__":
|
||||
main()
|
||||
|
|
|
@ -14,65 +14,73 @@ spec = spec_from_loader("mp", SourceFileLoader("mp", "hack/markdown-preprocess")
|
|||
mp = module_from_spec(spec)
|
||||
spec.loader.exec_module(mp)
|
||||
|
||||
pp = mp.Preprocessor()
|
||||
|
||||
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):
|
||||
"""basic pod|container and vice-versa"""
|
||||
s = '<<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')
|
||||
self.check_4_way('container', 'pod')
|
||||
|
||||
def test_case_insensitive(self):
|
||||
"""test case-insensitive replacement of Pod, Container"""
|
||||
s = '<<Pod|Container>>'
|
||||
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')
|
||||
self.check_4_way('Container', 'Pod')
|
||||
|
||||
def test_dont_care_about_podman(self):
|
||||
"""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):
|
||||
"""oops - test for 'pod' other than at beginning of string"""
|
||||
s = '<<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')
|
||||
self.check_4_way('container', 'container or pod')
|
||||
|
||||
def test_blank(self):
|
||||
"""test that either side of '|' can be empty"""
|
||||
s = 'abc container<<| or pod>> def'
|
||||
self.assertEqual(mp.replace_type(s, 'container'), 'abc container def')
|
||||
self.assertEqual(mp.replace_type(s, 'pod'), 'abc container or pod def')
|
||||
s = 'abc container<< or pod|>> def'
|
||||
self.assertEqual(mp.replace_type(s, 'container'), 'abc container def')
|
||||
self.assertEqual(mp.replace_type(s, 'pod'), 'abc container or pod def')
|
||||
s_lblank = 'abc container<<| or pod>> def'
|
||||
s_rblank = 'abc container<< or pod|>> def'
|
||||
|
||||
pp.pod_or_container = 'container'
|
||||
self.assertEqual(pp.replace_type(s_lblank), 'abc container 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):
|
||||
"""test that 'pod' on both sides raises exception"""
|
||||
with self.assertRaisesRegex(Exception, "in both left and right sides"):
|
||||
mp.replace_type('<<pod 123|pod 321>>', 'pod')
|
||||
for word in ['pod', 'container']:
|
||||
pp.pod_or_container = word
|
||||
with self.assertRaisesRegex(Exception, "in both left and right sides"):
|
||||
pp.replace_type('<<pod 123|pod 321>>')
|
||||
|
||||
def test_exception_neither(self):
|
||||
"""test that 'pod' on neither side raises exception"""
|
||||
with self.assertRaisesRegex(Exception, "in either side"):
|
||||
mp.replace_type('<<container 123|container 321>>', 'pod')
|
||||
for word in ['pod', 'container']:
|
||||
pp.pod_or_container = word
|
||||
with self.assertRaisesRegex(Exception, "in either side"):
|
||||
pp.replace_type('<<container 123|container 321>>')
|
||||
|
||||
class TestPodmanSubcommand(unittest.TestCase):
|
||||
def test_basic(self):
|
||||
"""podman subcommand basic test"""
|
||||
self.assertEqual(mp.podman_subcommand("podman-foo.1.md.in"), "foo")
|
||||
self.assertEqual(mp.podman_subcommand("podman-foo-bar.1.md.in"), "foo bar")
|
||||
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.1.md.in'
|
||||
self.assertEqual(pp.podman_subcommand(), "foo")
|
||||
|
||||
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__':
|
||||
unittest.main()
|
||||
|
|
Loading…
Reference in New Issue