2021-01-08 06:39:40 +00:00
|
|
|
""" md
|
|
|
|
## Usage
|
|
|
|
|
2021-02-09 17:47:02 +00:00
|
|
|
Once this plugin is [installed](../README.md#installation), just replace
|
|
|
|
the plugin name `simple` with `semiliterate` in your `mkdocs.yml` file.
|
|
|
|
It accepts all of the same parameters, so `mkdocs` will still work as before,
|
|
|
|
and you will have immediate access to all of the following extensions.
|
|
|
|
(Note that this documentation assumes a familiarity with the
|
2021-01-08 18:40:06 +00:00
|
|
|
[usage](https://athackst.github.io/mkdocs-simple-plugin/mkdocs_simple_plugin/plugin/)
|
|
|
|
of the `simple` plugin.)
|
2021-01-08 19:36:03 +00:00
|
|
|
"""
|
|
|
|
|
|
|
|
from mkdocs import utils
|
|
|
|
from mkdocs.config import config_options
|
2021-01-14 05:36:29 +00:00
|
|
|
from mkdocs_simple_plugin.plugin import SimplePlugin, StreamExtract
|
2021-01-08 19:36:03 +00:00
|
|
|
|
2021-01-29 00:20:06 +00:00
|
|
|
import os
|
2021-01-08 19:36:03 +00:00
|
|
|
import re
|
2021-02-12 06:31:32 +00:00
|
|
|
import subprocess
|
|
|
|
import tempfile
|
2021-01-08 19:36:03 +00:00
|
|
|
import yaml
|
|
|
|
|
|
|
|
|
|
|
|
class StreamInclusion(StreamExtract):
|
2021-02-09 17:47:02 +00:00
|
|
|
r""" md An extension of the StreamExtract class which adds
|
2021-01-08 06:39:40 +00:00
|
|
|
|
|
|
|
### Inclusion syntax
|
|
|
|
|
2021-01-08 19:36:03 +00:00
|
|
|
While extracting content from a file (because it matches one of the
|
|
|
|
`semiliterate` patterns, rather than just one of the `include_extensions`),
|
|
|
|
an unescaped expression of the form
|
|
|
|
|
|
|
|
`{! FILENAME YAML !}`
|
|
|
|
|
|
|
|
(which may span multiple lines) will trigger file inclusion. The FILENAME may
|
|
|
|
be a bare word, in which case it cannot contain whitespace, or it may be
|
|
|
|
enclosed in single or double quotes. Note that FILENAME is interpreted relative
|
|
|
|
to the directory in which the file containing the `{! .. !}` expression
|
|
|
|
resides. The YAML is interpreted exactly as the extraction options to a
|
|
|
|
`semiliterate` item as
|
2021-02-09 17:47:02 +00:00
|
|
|
[documented](https://athackst.github.io/mkdocs-simple-plugin/mkdocs_simple_plugin/plugin/index.html#semiliterate)
|
2021-01-08 19:36:03 +00:00
|
|
|
for the `simple` extension. The text extracted from FILENAME
|
|
|
|
is interpolated at the current location in the file currently being written.
|
|
|
|
Recursive inclusion is supported.
|
|
|
|
|
2021-02-09 17:47:02 +00:00
|
|
|
The simplest example of such an inclusion directive is just
|
|
|
|
`{! boilerplate.md !}`, which (because of the conventions for extraction
|
|
|
|
parameters) simply interpolates the entire contents of `boilerplate.md`
|
|
|
|
at the current location.
|
|
|
|
|
|
|
|
For an example that uses more of the extraction parameters, the current
|
|
|
|
version number of mkdocs-semiliterate is extracted into the
|
|
|
|
[Overview](../README.md) of this documentation via
|
|
|
|
|
|
|
|
` {! ../README.md extract: { start: 'repo:.*(\{!.*!\})' }
|
|
|
|
terminate: Rationale
|
|
|
|
!}`
|
|
|
|
|
|
|
|
to take advantage of the beginning of the `setup.cfg` file:
|
|
|
|
```
|
|
|
|
{! ../setup.cfg terminate: long !}...
|
|
|
|
```
|
|
|
|
|
|
|
|
(and of course both of the code snippets just above are extracted into this
|
|
|
|
page with `{! ... !}`, as you can see in the
|
|
|
|
[source code](https://code.studioinfinity.org/glen/mkdocs-semiliterate/src/branch/main/mkdocs_semiliterate/plugin.py)
|
|
|
|
for the plugin.)
|
|
|
|
|
|
|
|
Note that a `{! ... !}` directive must be in a line that semiliterate would
|
|
|
|
normally copy. That is, semiliterate does not examine lines after
|
|
|
|
the `terminate` regexp, or when no mode of extraction is active.
|
|
|
|
It also doesn't check any text written from lines that match these
|
|
|
|
special expressions, including `start` and `stop`.
|
2021-01-15 17:44:25 +00:00
|
|
|
Moreover, on such normally-transcribed lines,
|
2021-01-09 16:11:30 +00:00
|
|
|
it's the text **after** the application of any semiliterate `replace`ments that
|
2021-01-08 19:36:03 +00:00
|
|
|
is checked for `{! ... !}`.
|
2021-02-09 17:47:02 +00:00
|
|
|
""" # noqa: E501
|
2021-01-08 19:36:03 +00:00
|
|
|
|
|
|
|
include_open = re.compile(r'''(?<![`\\])(\{\!\s*)([\s'"])''')
|
|
|
|
include_quoted_file = re.compile(
|
2021-01-10 17:12:49 +00:00
|
|
|
r'''(['"])(?P<fn>.*?)\1\s+(?P<yml>[\s\S]*?)\s?\!\}''')
|
|
|
|
include_bare_file = re.compile(r'\s(?P<fn>.*?)\s+(?P<yml>[\s\S]*?)\s?\!\}')
|
2021-01-08 19:36:03 +00:00
|
|
|
|
|
|
|
def extract_line(self, line):
|
|
|
|
"""Copy line to the output stream, applying all specified replacements
|
|
|
|
and handling inclusion syntax.
|
|
|
|
"""
|
|
|
|
line = self.replace_line(line)
|
|
|
|
include_match = StreamInclusion.include_open.search(line)
|
|
|
|
if not include_match:
|
|
|
|
self.transcribe(line)
|
|
|
|
return
|
|
|
|
# OK, we have found (the start of) an inclusion and must process it
|
|
|
|
preamble = line[:include_match.start()]
|
|
|
|
remainder = line[include_match.end(1):]
|
2021-02-12 05:35:58 +00:00
|
|
|
doublequoted = False
|
2021-01-08 19:36:03 +00:00
|
|
|
body_pattern = StreamInclusion.include_quoted_file
|
|
|
|
if include_match[2].isspace():
|
|
|
|
body_pattern = StreamInclusion.include_bare_file
|
2021-02-12 05:35:58 +00:00
|
|
|
elif include_match[2] == '"':
|
|
|
|
doublequoted = True
|
2021-01-08 19:36:03 +00:00
|
|
|
body_match = body_pattern.search(remainder)
|
|
|
|
if not body_match:
|
|
|
|
for extra_line in self.input_stream:
|
|
|
|
remainder += self.replace_line(extra_line)
|
|
|
|
body_match = body_pattern.search(remainder)
|
|
|
|
if body_match:
|
|
|
|
break
|
|
|
|
if not body_match:
|
2021-01-10 17:12:49 +00:00
|
|
|
errmsg = "semiliterate: End of file while scanning for `!}`"
|
|
|
|
utils.log.error(errmsg)
|
|
|
|
raise EOFError(errmsg)
|
2021-02-12 05:35:58 +00:00
|
|
|
filename = body_match['fn']
|
2021-02-12 06:31:32 +00:00
|
|
|
gitextract = False
|
2021-02-12 16:50:38 +00:00
|
|
|
dq_doc = """ md
|
|
|
|
### Double-quoted filenames and Git extraction
|
|
|
|
|
|
|
|
Standard Python escape sequences in double-quoted filenames are interpreted
|
|
|
|
as usual; for example you can write
|
|
|
|
```
|
|
|
|
{! ../tests/fixtures/quoted-filename/README.md extract:
|
|
|
|
start: '(.*!.*)'
|
|
|
|
stop: '\s'
|
|
|
|
!}
|
|
|
|
```
|
|
|
|
to include a file whose name (`snippet/Say "Don't"`, in this case) has both
|
|
|
|
double and single quotes.
|
|
|
|
|
|
|
|
Further, `semiliterate` supports a special escape to extract a file from the
|
|
|
|
Git archive of the project (presuming it is under Git version control) and then
|
|
|
|
include content from that file. For example, you could write
|
|
|
|
```
|
|
|
|
{! ../tests/fixtures/git-inclusion/README.md extract:
|
|
|
|
start: '(.*!.*)'
|
|
|
|
stop: '\s'
|
|
|
|
!}
|
|
|
|
```
|
|
|
|
|
|
|
|
to extract content starting after the `### install` line from the
|
|
|
|
`mkdocs.yml` file in the Git commit of this repository
|
|
|
|
tagged `0.1.0`. This feature is primarily useful if you are documenting the
|
|
|
|
development or changes to a project over time, and want to be sure that
|
|
|
|
material included in your documentation does _not_ change as the project
|
|
|
|
progresses. (This behavior is as opposed to the usual case, in which you want
|
|
|
|
your documentation to incorporate the most up-to-date version of extracted
|
|
|
|
content.)
|
|
|
|
|
|
|
|
The precise behavior for a FILENAME argument in a `{! ... !}` inclusion of the
|
|
|
|
form
|
|
|
|
|
|
|
|
`"\git SPECIFIER"`
|
|
|
|
|
|
|
|
is that the output of `git show SPECIFIER` is written to a temporary file,
|
|
|
|
and that file is extracted from.
|
|
|
|
"""
|
2021-02-12 05:35:58 +00:00
|
|
|
if doublequoted:
|
2021-02-12 06:31:32 +00:00
|
|
|
if filename[:5] == r'\git ':
|
|
|
|
gitextract = True
|
|
|
|
filename = filename[5:]
|
2021-02-12 05:35:58 +00:00
|
|
|
filename = (filename.encode('latin-1', 'backslashreplace')
|
|
|
|
.decode('unicode-escape'))
|
|
|
|
include_path = self.include_root + '/' + filename
|
2021-02-12 06:31:32 +00:00
|
|
|
if gitextract:
|
|
|
|
(write_handle, include_path) = tempfile.mkstemp()
|
|
|
|
utils.log.info(
|
|
|
|
f"semiliterate: extracting {filename} to {include_path}")
|
|
|
|
contents = subprocess.check_output(['git', 'show', filename])
|
|
|
|
os.write(write_handle, contents)
|
|
|
|
os.close(write_handle)
|
2021-01-08 19:36:03 +00:00
|
|
|
new_root = re.match(r'(.*)/', include_path)[1]
|
2021-01-14 05:36:29 +00:00
|
|
|
try:
|
|
|
|
include_parameters = yaml.safe_load(body_match['yml'])
|
|
|
|
except Exception as err:
|
|
|
|
newmsg = (f"While attempting to include '{include_path}', could"
|
|
|
|
+ f" not parse yaml '{body_match['yml']}'.")
|
|
|
|
if hasattr(err, 'message'):
|
|
|
|
raise SyntaxError(
|
|
|
|
f"{newmsg} YAML parser reports: {err.message}")
|
|
|
|
raise SyntaxError(f"{newmsg} Caught exception: {str(err)}")
|
2021-01-10 17:12:49 +00:00
|
|
|
if not include_parameters:
|
|
|
|
include_parameters = {}
|
2021-01-08 19:36:03 +00:00
|
|
|
with open(include_path) as include_file:
|
|
|
|
self.transcribe(preamble)
|
|
|
|
inclusion = StreamInclusion(
|
|
|
|
include_file, self.output_stream, include_root=new_root,
|
|
|
|
**include_parameters)
|
2021-01-14 05:36:29 +00:00
|
|
|
if inclusion.extract():
|
2021-01-08 19:36:03 +00:00
|
|
|
self.wrote_something = True
|
|
|
|
self.transcribe(remainder[body_match.end():])
|
2021-01-08 06:39:40 +00:00
|
|
|
|
|
|
|
|
|
|
|
class SemiliteratePlugin(SimplePlugin):
|
2021-01-09 16:11:30 +00:00
|
|
|
r""" md An extension of the mkdocs-simple-plugin
|
2021-01-14 05:36:29 +00:00
|
|
|
|
2021-02-12 16:50:38 +00:00
|
|
|
### Universal block-comment markdown
|
|
|
|
|
|
|
|
By default, `semiliterate` scans for block-comment markdown `/** md` ... `**/`
|
|
|
|
in all files with _any_ extension, as it's valid in so many disparate languages.
|
|
|
|
(As opposed to `simple`, which defaults to searching for such markdown in a
|
|
|
|
specific list of file types.)
|
|
|
|
"""
|
2021-01-14 05:36:29 +00:00
|
|
|
|
2021-02-12 16:50:38 +00:00
|
|
|
super_sdict = dict(SimplePlugin.config_scheme)
|
|
|
|
super_semi_dflt = super_sdict['semiliterate'].default
|
|
|
|
semi_dflt = [b if 'js' not in b['pattern'] else dict(b, pattern=r'\.')
|
|
|
|
for b in super_semi_dflt]
|
|
|
|
altered_config_scheme = dict(
|
|
|
|
super_sdict,
|
|
|
|
semiliterate=config_options.Type(list, default=semi_dflt)).items()
|
|
|
|
|
|
|
|
add_param_doc = r""" md
|
2021-01-08 18:40:06 +00:00
|
|
|
### Additional plugin parameters
|
|
|
|
|
|
|
|
`semiliterate` adds a couple of new plugin parameters to further tailor its
|
2021-01-09 16:11:30 +00:00
|
|
|
behavior as compared to `simple`. They are described in this section, with
|
2021-01-08 18:40:06 +00:00
|
|
|
default values in parentheses at the beginning of each entry.
|
|
|
|
|
2021-01-15 17:44:25 +00:00
|
|
|
{! plugin.py extract:
|
2021-01-14 05:36:29 +00:00
|
|
|
start: '[*]altered_config_scheme'
|
2021-01-08 19:36:03 +00:00
|
|
|
replace:
|
2021-01-09 18:17:34 +00:00
|
|
|
- ["\\('(.*)',\\s*$", '\1\n']
|
|
|
|
- ['config_options.Type.*?default=([^\)]*)', ': (\1)']
|
2021-01-08 19:36:03 +00:00
|
|
|
- '^\s*#(.*\s*)$'
|
2021-01-15 17:44:25 +00:00
|
|
|
terminate: '^\s*\)'
|
2021-01-08 19:36:03 +00:00
|
|
|
!}
|
2021-01-08 18:40:06 +00:00
|
|
|
"""
|
|
|
|
config_scheme = (
|
2021-01-09 18:17:34 +00:00
|
|
|
# Note documentation of each new parameter **follows** the parameter.
|
2021-01-14 05:36:29 +00:00
|
|
|
*altered_config_scheme,
|
2021-01-29 00:20:06 +00:00
|
|
|
('exclude_extensions',
|
|
|
|
config_options.Type(list, default=['.o'])),
|
|
|
|
# Files whose name contains a string in this list will not be processed
|
|
|
|
# by `semiliterate`, regardless of whether they might match
|
|
|
|
# `include_extensions`, the `semiliterate` patterns, or standard
|
|
|
|
# Markdown.
|
2021-01-09 18:17:34 +00:00
|
|
|
('copy_standard_markdown',
|
|
|
|
config_options.Type(bool, default=False)),
|
2021-01-08 19:36:03 +00:00
|
|
|
# Whether to add MkDocs' list of standard Markdown extensions to the
|
|
|
|
# `include_extensions` parameter so that Markdown files will be
|
|
|
|
# directly copied to the docsite. Note that the `simple` behavior
|
|
|
|
# corresponds to a _true_ value for `copy_standard_markdown`, but
|
|
|
|
# `semiliterate` still incorporates all standard Markdown files
|
2021-01-09 18:17:34 +00:00
|
|
|
# because of the following `extract_standard_markdown` parameter.
|
|
|
|
('extract_standard_markdown',
|
2021-02-09 06:26:39 +00:00
|
|
|
config_options.Type(dict, default={}))
|
2021-01-09 18:17:34 +00:00
|
|
|
# If the `enable` key of this dict parameter is true
|
2021-01-14 05:36:29 +00:00
|
|
|
# (it defaults to the opposite of `copy_standard_markdown`),
|
2021-01-09 18:17:34 +00:00
|
|
|
# it adds a semiliterate block causing extraction (and hence
|
|
|
|
# include-directive processing) from all standard Markdown files
|
|
|
|
# (as defined by MkDocs). The remaining keys of this parameter are
|
|
|
|
# included as parameters of that semiliterate block. Thus, the
|
2021-01-14 05:36:29 +00:00
|
|
|
# default values of the parameters arrange for Markdown files to be
|
2021-01-09 18:17:34 +00:00
|
|
|
# copied "as-is", except possibly for embedded inclusions.
|
2021-01-14 05:36:29 +00:00
|
|
|
# On the other hand, setting this parameter to `{enable: false}`
|
|
|
|
# (which is also the default when `copy_standard_markdown` is true)
|
2021-02-09 17:47:02 +00:00
|
|
|
# will prevent automatic extraction (and hence disable
|
|
|
|
# inclusion-directive processing) from standard Markdown files.
|
2021-01-08 18:40:06 +00:00
|
|
|
)
|
|
|
|
|
2021-01-09 16:11:30 +00:00
|
|
|
def build_docs(self):
|
2021-01-29 00:20:06 +00:00
|
|
|
self.exclude_extensions = self.config['exclude_extensions']
|
2021-01-14 05:36:29 +00:00
|
|
|
dflt_enable = False
|
2021-01-08 18:40:06 +00:00
|
|
|
if not self.config['copy_standard_markdown']:
|
|
|
|
self.include_extensions = self.config['include_extensions']
|
2021-01-14 05:36:29 +00:00
|
|
|
dflt_enable = True
|
|
|
|
if self.config['extract_standard_markdown'].get('enable', dflt_enable):
|
2021-01-09 18:17:34 +00:00
|
|
|
ext_pat = '|'.join(re.escape(s) for s in utils.markdown_extensions)
|
|
|
|
self.semiliterate.append(dict(
|
|
|
|
pattern=re.compile(f"^(.*(?:{ext_pat}))$"),
|
|
|
|
destination=r'\1',
|
|
|
|
**self.config['extract_standard_markdown']))
|
2021-01-29 00:20:06 +00:00
|
|
|
paths = []
|
|
|
|
for root, directories, files in os.walk("."):
|
|
|
|
if self.in_include_directory(root):
|
|
|
|
document_root = self.build_docs_dir + root[1:]
|
|
|
|
for f in files:
|
|
|
|
if any(ext in f for ext in self.exclude_extensions):
|
|
|
|
continue
|
|
|
|
paths.extend(self.copy_file(root, f, document_root))
|
|
|
|
paths.extend(self.extract_from(root, f, document_root))
|
|
|
|
directories[:] = [d for d in directories
|
|
|
|
if self.in_search_directory(d, root)]
|
|
|
|
return paths
|
2021-01-08 19:36:03 +00:00
|
|
|
|
|
|
|
def try_extraction(self, original_file, root, new_file, **kwargs):
|
2021-01-14 05:36:29 +00:00
|
|
|
extraction = StreamInclusion(
|
|
|
|
original_file, new_file, include_root=root, **kwargs)
|
|
|
|
return extraction.extract()
|