summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorMatthew Peveler <matt.peveler@gmail.com>2021-02-03 22:24:48 -0500
committerGitHub <noreply@github.com>2021-02-03 22:24:48 -0500
commit1cdafdb1b7020a0595537a11e98f9768ba9b7bbd (patch)
treec79a2d040692b45f4f03e01afb017fb8895894d4
parente76294bc6b1fe878a2ed5d5d4816385d600574da (diff)
downloadasciidoc-py3-1cdafdb1b7020a0595537a11e98f9768ba9b7bbd.tar.gz
add support for lines attribute for include directive (#167)
Signed-off-by: Matthew Peveler <matt.peveler@gmail.com>
-rw-r--r--asciidoc/asciidoc.py41
-rw-r--r--doc/asciidoc.txt7
-rw-r--r--tests/data/dummy_file.py470
-rw-r--r--tests/data/include-lines-html5.html901
-rw-r--r--tests/data/include-lines-test.txt23
-rw-r--r--tests/testasciidoc.conf10
6 files changed, 1445 insertions, 7 deletions
diff --git a/asciidoc/asciidoc.py b/asciidoc/asciidoc.py
index b4cd588..bf54477 100644
--- a/asciidoc/asciidoc.py
+++ b/asciidoc/asciidoc.py
@@ -4229,6 +4229,7 @@ class Reader1:
self.tabsize = 8 # Tab expansion number of spaces.
self.parent = None # Included reader's parent reader.
self._lineno = 0 # The last line read from file object f.
+ self.line_ranges = None # line ranges to include
self.current_depth = 0 # Current include depth.
self.max_depth = 10 # Initial maxiumum allowed include depth.
self.bom = None # Byte order mark (BOM).
@@ -4267,15 +4268,34 @@ class Reader1:
self.closefile()
self.__init__()
+ def readline(self):
+ while True:
+ s = self.f.readline()
+ if s:
+ self._lineno = self._lineno + 1
+ else:
+ break
+
+ if self.line_ranges is not None:
+ for line_range in self.line_ranges:
+ if len(line_range) == 1 and self._lineno == line_range[0]:
+ break
+ elif len(line_range) == 2 and line_range[0] <= self._lineno and (line_range[1] == -1 or self._lineno <= line_range[1]):
+ break
+ else:
+ continue
+ break
+ else:
+ break
+ return s
+
def read(self, skip=False):
"""Read next line. Return None if EOF. Expand tabs. Strip trailing
white space. Maintain self.next read ahead buffer. If skip=True then
conditional exclusion is active (ifdef and ifndef macros)."""
# Top up buffer.
if len(self.next) <= self.READ_BUFFER_MIN:
- s = self.f.readline()
- if s:
- self._lineno = self._lineno + 1
+ s = self.readline()
while s:
if self.tabsize != 0:
s = s.expandtabs(self.tabsize)
@@ -4283,9 +4303,7 @@ class Reader1:
self.next.append([self.fname, self._lineno, s])
if len(self.next) > self.READ_BUFFER_MIN:
break
- s = self.f.readline()
- if s:
- self._lineno = self._lineno + 1
+ s = self.readline()
# Return first (oldest) buffer entry.
if len(self.next) > 0:
self.cursor = self.next[0]
@@ -4351,6 +4369,17 @@ class Reader1:
self.max_depth = self.current_depth + val
except ValueError:
raise EAsciiDoc("include macro: illegal 'depth' argument")
+ if 'lines' in attrs:
+ try:
+ if ';' in attrs['lines']:
+ ranges = attrs['lines'].split(';')
+ else:
+ ranges = attrs['lines'].split(',')
+ for idx in range(len(ranges)):
+ ranges[idx] = [int(x) for x in ranges[idx].split('..')]
+ self.line_ranges = ranges
+ except ValueError:
+ raise EAsciiDoc("include macro: illegal 'lines' argument")
# Process included file.
message.verbose('include: ' + fname, linenos=False)
self.open(fname)
diff --git a/doc/asciidoc.txt b/doc/asciidoc.txt
index be0a34f..b30bc8a 100644
--- a/doc/asciidoc.txt
+++ b/doc/asciidoc.txt
@@ -2817,7 +2817,12 @@ headers. Example:
does not process nested includes). Setting 'depth' to '1' disables
nesting inside the included file. By default, nesting is limited to
a depth of ten.
-- If the he 'warnings' attribute is set to 'False' (or any other
+- The `lines` macro attribute can be used to include specific lines of
+ the file. You can specify a range of pages by using `..` between
+ the two numbers, for example `1..10` would include the first 10
+ lines. You can include multiple ranges or invdividual pages by using
+ a comma or semi-colon, for example `1..10,45,50..60`.
+- If the 'warnings' attribute is set to 'False' (or any other
Python literal that evaluates to boolean false) then no warning
message is printed if the included file does not exist. By default
'warnings' are enabled.
diff --git a/tests/data/dummy_file.py b/tests/data/dummy_file.py
new file mode 100644
index 0000000..aec34aa
--- /dev/null
+++ b/tests/data/dummy_file.py
@@ -0,0 +1,470 @@
+#!/usr/bin/env python3
+
+__version__ = '0.4.0'
+
+
+import difflib
+import io
+import os
+from pathlib import Path
+import re
+import shutil
+import sys
+
+sys.path.append(str(Path(__file__).resolve().parent.parent))
+from asciidoc import asciidoc # noqa: E402
+
+# Default backends.
+BACKENDS = ('html4', 'xhtml11', 'docbook', 'docbook5', 'html5')
+BACKEND_EXT = {
+ 'html4': '.html',
+ 'xhtml11': '.html',
+ 'docbook': '.xml',
+ 'docbook5': '.xml',
+ 'slidy': '.html',
+ 'html5': '.html'
+}
+
+
+def iif(condition, iftrue, iffalse=None):
+ """
+ Immediate if c.f. ternary ?: operator.
+ False value defaults to '' if the true value is a string.
+ False value defaults to 0 if the true value is a number.
+ """
+ if iffalse is None:
+ if isinstance(iftrue, str):
+ iffalse = ''
+ if type(iftrue) in (int, float):
+ iffalse = 0
+ if condition:
+ return iftrue
+ else:
+ return iffalse
+
+
+def message(msg=''):
+ print(msg, file=sys.stderr)
+
+
+def strip_end(lines):
+ """
+ Strip blank strings from the end of list of strings.
+ """
+ for i in range(len(lines) - 1, -1, -1):
+ if not lines[i]:
+ del lines[i]
+ else:
+ break
+
+
+def normalize_data(lines):
+ """
+ Strip comments and trailing blank strings from lines.
+ """
+ result = [s for s in lines if not s.startswith('#')]
+ strip_end(result)
+ return result
+
+
+class AsciiDocTest(object):
+ def __init__(self):
+ self.number = None # Test number (1..).
+ self.name = '' # Optional test name.
+ self.title = '' # Optional test name.
+ self.description = [] # List of lines followoing title.
+ self.source = None # AsciiDoc test source file name.
+ self.options = []
+ self.attributes = {'asciidoc-version': 'test'}
+ self.backends = BACKENDS
+ self.artifacts = [] # list of generated artifacts to delete
+ self.requires = [] # list of dependencies to check for for the test
+ self.confdir = None
+ self.datadir = None # Where output files are stored.
+ self.disabled = False
+ self.passed = self.skipped = self.failed = 0
+
+ def backend_filename(self, backend):
+ """
+ Return the path name of the backend output file that is generated from
+ the test name and output file type.
+ """
+ return '%s-%s%s' % (
+ os.path.normpath(os.path.join(self.datadir, self.name)),
+ backend,
+ BACKEND_EXT[backend]
+ )
+
+ def parse(self, lines, confdir, datadir):
+ """
+ Parse conf file test section from list of text lines.
+ """
+ self.__init__()
+ self.confdir = confdir
+ self.datadir = datadir
+ lines = Lines(lines)
+ while not lines.eol():
+ text = lines.read_until(r'^%')
+ if text:
+ if not text[0].startswith('%'):
+ if text[0][0] == '!':
+ self.disabled = True
+ self.title = text[0][1:]
+ else:
+ self.title = text[0]
+ self.description = text[1:]
+ continue
+ reo = re.match(r'^%\s*(?P<directive>[\w_-]+)', text[0])
+ if not reo:
+ raise ValueError
+ directive = reo.groupdict()['directive']
+ data = normalize_data(text[1:])
+ if directive == 'source':
+ if data:
+ self.source = os.path.normpath(os.path.join(
+ self.confdir, os.path.normpath(data[0])
+ ))
+ elif directive == 'options':
+ self.options = eval(' '.join(data))
+ for i, v in enumerate(self.options):
+ if isinstance(v, str):
+ self.options[i] = (v, None)
+ elif directive == 'attributes':
+ self.attributes.update(eval(' '.join(data)))
+ elif directive == 'backends':
+ self.backends = eval(' '.join(data))
+ elif directive == 'name':
+ self.name = data[0].strip()
+ elif directive == 'requires':
+ self.requires = eval(' '.join(data))
+ elif directive == 'artifacts':
+ self.artifacts = eval(' '.join(data))
+ else:
+ raise ValueError
+ if not self.title:
+ self.title = self.source
+ if not self.name:
+ self.name = os.path.basename(os.path.splitext(self.source)[0])
+
+ def is_missing(self, backend):
+ """
+ Returns True if there is no output test data file for backend.
+ """
+ return not os.path.isfile(self.backend_filename(backend))
+
+ def is_missing_or_outdated(self, backend):
+ """
+ Returns True if the output test data file is missing or out of date.
+ """
+ return self.is_missing(backend) or (
+ os.path.getmtime(self.source)
+ > os.path.getmtime(self.backend_filename(backend))
+ )
+
+ def clean_artifacts(self):
+ for artifact in self.artifacts:
+ loc = os.path.join(self.confdir, artifact)
+ if os.path.exists(loc):
+ os.unlink(loc)
+
+ def get_expected(self, backend):
+ """
+ Return expected test data output for backend.
+ """
+ with open(
+ self.backend_filename(backend),
+ encoding='utf-8',
+ newline=''
+ ) as open_file:
+ return open_file.readlines()
+
+ def generate_expected(self, backend):
+ """
+ Generate and return test data output for backend.
+ """
+ asciidoc.reset_asciidoc()
+ outfile = io.StringIO()
+ options = self.options[:]
+ options.append(('--out-file', outfile))
+ options.append(('--backend', backend))
+ for k, v in self.attributes.items():
+ if v == '' or k[-1] in '!@':
+ s = str(k)
+ elif v is None:
+ s = k + '!'
+ else:
+ s = '%s=%s' % (k, v)
+ options.append(('--attribute', s))
+ asciidoc.execute('asciidoc', options, [self.source])
+ return outfile.getvalue().splitlines(keepends=True)
+
+ def update_expected(self, backend):
+ """
+ Generate and write backend data.
+ """
+ lines = self.generate_expected(backend)
+ if not os.path.isdir(self.datadir):
+ print('CREATING: %s' % self.datadir)
+ os.mkdir(self.datadir)
+ with open(
+ self.backend_filename(backend),
+ 'w+',
+ encoding='utf-8',
+ newline=''
+ ) as open_file:
+ print('WRITING: %s' % open_file.name)
+ open_file.writelines(lines)
+
+ def update(self, backend=None, force=False):
+ """
+ Regenerate and update expected test data outputs.
+ """
+ if backend is None:
+ backends = self.backends
+ else:
+ backends = [backend]
+
+ print('SOURCE: asciidoc: %s' % self.source)
+ for backend in backends:
+ if force or self.is_missing_or_outdated(backend):
+ self.update_expected(backend)
+ print()
+
+ self.clean_artifacts()
+
+ def run(self, backend=None):
+ """
+ Execute test.
+ Return True if test passes.
+ """
+ if backend is None:
+ backends = self.backends
+ else:
+ backends = [backend]
+ result = True # Assume success.
+ self.passed = self.failed = self.skipped = 0
+ print('%d: %s' % (self.number, self.title))
+ if self.source and os.path.isfile(self.source):
+ print('SOURCE: asciidoc: %s' % self.source)
+ for backend in backends:
+ fromfile = self.backend_filename(backend)
+ skip = False
+ for require in self.requires:
+ if shutil.which(require) is None:
+ skip = True
+ break
+ if not skip and not self.is_missing(backend):
+ expected = self.get_expected(backend)
+ strip_end(expected)
+ got = self.generate_expected(backend)
+ strip_end(got)
+ lines = []
+ for line in difflib.unified_diff(got, expected, n=0):
+ lines.append(line)
+ if lines:
+ result = False
+ self.failed += 1
+ lines = lines[3:]
+ print('FAILED: %s: %s' % (backend, fromfile))
+ message('+++ %s' % fromfile)
+ message('--- got')
+ for line in lines:
+ message(line)
+ message()
+ else:
+ self.passed += 1
+ print('PASSED: %s: %s' % (backend, fromfile))
+ else:
+ self.skipped += 1
+ print('SKIPPED: %s: %s' % (backend, fromfile))
+ self.clean_artifacts()
+ else:
+ self.skipped += len(backends)
+ if self.source:
+ msg = 'MISSING: %s' % self.source
+ else:
+ msg = 'NO ASCIIDOC SOURCE FILE SPECIFIED'
+ print(msg)
+ print('')
+ return result
+
+
+class AsciiDocTests(object):
+ def __init__(self, conffile):
+ """
+ Parse configuration file
+ :param conffile:
+ """
+ self.conffile = conffile
+ self.passed = self.failed = self.skipped = 0
+ # All file names are relative to configuration file directory.
+ self.confdir = os.path.dirname(self.conffile)
+ self.datadir = self.confdir # Default expected files directory.
+ self.tests = [] # List of parsed AsciiDocTest objects.
+ self.globals = {}
+ with open(self.conffile, encoding='utf-8') as open_file:
+ lines = Lines(open_file.readlines())
+ first = True
+ while not lines.eol():
+ s = lines.read_until(r'^%+$')
+ s = [line for line in s if len(line) > 0] # Drop blank lines.
+ # Must be at least one non-blank line in addition to delimiter.
+ if len(s) > 1:
+ # Optional globals precede all tests.
+ if first and re.match(r'^%\s*globals$', s[0]):
+ self.globals = eval(' '.join(normalize_data(s[1:])))
+ if 'datadir' in self.globals:
+ self.datadir = os.path.join(
+ self.confdir,
+ os.path.normpath(self.globals['datadir'])
+ )
+ else:
+ test = AsciiDocTest()
+ test.parse(s[1:], self.confdir, self.datadir)
+ self.tests.append(test)
+ test.number = len(self.tests)
+ first = False
+
+ def run(self, number=None, backend=None):
+ """
+ Run all tests.
+ If number is specified run test number (1..).
+ """
+ self.passed = self.failed = self.skipped = 0
+ for test in self.tests:
+ if (
+ (not test.disabled or number)
+ and (not number or number == test.number)
+ and (not backend or backend in test.backends)
+ ):
+ test.run(backend)
+ self.passed += test.passed
+ self.failed += test.failed
+ self.skipped += test.skipped
+ if self.passed > 0:
+ print('TOTAL PASSED: %s' % self.passed)
+ if self.failed > 0:
+ print('TOTAL FAILED: %s' % self.failed)
+ if self.skipped > 0:
+ print('TOTAL SKIPPED: %s' % self.skipped)
+
+ def update(self, number=None, backend=None, force=False):
+ """
+ Regenerate expected test data and update configuratio file.
+ """
+ for test in self.tests:
+ if (not test.disabled or number) and (not number or number == test.number):
+ test.update(backend, force=force)
+
+ def list(self):
+ """
+ Lists tests to stdout.
+ """
+ for test in self.tests:
+ print('%d: %s%s' % (test.number, iif(test.disabled, '!'), test.title))
+
+
+class Lines(list):
+ """
+ A list of strings.
+ Adds eol() and read_until() to list type.
+ """
+
+ def __init__(self, lines):
+ super(Lines, self).__init__()
+ self.extend([s.rstrip() for s in lines])
+ self.pos = 0
+
+ def eol(self):
+ return self.pos >= len(self)
+
+ def read_until(self, regexp):
+ """
+ Return a list of lines from current position up until the next line
+ matching regexp.
+ Advance position to matching line.
+ """
+ result = []
+ if not self.eol():
+ result.append(self[self.pos])
+ self.pos += 1
+ while not self.eol():
+ if re.match(regexp, self[self.pos]):
+ break
+ result.append(self[self.pos])
+ self.pos += 1
+ return result
+
+
+if __name__ == '__main__':
+ # guarantee a stable timestamp matching the test fixtures
+ os.environ['SOURCE_DATE_EPOCH'] = '1038184662'
+ # Process command line options.
+ from argparse import ArgumentParser
+ parser = ArgumentParser(
+ description='Run AsciiDoc conformance tests specified in configuration'
+ 'FILE.'
+ )
+ msg = 'Use configuration file CONF_FILE (default configuration file is '\
+ 'testasciidoc.conf in testasciidoc.py directory)'
+ parser.add_argument(
+ '-v',
+ '--version',
+ action='version',
+ version='%(prog)s {}'.format(__version__)
+ )
+ parser.add_argument('-f', '--conf-file', help=msg)
+
+ subparsers = parser.add_subparsers(metavar='command', dest='command')
+ subparsers.required = True
+
+ subparsers.add_parser('list', help='List tests')
+
+ options = ArgumentParser(add_help=False)
+ options.add_argument('-n', '--number', type=int, help='Test number to run')
+ options.add_argument('-b', '--backend', type=str, help='Backend to run')
+
+ subparsers.add_parser('run', help='Execute tests', parents=[options])
+
+ subparser = subparsers.add_parser(
+ 'update',
+ help='Regenerate and update test data',
+ parents=[options]
+ )
+ subparser.add_argument(
+ '--force',
+ action='store_true',
+ help='Update all test data overwriting existing data'
+ )
+
+ args = parser.parse_args()
+
+ conffile = os.path.join(os.path.dirname(sys.argv[0]), 'testasciidoc.conf')
+ force = 'force' in args and args.force is True
+ if args.conf_file is not None:
+ conffile = args.conf_file
+ if not os.path.isfile(conffile):
+ message('missing CONF_FILE: %s' % conffile)
+ sys.exit(1)
+ tests = AsciiDocTests(conffile)
+ cmd = args.command
+ number = None
+ backend = None
+ if 'number' in args:
+ number = args.number
+ if 'backend' in args:
+ backend = args.backend
+ if backend and backend not in BACKENDS:
+ message('illegal BACKEND: {:s}'.format(backend))
+ sys.exit(1)
+ if number is not None and (number < 1 or number > len(tests.tests)):
+ message('illegal test NUMBER: {:d}'.format(number))
+ sys.exit(1)
+ if cmd == 'run':
+ tests.run(number, backend)
+ if tests.failed:
+ sys.exit(1)
+ elif cmd == 'update':
+ tests.update(number, backend, force=force)
+ elif cmd == 'list':
+ tests.list()
diff --git a/tests/data/include-lines-html5.html b/tests/data/include-lines-html5.html
new file mode 100644
index 0000000..e1d72f9
--- /dev/null
+++ b/tests/data/include-lines-html5.html
@@ -0,0 +1,901 @@
+<!DOCTYPE html>
+<html lang="en">
+<head>
+<meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
+<meta name="generator" content="AsciiDoc test">
+<title>include line test</title>
+<style type="text/css">
+/* Shared CSS for AsciiDoc xhtml11 and html5 backends */
+
+/* Default font. */
+body {
+ font-family: Georgia,serif;
+}
+
+/* Title font. */
+h1, h2, h3, h4, h5, h6,
+div.title, caption.title,
+thead, p.table.header,
+#toctitle,
+#author, #revnumber, #revdate, #revremark,
+#footer {
+ font-family: Arial,Helvetica,sans-serif;
+}
+
+body {
+ margin: 1em 5% 1em 5%;
+}
+
+a {
+ color: blue;
+ text-decoration: underline;
+}
+a:visited {
+ color: fuchsia;
+}
+
+em {
+ font-style: italic;
+ color: navy;
+}
+
+strong {
+ font-weight: bold;
+ color: #083194;
+}
+
+h1, h2, h3, h4, h5, h6 {
+ color: #527bbd;
+ margin-top: 1.2em;
+ margin-bottom: 0.5em;
+ line-height: 1.3;
+}
+
+h1, h2, h3 {
+ border-bottom: 2px solid silver;
+}
+h2 {
+ padding-top: 0.5em;
+}
+h3 {
+ float: left;
+}
+h3 + * {
+ clear: left;
+}
+h5 {
+ font-size: 1.0em;
+}
+
+div.sectionbody {
+ margin-left: 0;
+}
+
+hr {
+ border: 1px solid silver;
+}
+
+p {
+ margin-top: 0.5em;
+ margin-bottom: 0.5em;
+}
+
+ul, ol, li > p {
+ margin-top: 0;
+}
+ul > li { color: #aaa; }
+ul > li > * { color: black; }
+
+.monospaced, code, pre {
+ font-family: "Courier New", Courier, monospace;
+ font-size: inherit;
+ color: navy;
+ padding: 0;
+ margin: 0;
+}
+pre {
+ white-space: pre-wrap;
+}
+
+#author {
+ color: #527bbd;
+ font-weight: bold;
+ font-size: 1.1em;
+}
+#email {
+}
+#revnumber, #revdate, #revremark {
+}
+
+#footer {
+ font-size: small;
+ border-top: 2px solid silver;
+ padding-top: 0.5em;
+ margin-top: 4.0em;
+}
+#footer-text {
+ float: left;
+ padding-bottom: 0.5em;
+}
+#footer-badges {
+ float: right;
+ padding-bottom: 0.5em;
+}
+
+#preamble {
+ margin-top: 1.5em;
+ margin-bottom: 1.5em;
+}
+div.imageblock, div.exampleblock, div.verseblock,
+div.quoteblock, div.literalblock, div.listingblock, div.sidebarblock,
+div.admonitionblock {
+ margin-top: 1.0em;
+ margin-bottom: 1.5em;
+}
+div.admonitionblock {
+ margin-top: 2.0em;
+ margin-bottom: 2.0em;
+ margin-right: 10%;
+ color: #606060;
+}
+
+div.content { /* Block element content. */
+ padding: 0;
+}
+
+/* Block element titles. */
+div.title, caption.title {
+ color: #527bbd;
+ font-weight: bold;
+ text-align: left;
+ margin-top: 1.0em;
+ margin-bottom: 0.5em;
+}
+div.title + * {
+ margin-top: 0;
+}
+
+td div.title:first-child {
+ margin-top: 0.0em;
+}
+div.content div.title:first-child {
+ margin-top: 0.0em;
+}
+div.content + div.title {
+ margin-top: 0.0em;
+}
+
+div.sidebarblock > div.content {
+ background: #ffffee;
+ border: 1px solid #dddddd;
+ border-left: 4px solid #f0f0f0;
+ padding: 0.5em;
+}
+
+div.listingblock > div.content {
+ border: 1px solid #dddddd;
+ border-left: 5px solid #f0f0f0;
+ background: #f8f8f8;
+ padding: 0.5em;
+}
+
+div.quoteblock, div.verseblock {
+ padding-left: 1.0em;
+ margin-left: 1.0em;
+ margin-right: 10%;
+ border-left: 5px solid #f0f0f0;
+ color: #888;
+}
+
+div.quoteblock > div.attribution {
+ padding-top: 0.5em;
+ text-align: right;
+}
+
+div.verseblock > pre.content {
+ font-family: inherit;
+ font-size: inherit;
+}
+div.verseblock > div.attribution {
+ padding-top: 0.75em;
+ text-align: left;
+}
+/* DEPRECATED: Pre version 8.2.7 verse style literal block. */
+div.verseblock + div.attribution {
+ text-align: left;
+}
+
+div.admonitionblock .icon {
+ vertical-align: top;
+ font-size: 1.1em;
+ font-weight: bold;
+ text-decoration: underline;
+ color: #527bbd;
+ padding-right: 0.5em;
+}
+div.admonitionblock td.content {
+ padding-left: 0.5em;
+ border-left: 3px solid #dddddd;
+}
+
+div.exampleblock > div.content {
+ border-left: 3px solid #dddddd;
+ padding-left: 0.5em;
+}
+
+div.imageblock div.content { padding-left: 0; }
+span.image img { border-style: none; vertical-align: text-bottom; }
+a.image:visited { color: white; }
+
+dl {
+ margin-top: 0.8em;
+ margin-bottom: 0.8em;
+}
+dt {
+ margin-top: 0.5em;
+ margin-bottom: 0;
+ font-style: normal;
+ color: navy;
+}
+dd > *:first-child {
+ margin-top: 0.1em;
+}
+
+ul, ol {
+ list-style-position: outside;
+}
+ol.arabic {
+ list-style-type: decimal;
+}
+ol.loweralpha {
+ list-style-type: lower-alpha;
+}
+ol.upperalpha {
+ list-style-type: upper-alpha;
+}
+ol.lowerroman {
+ list-style-type: lower-roman;
+}
+ol.upperroman {
+ list-style-type: upper-roman;
+}
+
+div.compact ul, div.compact ol,
+div.compact p, div.compact p,
+div.compact div, div.compact div {
+ margin-top: 0.1em;
+ margin-bottom: 0.1em;
+}
+
+tfoot {
+ font-weight: bold;
+}
+td > div.verse {
+ white-space: pre;
+}
+
+div.hdlist {
+ margin-top: 0.8em;
+ margin-bottom: 0.8em;
+}
+div.hdlist tr {
+ padding-bottom: 15px;
+}
+dt.hdlist1.strong, td.hdlist1.strong {
+ font-weight: bold;
+}
+td.hdlist1 {
+ vertical-align: top;
+ font-style: normal;
+ padding-right: 0.8em;
+ color: navy;
+}
+td.hdlist2 {
+ vertical-align: top;
+}
+div.hdlist.compact tr {
+ margin: 0;
+ padding-bottom: 0;
+}
+
+.comment {
+ background: yellow;
+}
+
+.footnote, .footnoteref {
+ font-size: 0.8em;
+}
+
+span.footnote, span.footnoteref {
+ vertical-align: super;
+}
+
+#footnotes {
+ margin: 20px 0 20px 0;
+ padding: 7px 0 0 0;
+}
+
+#footnotes div.footnote {
+ margin: 0 0 5px 0;
+}
+
+#footnotes hr {
+ border: none;
+ border-top: 1px solid silver;
+ height: 1px;
+ text-align: left;
+ margin-left: 0;
+ width: 20%;
+ min-width: 100px;
+}
+
+div.colist td {
+ padding-right: 0.5em;
+ padding-bottom: 0.3em;
+ vertical-align: top;
+}
+div.colist td img {
+ margin-top: 0.3em;
+}
+
+@media print {
+ #footer-badges { display: none; }
+}
+
+#toc {
+ margin-bottom: 2.5em;
+}
+
+#toctitle {
+ color: #527bbd;
+ font-size: 1.1em;
+ font-weight: bold;
+ margin-top: 1.0em;
+ margin-bottom: 0.1em;
+}
+
+div.toclevel0, div.toclevel1, div.toclevel2, div.toclevel3, div.toclevel4 {
+ margin-top: 0;
+ margin-bottom: 0;
+}
+div.toclevel2 {
+ margin-left: 2em;
+ font-size: 0.9em;
+}
+div.toclevel3 {
+ margin-left: 4em;
+ font-size: 0.9em;
+}
+div.toclevel4 {
+ margin-left: 6em;
+ font-size: 0.9em;
+}
+
+span.aqua { color: aqua; }
+span.black { color: black; }
+span.blue { color: blue; }
+span.fuchsia { color: fuchsia; }
+span.gray { color: gray; }
+span.green { color: green; }
+span.lime { color: lime; }
+span.maroon { color: maroon; }
+span.navy { color: navy; }
+span.olive { color: olive; }
+span.purple { color: purple; }
+span.red { color: red; }
+span.silver { color: silver; }
+span.teal { color: teal; }
+span.white { color: white; }
+span.yellow { color: yellow; }
+
+span.aqua-background { background: aqua; }
+span.black-background { background: black; }
+span.blue-background { background: blue; }
+span.fuchsia-background { background: fuchsia; }
+span.gray-background { background: gray; }
+span.green-background { background: green; }
+span.lime-background { background: lime; }
+span.maroon-background { background: maroon; }
+span.navy-background { background: navy; }
+span.olive-background { background: olive; }
+span.purple-background { background: purple; }
+span.red-background { background: red; }
+span.silver-background { background: silver; }
+span.teal-background { background: teal; }
+span.white-background { background: white; }
+span.yellow-background { background: yellow; }
+
+span.big { font-size: 2em; }
+span.small { font-size: 0.6em; }
+
+span.underline { text-decoration: underline; }
+span.overline { text-decoration: overline; }
+span.line-through { text-decoration: line-through; }
+
+div.unbreakable { page-break-inside: avoid; }
+
+
+/*
+ * xhtml11 specific
+ *
+ * */
+
+div.tableblock {
+ margin-top: 1.0em;
+ margin-bottom: 1.5em;
+}
+div.tableblock > table {
+ border: 3px solid #527bbd;
+}
+thead, p.table.header {
+ font-weight: bold;
+ color: #527bbd;
+}
+p.table {
+ margin-top: 0;
+}
+/* Because the table frame attribute is overridden by CSS in most browsers. */
+div.tableblock > table[frame="void"] {
+ border-style: none;
+}
+div.tableblock > table[frame="hsides"] {
+ border-left-style: none;
+ border-right-style: none;
+}
+div.tableblock > table[frame="vsides"] {
+ border-top-style: none;
+ border-bottom-style: none;
+}
+
+
+/*
+ * html5 specific
+ *
+ * */
+
+table.tableblock {
+ margin-top: 1.0em;
+ margin-bottom: 1.5em;
+}
+thead, p.tableblock.header {
+ font-weight: bold;
+ color: #527bbd;
+}
+p.tableblock {
+ margin-top: 0;
+}
+table.tableblock {
+ border-width: 3px;
+ border-spacing: 0px;
+ border-style: solid;
+ border-color: #527bbd;
+ border-collapse: collapse;
+}
+th.tableblock, td.tableblock {
+ border-width: 1px;
+ padding: 4px;
+ border-style: solid;
+ border-color: #527bbd;
+}
+
+table.tableblock.frame-topbot {
+ border-left-style: hidden;
+ border-right-style: hidden;
+}
+table.tableblock.frame-sides {
+ border-top-style: hidden;
+ border-bottom-style: hidden;
+}
+table.tableblock.frame-none {
+ border-style: hidden;
+}
+
+th.tableblock.halign-left, td.tableblock.halign-left {
+ text-align: left;
+}
+th.tableblock.halign-center, td.tableblock.halign-center {
+ text-align: center;
+}
+th.tableblock.halign-right, td.tableblock.halign-right {
+ text-align: right;
+}
+
+th.tableblock.valign-top, td.tableblock.valign-top {
+ vertical-align: top;
+}
+th.tableblock.valign-middle, td.tableblock.valign-middle {
+ vertical-align: middle;
+}
+th.tableblock.valign-bottom, td.tableblock.valign-bottom {
+ vertical-align: bottom;
+}
+
+
+/*
+ * manpage specific
+ *
+ * */
+
+body.manpage h1 {
+ padding-top: 0.5em;
+ padding-bottom: 0.5em;
+ border-top: 2px solid silver;
+ border-bottom: 2px solid silver;
+}
+body.manpage h2 {
+ border-style: none;
+}
+body.manpage div.sectionbody {
+ margin-left: 3em;
+}
+
+@media print {
+ body.manpage div#toc { display: none; }
+}
+
+
+</style>
+<script type="text/javascript">
+/*<![CDATA[*/
+var asciidoc = { // Namespace.
+
+/////////////////////////////////////////////////////////////////////
+// Table Of Contents generator
+/////////////////////////////////////////////////////////////////////
+
+/* Author: Mihai Bazon, September 2002
+ * http://students.infoiasi.ro/~mishoo
+ *
+ * Table Of Content generator
+ * Version: 0.4
+ *
+ * Feel free to use this script under the terms of the GNU General Public
+ * License, as long as you do not remove or alter this notice.
+ */
+
+ /* modified by Troy D. Hanson, September 2006. License: GPL */
+ /* modified by Stuart Rackham, 2006, 2009. License: GPL */
+
+// toclevels = 1..4.
+toc: function (toclevels) {
+
+ function getText(el) {
+ var text = "";
+ for (var i = el.firstChild; i != null; i = i.nextSibling) {
+ if (i.nodeType == 3 /* Node.TEXT_NODE */) // IE doesn't speak constants.
+ text += i.data;
+ else if (i.firstChild != null)
+ text += getText(i);
+ }
+ return text;
+ }
+
+ function TocEntry(el, text, toclevel) {
+ this.element = el;
+ this.text = text;
+ this.toclevel = toclevel;
+ }
+
+ function tocEntries(el, toclevels) {
+ var result = new Array;
+ var re = new RegExp('[hH]([1-'+(toclevels+1)+'])');
+ // Function that scans the DOM tree for header elements (the DOM2
+ // nodeIterator API would be a better technique but not supported by all
+ // browsers).
+ var iterate = function (el) {
+ for (var i = el.firstChild; i != null; i = i.nextSibling) {
+ if (i.nodeType == 1 /* Node.ELEMENT_NODE */) {
+ var mo = re.exec(i.tagName);
+ if (mo && (i.getAttribute("class") || i.getAttribute("className")) != "float") {
+ result[result.length] = new TocEntry(i, getText(i), mo[1]-1);
+ }
+ iterate(i);
+ }
+ }
+ }
+ iterate(el);
+ return result;
+ }
+
+ var toc = document.getElementById("toc");
+ if (!toc) {
+ return;
+ }
+
+ // Delete existing TOC entries in case we're reloading the TOC.
+ var tocEntriesToRemove = [];
+ var i;
+ for (i = 0; i < toc.childNodes.length; i++) {
+ var entry = toc.childNodes[i];
+ if (entry.nodeName.toLowerCase() == 'div'
+ && entry.getAttribute("class")
+ && entry.getAttribute("class").match(/^toclevel/))
+ tocEntriesToRemove.push(entry);
+ }
+ for (i = 0; i < tocEntriesToRemove.length; i++) {
+ toc.removeChild(tocEntriesToRemove[i]);
+ }
+
+ // Rebuild TOC entries.
+ var entries = tocEntries(document.getElementById("content"), toclevels);
+ for (var i = 0; i < entries.length; ++i) {
+ var entry = entries[i];
+ if (entry.element.id == "")
+ entry.element.id = "_toc_" + i;
+ var a = document.createElement("a");
+ a.href = "#" + entry.element.id;
+ a.appendChild(document.createTextNode(entry.text));
+ var div = document.createElement("div");
+ div.appendChild(a);
+ div.className = "toclevel" + entry.toclevel;
+ toc.appendChild(div);
+ }
+ if (entries.length == 0)
+ toc.parentNode.removeChild(toc);
+},
+
+
+/////////////////////////////////////////////////////////////////////
+// Footnotes generator
+/////////////////////////////////////////////////////////////////////
+
+/* Based on footnote generation code from:
+ * http://www.brandspankingnew.net/archive/2005/07/format_footnote.html
+ */
+
+footnotes: function () {
+ // Delete existing footnote entries in case we're reloading the footnodes.
+ var i;
+ var noteholder = document.getElementById("footnotes");
+ if (!noteholder) {
+ return;
+ }
+ var entriesToRemove = [];
+ for (i = 0; i < noteholder.childNodes.length; i++) {
+ var entry = noteholder.childNodes[i];
+ if (entry.nodeName.toLowerCase() == 'div' && entry.getAttribute("class") == "footnote")
+ entriesToRemove.push(entry);
+ }
+ for (i = 0; i < entriesToRemove.length; i++) {
+ noteholder.removeChild(entriesToRemove[i]);
+ }
+
+ // Rebuild footnote entries.
+ var cont = document.getElementById("content");
+ var spans = cont.getElementsByTagName("span");
+ var refs = {};
+ var n = 0;
+ for (i=0; i<spans.length; i++) {
+ if (spans[i].className == "footnote") {
+ n++;
+ var note = spans[i].getAttribute("data-note");
+ if (!note) {
+ // Use [\s\S] in place of . so multi-line matches work.
+ // Because JavaScript has no s (dotall) regex flag.
+ note = spans[i].innerHTML.match(/\s*\[([\s\S]*)]\s*/)[1];
+ spans[i].innerHTML =
+ "[<a id='_footnoteref_" + n + "' href='#_footnote_" + n +
+ "' title='View footnote' class='footnote'>" + n + "</a>]";
+ spans[i].setAttribute("data-note", note);
+ }
+ noteholder.innerHTML +=
+ "<div class='footnote' id='_footnote_" + n + "'>" +
+ "<a href='#_footnoteref_" + n + "' title='Return to text'>" +
+ n + "</a>. " + note + "</div>";
+ var id =spans[i].getAttribute("id");
+ if (id != null) refs["#"+id] = n;
+ }
+ }
+ if (n == 0)
+ noteholder.parentNode.removeChild(noteholder);
+ else {
+ // Process footnoterefs.
+ for (i=0; i<spans.length; i++) {
+ if (spans[i].className == "footnoteref") {
+ var href = spans[i].getElementsByTagName("a")[0].getAttribute("href");
+ href = href.match(/#.*/)[0]; // Because IE return full URL.
+ n = refs[href];
+ spans[i].innerHTML =
+ "[<a href='#_footnote_" + n +
+ "' title='View footnote' class='footnote'>" + n + "</a>]";
+ }
+ }
+ }
+},
+
+install: function(toclevels) {
+ var timerId;
+
+ function reinstall() {
+ asciidoc.footnotes();
+ if (toclevels) {
+ asciidoc.toc(toclevels);
+ }
+ }
+
+ function reinstallAndRemoveTimer() {
+ clearInterval(timerId);
+ reinstall();
+ }
+
+ timerId = setInterval(reinstall, 500);
+ if (document.addEventListener)
+ document.addEventListener("DOMContentLoaded", reinstallAndRemoveTimer, false);
+ else
+ window.onload = reinstallAndRemoveTimer;
+}
+
+}
+asciidoc.install();
+/*]]>*/
+</script>
+</head>
+<body class="article">
+<div id="header">
+<h1>include line test</h1>
+</div>
+<div id="content">
+<div id="preamble">
+<div class="sectionbody">
+<div class="paragraph"><p>The imports:</p></div>
+<div class="listingblock">
+<div class="content"><!-- Generator: GNU source-highlight
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><tt><span style="font-weight: bold"><span style="color: #000080">import</span></span> difflib
+<span style="font-weight: bold"><span style="color: #000080">import</span></span> io
+<span style="font-weight: bold"><span style="color: #000080">import</span></span> os
+<span style="font-weight: bold"><span style="color: #000080">from</span></span> pathlib <span style="font-weight: bold"><span style="color: #000080">import</span></span> Path
+<span style="font-weight: bold"><span style="color: #000080">import</span></span> re
+<span style="font-weight: bold"><span style="color: #000080">import</span></span> shutil
+<span style="font-weight: bold"><span style="color: #000080">import</span></span> sys
+
+sys<span style="color: #990000">.</span>path<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">append</span></span><span style="color: #990000">(</span><span style="font-weight: bold"><span style="color: #000000">str</span></span><span style="color: #990000">(</span><span style="font-weight: bold"><span style="color: #000000">Path</span></span><span style="color: #990000">(</span>__file__<span style="color: #990000">).</span><span style="font-weight: bold"><span style="color: #000000">resolve</span></span><span style="color: #990000">().</span>parent<span style="color: #990000">.</span>parent<span style="color: #990000">))</span>
+<span style="font-weight: bold"><span style="color: #000080">from</span></span> asciidoc <span style="font-weight: bold"><span style="color: #000080">import</span></span> asciidoc <span style="font-style: italic"><span style="color: #9A1900"># noqa: E402</span></span></tt></pre></div></div>
+<div class="paragraph"><p>Some random ranges:</p></div>
+<div class="listingblock">
+<div class="content"><!-- Generator: GNU source-highlight
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><tt><span style="font-weight: bold"><span style="color: #0000FF">def</span></span> <span style="font-weight: bold"><span style="color: #000000">iif</span></span><span style="color: #990000">(</span>condition<span style="color: #990000">,</span> iftrue<span style="color: #990000">,</span> iffalse<span style="color: #990000">=</span>None<span style="color: #990000">):</span>
+<span style="font-style: italic"><span style="color: #9A1900"> """</span></span>
+<span style="font-style: italic"><span style="color: #9A1900"> Immediate if c.f. ternary ?: operator.</span></span>
+<span style="font-style: italic"><span style="color: #9A1900"> False value defaults to '' if the true value is a string.</span></span>
+<span style="font-style: italic"><span style="color: #9A1900"> False value defaults to 0 if the true value is a number.</span></span>
+<span style="font-style: italic"><span style="color: #9A1900"> """</span></span>
+ <span style="font-weight: bold"><span style="color: #0000FF">if</span></span> iffalse <span style="font-weight: bold"><span style="color: #0000FF">is</span></span> None<span style="color: #990000">:</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">if</span></span> <span style="font-weight: bold"><span style="color: #000000">isinstance</span></span><span style="color: #990000">(</span>iftrue<span style="color: #990000">,</span> str<span style="color: #990000">):</span>
+ iffalse <span style="color: #990000">=</span> <span style="color: #FF0000">''</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">if</span></span> <span style="font-weight: bold"><span style="color: #000000">type</span></span><span style="color: #990000">(</span>iftrue<span style="color: #990000">)</span> <span style="font-weight: bold"><span style="color: #0000FF">in</span></span> <span style="color: #990000">(</span>int<span style="color: #990000">,</span> float<span style="color: #990000">):</span>
+ iffalse <span style="color: #990000">=</span> <span style="color: #993399">0</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">if</span></span> condition<span style="color: #990000">:</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">return</span></span> iftrue
+ <span style="font-weight: bold"><span style="color: #0000FF">else</span></span><span style="color: #990000">:</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">return</span></span> iffalse
+
+<span style="font-weight: bold"><span style="color: #0000FF">def</span></span> <span style="font-weight: bold"><span style="color: #000000">normalize_data</span></span><span style="color: #990000">(</span>lines<span style="color: #990000">):</span>
+<span style="font-style: italic"><span style="color: #9A1900"> """</span></span>
+<span style="font-style: italic"><span style="color: #9A1900"> Strip comments and trailing blank strings from lines.</span></span>
+<span style="font-style: italic"><span style="color: #9A1900"> """</span></span>
+ result <span style="color: #990000">=</span> <span style="color: #990000">[</span>s <span style="font-weight: bold"><span style="color: #0000FF">for</span></span> s <span style="font-weight: bold"><span style="color: #0000FF">in</span></span> lines <span style="font-weight: bold"><span style="color: #0000FF">if</span></span> <span style="font-weight: bold"><span style="color: #0000FF">not</span></span> s<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">startswith</span></span><span style="color: #990000">(</span><span style="color: #FF0000">'#'</span><span style="color: #990000">)]</span>
+ <span style="font-weight: bold"><span style="color: #000000">strip_end</span></span><span style="color: #990000">(</span>result<span style="color: #990000">)</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">return</span></span> result
+
+<span style="font-weight: bold"><span style="color: #0000FF">class</span></span> <span style="font-weight: bold"><span style="color: #000000">AsciiDocTest</span></span><span style="color: #990000">(</span>object<span style="color: #990000">):</span>
+
+<span style="font-weight: bold"><span style="color: #0000FF">if</span></span> __name__ <span style="color: #990000">==</span> <span style="color: #FF0000">'__main__'</span><span style="color: #990000">:</span>
+ <span style="font-style: italic"><span style="color: #9A1900"># guarantee a stable timestamp matching the test fixtures</span></span>
+ os<span style="color: #990000">.</span>environ<span style="color: #990000">[</span><span style="color: #FF0000">'SOURCE_DATE_EPOCH'</span><span style="color: #990000">]</span> <span style="color: #990000">=</span> <span style="color: #FF0000">'1038184662'</span>
+ <span style="font-style: italic"><span style="color: #9A1900"># Process command line options.</span></span>
+ <span style="font-weight: bold"><span style="color: #000080">from</span></span> argparse <span style="font-weight: bold"><span style="color: #000080">import</span></span> ArgumentParser
+ parser <span style="color: #990000">=</span> <span style="font-weight: bold"><span style="color: #000000">ArgumentParser</span></span><span style="color: #990000">(</span>
+ description<span style="color: #990000">=</span><span style="color: #FF0000">'Run AsciiDoc conformance tests specified in configuration'</span>
+<span style="font-style: italic"><span style="color: #9A1900"> 'FILE.'</span></span>
+ <span style="color: #990000">)</span>
+ msg <span style="color: #990000">=</span> <span style="color: #FF0000">'Use configuration file CONF_FILE (default configuration file is '</span><span style="color: #990000">\</span>
+<span style="font-style: italic"><span style="color: #9A1900"> 'testasciidoc.conf in testasciidoc.py directory)'</span></span>
+ parser<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">add_argument</span></span><span style="color: #990000">(</span>
+ <span style="color: #FF0000">'-v'</span><span style="color: #990000">,</span>
+ <span style="color: #FF0000">'--version'</span><span style="color: #990000">,</span>
+ action<span style="color: #990000">=</span><span style="color: #FF0000">'version'</span><span style="color: #990000">,</span>
+ version<span style="color: #990000">=</span><span style="color: #FF0000">'%(prog)s {}'</span><span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">format</span></span><span style="color: #990000">(</span>__version__<span style="color: #990000">)</span>
+ <span style="color: #990000">)</span>
+ parser<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">add_argument</span></span><span style="color: #990000">(</span><span style="color: #FF0000">'-f'</span><span style="color: #990000">,</span> <span style="color: #FF0000">'--conf-file'</span><span style="color: #990000">,</span> help<span style="color: #990000">=</span>msg<span style="color: #990000">)</span>
+
+ subparsers <span style="color: #990000">=</span> parser<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">add_subparsers</span></span><span style="color: #990000">(</span>metavar<span style="color: #990000">=</span><span style="color: #FF0000">'command'</span><span style="color: #990000">,</span> dest<span style="color: #990000">=</span><span style="color: #FF0000">'command'</span><span style="color: #990000">)</span>
+ subparsers<span style="color: #990000">.</span>required <span style="color: #990000">=</span> True
+
+ subparsers<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">add_parser</span></span><span style="color: #990000">(</span><span style="color: #FF0000">'list'</span><span style="color: #990000">,</span> help<span style="color: #990000">=</span><span style="color: #FF0000">'List tests'</span><span style="color: #990000">)</span>
+
+ options <span style="color: #990000">=</span> <span style="font-weight: bold"><span style="color: #000000">ArgumentParser</span></span><span style="color: #990000">(</span>add_help<span style="color: #990000">=</span>False<span style="color: #990000">)</span>
+ options<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">add_argument</span></span><span style="color: #990000">(</span><span style="color: #FF0000">'-n'</span><span style="color: #990000">,</span> <span style="color: #FF0000">'--number'</span><span style="color: #990000">,</span> type<span style="color: #990000">=</span>int<span style="color: #990000">,</span> help<span style="color: #990000">=</span><span style="color: #FF0000">'Test number to run'</span><span style="color: #990000">)</span>
+ options<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">add_argument</span></span><span style="color: #990000">(</span><span style="color: #FF0000">'-b'</span><span style="color: #990000">,</span> <span style="color: #FF0000">'--backend'</span><span style="color: #990000">,</span> type<span style="color: #990000">=</span>str<span style="color: #990000">,</span> help<span style="color: #990000">=</span><span style="color: #FF0000">'Backend to run'</span><span style="color: #990000">)</span>
+
+ subparsers<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">add_parser</span></span><span style="color: #990000">(</span><span style="color: #FF0000">'run'</span><span style="color: #990000">,</span> help<span style="color: #990000">=</span><span style="color: #FF0000">'Execute tests'</span><span style="color: #990000">,</span> parents<span style="color: #990000">=[</span>options<span style="color: #990000">])</span>
+
+ subparser <span style="color: #990000">=</span> subparsers<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">add_parser</span></span><span style="color: #990000">(</span>
+ <span style="color: #FF0000">'update'</span><span style="color: #990000">,</span>
+ help<span style="color: #990000">=</span><span style="color: #FF0000">'Regenerate and update test data'</span><span style="color: #990000">,</span>
+ parents<span style="color: #990000">=[</span>options<span style="color: #990000">]</span>
+ <span style="color: #990000">)</span>
+ subparser<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">add_argument</span></span><span style="color: #990000">(</span>
+ <span style="color: #FF0000">'--force'</span><span style="color: #990000">,</span>
+ action<span style="color: #990000">=</span><span style="color: #FF0000">'store_true'</span><span style="color: #990000">,</span>
+ help<span style="color: #990000">=</span><span style="color: #FF0000">'Update all test data overwriting existing data'</span>
+ <span style="color: #990000">)</span>
+
+ args <span style="color: #990000">=</span> parser<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">parse_args</span></span><span style="color: #990000">()</span>
+
+ conffile <span style="color: #990000">=</span> os<span style="color: #990000">.</span>path<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">join</span></span><span style="color: #990000">(</span>os<span style="color: #990000">.</span>path<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">dirname</span></span><span style="color: #990000">(</span>sys<span style="color: #990000">.</span>argv<span style="color: #990000">[</span><span style="color: #993399">0</span><span style="color: #990000">]),</span> <span style="color: #FF0000">'testasciidoc.conf'</span><span style="color: #990000">)</span>
+ force <span style="color: #990000">=</span> <span style="color: #FF0000">'force'</span> <span style="font-weight: bold"><span style="color: #0000FF">in</span></span> args <span style="font-weight: bold"><span style="color: #0000FF">and</span></span> args<span style="color: #990000">.</span>force <span style="font-weight: bold"><span style="color: #0000FF">is</span></span> True
+ <span style="font-weight: bold"><span style="color: #0000FF">if</span></span> args<span style="color: #990000">.</span>conf_file <span style="font-weight: bold"><span style="color: #0000FF">is</span></span> <span style="font-weight: bold"><span style="color: #0000FF">not</span></span> None<span style="color: #990000">:</span>
+ conffile <span style="color: #990000">=</span> args<span style="color: #990000">.</span>conf_file
+ <span style="font-weight: bold"><span style="color: #0000FF">if</span></span> <span style="font-weight: bold"><span style="color: #0000FF">not</span></span> os<span style="color: #990000">.</span>path<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">isfile</span></span><span style="color: #990000">(</span>conffile<span style="color: #990000">):</span>
+ <span style="font-weight: bold"><span style="color: #000000">message</span></span><span style="color: #990000">(</span><span style="color: #FF0000">'missing CONF_FILE: %s'</span> <span style="color: #990000">%</span> conffile<span style="color: #990000">)</span>
+ sys<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">exit</span></span><span style="color: #990000">(</span><span style="color: #993399">1</span><span style="color: #990000">)</span>
+ tests <span style="color: #990000">=</span> <span style="font-weight: bold"><span style="color: #000000">AsciiDocTests</span></span><span style="color: #990000">(</span>conffile<span style="color: #990000">)</span>
+ cmd <span style="color: #990000">=</span> args<span style="color: #990000">.</span>command
+ number <span style="color: #990000">=</span> None
+ backend <span style="color: #990000">=</span> None
+ <span style="font-weight: bold"><span style="color: #0000FF">if</span></span> <span style="color: #FF0000">'number'</span> <span style="font-weight: bold"><span style="color: #0000FF">in</span></span> args<span style="color: #990000">:</span>
+ number <span style="color: #990000">=</span> args<span style="color: #990000">.</span>number
+ <span style="font-weight: bold"><span style="color: #0000FF">if</span></span> <span style="color: #FF0000">'backend'</span> <span style="font-weight: bold"><span style="color: #0000FF">in</span></span> args<span style="color: #990000">:</span>
+ backend <span style="color: #990000">=</span> args<span style="color: #990000">.</span>backend
+ <span style="font-weight: bold"><span style="color: #0000FF">if</span></span> backend <span style="font-weight: bold"><span style="color: #0000FF">and</span></span> backend <span style="font-weight: bold"><span style="color: #0000FF">not</span></span> <span style="font-weight: bold"><span style="color: #0000FF">in</span></span> BACKENDS<span style="color: #990000">:</span>
+ <span style="font-weight: bold"><span style="color: #000000">message</span></span><span style="color: #990000">(</span><span style="color: #FF0000">'illegal BACKEND: {:s}'</span><span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">format</span></span><span style="color: #990000">(</span>backend<span style="color: #990000">))</span>
+ sys<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">exit</span></span><span style="color: #990000">(</span><span style="color: #993399">1</span><span style="color: #990000">)</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">if</span></span> number <span style="font-weight: bold"><span style="color: #0000FF">is</span></span> <span style="font-weight: bold"><span style="color: #0000FF">not</span></span> None <span style="font-weight: bold"><span style="color: #0000FF">and</span></span> <span style="color: #990000">(</span>number <span style="color: #990000">&lt;</span> <span style="color: #993399">1</span> <span style="font-weight: bold"><span style="color: #0000FF">or</span></span> number <span style="color: #990000">&gt;</span> <span style="font-weight: bold"><span style="color: #000000">len</span></span><span style="color: #990000">(</span>tests<span style="color: #990000">.</span>tests<span style="color: #990000">)):</span>
+ <span style="font-weight: bold"><span style="color: #000000">message</span></span><span style="color: #990000">(</span><span style="color: #FF0000">'illegal test NUMBER: {:d}'</span><span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">format</span></span><span style="color: #990000">(</span>number<span style="color: #990000">))</span>
+ sys<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">exit</span></span><span style="color: #990000">(</span><span style="color: #993399">1</span><span style="color: #990000">)</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">if</span></span> cmd <span style="color: #990000">==</span> <span style="color: #FF0000">'run'</span><span style="color: #990000">:</span>
+ tests<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">run</span></span><span style="color: #990000">(</span>number<span style="color: #990000">,</span> backend<span style="color: #990000">)</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">if</span></span> tests<span style="color: #990000">.</span>failed<span style="color: #990000">:</span>
+ sys<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">exit</span></span><span style="color: #990000">(</span><span style="color: #993399">1</span><span style="color: #990000">)</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">elif</span></span> cmd <span style="color: #990000">==</span> <span style="color: #FF0000">'update'</span><span style="color: #990000">:</span>
+ tests<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">update</span></span><span style="color: #990000">(</span>number<span style="color: #990000">,</span> backend<span style="color: #990000">,</span> force<span style="color: #990000">=</span>force<span style="color: #990000">)</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">elif</span></span> cmd <span style="color: #990000">==</span> <span style="color: #FF0000">'list'</span><span style="color: #990000">:</span>
+ tests<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">list</span></span><span style="color: #990000">()</span></tt></pre></div></div>
+<div class="paragraph"><p>Using ; as separator:</p></div>
+<div class="listingblock">
+<div class="content"><!-- Generator: GNU source-highlight
+by Lorenzo Bettini
+http://www.lorenzobettini.it
+http://www.gnu.org/software/src-highlite -->
+<pre><tt><span style="font-weight: bold"><span style="color: #0000FF">def</span></span> <span style="font-weight: bold"><span style="color: #000000">iif</span></span><span style="color: #990000">(</span>condition<span style="color: #990000">,</span> iftrue<span style="color: #990000">,</span> iffalse<span style="color: #990000">=</span>None<span style="color: #990000">):</span>
+<span style="font-style: italic"><span style="color: #9A1900"> """</span></span>
+<span style="font-style: italic"><span style="color: #9A1900"> Immediate if c.f. ternary ?: operator.</span></span>
+<span style="font-style: italic"><span style="color: #9A1900"> False value defaults to '' if the true value is a string.</span></span>
+<span style="font-style: italic"><span style="color: #9A1900"> False value defaults to 0 if the true value is a number.</span></span>
+<span style="font-style: italic"><span style="color: #9A1900"> """</span></span>
+ <span style="font-weight: bold"><span style="color: #0000FF">if</span></span> iffalse <span style="font-weight: bold"><span style="color: #0000FF">is</span></span> None<span style="color: #990000">:</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">if</span></span> <span style="font-weight: bold"><span style="color: #000000">isinstance</span></span><span style="color: #990000">(</span>iftrue<span style="color: #990000">,</span> str<span style="color: #990000">):</span>
+ iffalse <span style="color: #990000">=</span> <span style="color: #FF0000">''</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">if</span></span> <span style="font-weight: bold"><span style="color: #000000">type</span></span><span style="color: #990000">(</span>iftrue<span style="color: #990000">)</span> <span style="font-weight: bold"><span style="color: #0000FF">in</span></span> <span style="color: #990000">(</span>int<span style="color: #990000">,</span> float<span style="color: #990000">):</span>
+ iffalse <span style="color: #990000">=</span> <span style="color: #993399">0</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">if</span></span> condition<span style="color: #990000">:</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">return</span></span> iftrue
+ <span style="font-weight: bold"><span style="color: #0000FF">else</span></span><span style="color: #990000">:</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">return</span></span> iffalse
+
+<span style="font-weight: bold"><span style="color: #0000FF">def</span></span> <span style="font-weight: bold"><span style="color: #000000">normalize_data</span></span><span style="color: #990000">(</span>lines<span style="color: #990000">):</span>
+<span style="font-style: italic"><span style="color: #9A1900"> """</span></span>
+<span style="font-style: italic"><span style="color: #9A1900"> Strip comments and trailing blank strings from lines.</span></span>
+<span style="font-style: italic"><span style="color: #9A1900"> """</span></span>
+ result <span style="color: #990000">=</span> <span style="color: #990000">[</span>s <span style="font-weight: bold"><span style="color: #0000FF">for</span></span> s <span style="font-weight: bold"><span style="color: #0000FF">in</span></span> lines <span style="font-weight: bold"><span style="color: #0000FF">if</span></span> <span style="font-weight: bold"><span style="color: #0000FF">not</span></span> s<span style="color: #990000">.</span><span style="font-weight: bold"><span style="color: #000000">startswith</span></span><span style="color: #990000">(</span><span style="color: #FF0000">'#'</span><span style="color: #990000">)]</span>
+ <span style="font-weight: bold"><span style="color: #000000">strip_end</span></span><span style="color: #990000">(</span>result<span style="color: #990000">)</span>
+ <span style="font-weight: bold"><span style="color: #0000FF">return</span></span> result
+</tt></pre></div></div>
+</div>
+</div>
+</div>
+<div id="footnotes"><hr></div>
+<div id="footer">
+<div id="footer-text">
+Last updated
+ 2002-11-25 00:37:42 UTC
+</div>
+</div>
+</body>
+</html>
diff --git a/tests/data/include-lines-test.txt b/tests/data/include-lines-test.txt
new file mode 100644
index 0000000..6f0fd2c
--- /dev/null
+++ b/tests/data/include-lines-test.txt
@@ -0,0 +1,23 @@
+include line test
+=================
+
+The imports:
+
+[source,python]
+----
+include::./dummy_file.py[lines="6..15"]
+----
+
+Some random ranges:
+
+[source,python]
+----
+include::./dummy_file.py[lines="29..44,61..68,70,398..-1"]
+----
+
+Using ; as separator:
+
+[source,python]
+----
+include::./dummy_file.py[lines="29..44;61..68"]
+----
diff --git a/tests/testasciidoc.conf b/tests/testasciidoc.conf
index 4c3ca82..f0d7dd4 100644
--- a/tests/testasciidoc.conf
+++ b/tests/testasciidoc.conf
@@ -954,3 +954,13 @@ data/newline.txt
{'toc':True}
%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%
+Include Line Ranges
+
+% backends
+['html5']
+
+% name
+include-lines
+
+% source
+data/include-lines-test.txt