docs: add ..cmdinclude:: directive

This commit is contained in:
rebortg 2020-11-09 21:16:02 +01:00
parent 099b01d67f
commit c46473a849
2 changed files with 195 additions and 2 deletions

View File

@ -1,6 +1,10 @@
from docutils import nodes, utils
import re
import io
import os
from docutils import io, nodes, utils, statemachine
from docutils.utils.error_reporting import SafeString, ErrorString
from docutils.parsers.rst.roles import set_classes
from docutils.parsers.rst import Directive
from docutils.parsers.rst import Directive, directives
from sphinx.util.docutils import SphinxDirective
@ -49,6 +53,7 @@ def setup(app):
app.add_directive('cfgcmd', CfgCmdDirective)
app.add_directive('opcmd', OpCmdDirective)
app.add_directive('cmdinclude', CfgInclude)
app.connect('doctree-resolved', process_cmd_nodes)
@ -148,6 +153,156 @@ class inlinecmd(nodes.inline):
#self.literal_whitespace -= 1
class CfgInclude(Directive):
required_arguments = 1
optional_arguments = 0
final_argument_whitespace = True
option_spec = {
'var0': str,
'var1': str,
'var2': str,
'var3': str,
'var4': str,
'var5': str,
'var6': str,
'var7': str,
'var8': str,
'var9': str
}
def run(self):
### Copy from include directive docutils
"""Include a file as part of the content of this reST file."""
if not self.state.document.settings.file_insertion_enabled:
raise self.warning('"%s" directive disabled.' % self.name)
source = self.state_machine.input_lines.source(
self.lineno - self.state_machine.input_offset - 1)
source_dir = os.path.dirname(os.path.abspath(source))
path = directives.path(self.arguments[0])
if path.startswith('<') and path.endswith('>'):
path = os.path.join(self.standard_include_path, path[1:-1])
path = os.path.normpath(os.path.join(source_dir, path))
path = utils.relative_path(None, path)
path = nodes.reprunicode(path)
encoding = self.options.get(
'encoding', self.state.document.settings.input_encoding)
e_handler=self.state.document.settings.input_encoding_error_handler
tab_width = self.options.get(
'tab-width', self.state.document.settings.tab_width)
try:
self.state.document.settings.record_dependencies.add(path)
include_file = io.FileInput(source_path=path,
encoding=encoding,
error_handler=e_handler)
except UnicodeEncodeError:
raise self.severe(u'Problems with "%s" directive path:\n'
'Cannot encode input file path "%s" '
'(wrong locale?).' %
(self.name, SafeString(path)))
except IOError:
raise self.severe(u'Problems with "%s" directive path.' %
(self.name))
startline = self.options.get('start-line', None)
endline = self.options.get('end-line', None)
try:
if startline or (endline is not None):
lines = include_file.readlines()
rawtext = ''.join(lines[startline:endline])
else:
rawtext = include_file.read()
except UnicodeError:
raise self.severe(u'Problem with "%s" directive:\n%s' %
(self.name, ErrorString(error)))
# start-after/end-before: no restrictions on newlines in match-text,
# and no restrictions on matching inside lines vs. line boundaries
after_text = self.options.get('start-after', None)
if after_text:
# skip content in rawtext before *and incl.* a matching text
after_index = rawtext.find(after_text)
if after_index < 0:
raise self.severe('Problem with "start-after" option of "%s" '
'directive:\nText not found.' % self.name)
rawtext = rawtext[after_index + len(after_text):]
before_text = self.options.get('end-before', None)
if before_text:
# skip content in rawtext after *and incl.* a matching text
before_index = rawtext.find(before_text)
if before_index < 0:
raise self.severe('Problem with "end-before" option of "%s" '
'directive:\nText not found.' % self.name)
rawtext = rawtext[:before_index]
include_lines = statemachine.string2lines(rawtext, tab_width,
convert_whitespace=True)
if 'literal' in self.options:
# Convert tabs to spaces, if `tab_width` is positive.
if tab_width >= 0:
text = rawtext.expandtabs(tab_width)
else:
text = rawtext
literal_block = nodes.literal_block(rawtext, source=path,
classes=self.options.get('class', []))
literal_block.line = 1
self.add_name(literal_block)
if 'number-lines' in self.options:
try:
startline = int(self.options['number-lines'] or 1)
except ValueError:
raise self.error(':number-lines: with non-integer '
'start value')
endline = startline + len(include_lines)
if text.endswith('\n'):
text = text[:-1]
tokens = NumberLines([([], text)], startline, endline)
for classes, value in tokens:
if classes:
literal_block += nodes.inline(value, value,
classes=classes)
else:
literal_block += nodes.Text(value, value)
else:
literal_block += nodes.Text(text, text)
return [literal_block]
if 'code' in self.options:
self.options['source'] = path
codeblock = CodeBlock(self.name,
[self.options.pop('code')], # arguments
self.options,
include_lines, # content
self.lineno,
self.content_offset,
self.block_text,
self.state,
self.state_machine)
return codeblock.run()
new_include_lines = []
var_value0 = self.options.get('var0', '')
var_value1 = self.options.get('var1', '')
var_value2 = self.options.get('var2', '')
var_value3 = self.options.get('var3', '')
var_value4 = self.options.get('var4', '')
var_value5 = self.options.get('var5', '')
var_value6 = self.options.get('var6', '')
var_value7 = self.options.get('var7', '')
var_value8 = self.options.get('var8', '')
var_value9 = self.options.get('var9', '')
for line in include_lines:
line = re.sub('{{\s?var0\s?}}',var_value0,line)
line = re.sub('{{\s?var1\s?}}',var_value1,line)
line = re.sub('{{\s?var2\s?}}',var_value2,line)
line = re.sub('{{\s?var3\s?}}',var_value3,line)
line = re.sub('{{\s?var4\s?}}',var_value4,line)
line = re.sub('{{\s?var5\s?}}',var_value5,line)
line = re.sub('{{\s?var6\s?}}',var_value6,line)
line = re.sub('{{\s?var7\s?}}',var_value7,line)
line = re.sub('{{\s?var8\s?}}',var_value8,line)
line = re.sub('{{\s?var9\s?}}',var_value9,line)
new_include_lines.append(line)
self.state_machine.insert_input(new_include_lines, path)
return []
class CfgcmdlistDirective(Directive):
def run(self):

View File

@ -200,6 +200,44 @@ For a inline operational level command use ``:opcmd:``
:opcmd:`add system image`
cmdinclude
""""""""""
To minimize redundancy there is a special include directive. It include a txt
file and replace the ``{{ var0 }}`` - ``{{ var9 }}`` with the correct value
.. code-block:: none
.. cmdinclude:: interface-address.txt
:var0: ethernet
:var1: eth1
the content of interface-address.txt looks like this
.. code-block:: none
.. cfgcmd:: set interfaces {{ var0 }} <interface> address <address | dhcp |
dhcpv6>
Configure interface `<interface>` with one or more interface
addresses.
* **address** can be specified multiple times as IPv4 and/or IPv6
address, e.g. 192.0.2.1/24 and/or 2001:db8::1/64
* **dhcp** interface address is received by DHCP from a DHCP server
on this segment.
* **dhcpv6** interface address is received by DHCPv6 from a DHCPv6
server on this segment.
Example:
.. code-block:: none
set interfaces {{ var0 }} {{ var1 }} address 192.0.2.1/24
set interfaces {{ var0 }} {{ var1 }} address 192.0.2.2/24
set interfaces {{ var0 }} {{ var1 }} address 2001:db8::ffff/64
set interfaces {{ var0 }} {{ var1 }} address 2001:db8:100::ffff/64
vytask
""""""