Markdown Replace
Markdown Replace¤
Allows to add replacements into the markdown source, which are replaced by
- values or
- function call results,
given by a hot reloaded configurable python module.
Usage: lcd-md-replace
You set up replacements in a python file (default is: docs/mdreplace.py
), which must have a table
attribute, either dict or callable.
When callable it will be called with the mkdocs config and must return a replacement dict.
Features¤
- The values of the replacement dict can themselves be callable, and if so, are called at replacement time with contextual information:
replace(
mdblock=md,
plugin=self,
plugin_file=__file__,
config=config,
page=page,
markdown=markdown,
)
- If the callable does not require kw args (e.g.
time.ctime
) we will not pass them - The callable can return a replacement for the whole line, by returning a dict like
{'line': ....}
, i.e. with a "line" key. - The callable can also return content added to top and bottom of the markdown (e.g. for style
defs), in the returned dict. Keys are
markdown_header
,markdown_footer
. See e.g. admons.py - If the replace values are lists (also as returned by the callable), they will be properly indented as multiline text.
Controlling Replacements Within Fenced Blocks¤
- fenced blocks are omitted EXCEPT:
- if the replacement key is specified like this
key:all:
- then even:key:
in fenced blocks is replaced
Always Present Keys¤
:head:
:foot:
These keys (with the colons) will always be found, on any page - you can insert e.g. page level styles.
???? "Example"
Say we wanted *every* page to have the full width style. Instead of inserting everywhere the
`:cssfullwidth:` builtin (see below), we add this into our custom `mdreplace.py`:
``` python
from lcdoc.mkdocs.replace import BuiltInReplacements as BI
table = {
':head:' : BI.cssfullwidth,
'foo' : ...
```
Config¤
seperator
: ':' by default.
Example:':curtime:'
, for{"cur_time": time.ctime}
based replacements.replacement_file
: when not starting with '/' we'll prefix with docs_dir. Default: "mdreplace.py"
Built in Replacments¤
Some keys are hardwired (can be overwritten though, within your replacement module):
class css:
def csspdf(**kw):
"""
No loose headers in pdf print outs via a CSS hack.
(page-break-after:avoid not supported in browsers)
"""
s = '''
@page { size: A4; }
@media print {
body { font-family: Arial, Helvetica, sans-serif; }
h2,h3,h4 { page-break-inside: avoid; }
h2::after,h3::after { content: ""; display: block; height: 200px; margin-bottom: -200px; }
h4::after,h5::after { content: ""; display: block; height: 100px; margin-bottom: -100px; }
}
'''
return style(s)
# this widens the content on big screens to full width:
width = lambda max_width='none', min_width='76.25em': style(
f'''
@media only screen and (min-width: {min_width}) {{
.md-main__inner {{
max-width: {max_width};
}}
.md-sidebar--primary {{
left: 0;
}}
.md-sidebar--secondary {{
right: 0;
margin-left: 0;
-webkit-transform: none;
transform: none;
}}
}}
'''
)
fullwidth = width(max_width='none', min_width='76.25em')
class BuiltInReplacements(css):
# inserts the current time at process start:
ctime = time.strftime('%a, %d %b %Y %Hh GMT', time.localtime())
def pthbase(page, **kw):
"""Relative path to base.
Add media to docs/img/foo.svg and say: <img src=":pthbase:img/foo.svg" />
"""
l = page.url.split('/')
return '../' * (len(l) - 1)
def srcref(**kw):
"""Finds and links source code
Examples:
:srcref:src/lcdoc/mkdocs/replace/__init__.py
:srcref:fn=src/lcdoc/mkdocs/replace/__init__.py
:srcref:src/lcdoc/mkdocs/replace/__init__.py=somematch
:srcref:fn=src/lcdoc/mkdocs/replace/__init__.py,m=built_in_replacements,t=hardwired
"""
line = kw['line']
fn = line.split(':srcref:', 1)[1].split(' ', 1)[0]
if fn[-1] in {',', ')', ']', '}'}:
fn = fn[:-1]
repl = ':srcref:' + fn
if not ',' in fn:
if '=' in fn:
l = fn.split('=')
if l[0] == 'fn':
spec = {'fn': l[1]}
else:
spec = {'fn': l[0], 'm': l[1]}
else:
spec = {'fn': fn}
else:
try:
spec = dict([kv.split('=', 1) for kv in fn.split(',')])
except Exception as ex:
app.error('inline_srclink failed', line=line, page=kw['page'])
return {'line': line}
spec['t'] = spec.get('t', '`%s`' % spec['fn']) # title default: file path
if spec['t'] == 'm':
spec['t'] = spec['m']
# if 'changelog' in line: breakpoint() # FIXME BREAKPOINT
l = srclink(spec['fn'], kw['config'], match=spec.get('m'), title=spec['t'])
return {'line': line.replace(repl, l['link'])}
Example¤
This repo's docs/mdreplace.py
.
Here is a srcref
usage example with title and match string:
:srcref:fn=src/lcdoc/lp.py,m=remote_content,t=mytitle
We find the first occurance of the match string (m=...) in the file and link to it with given title (t=...):
Result:
Custom Admonitions¤
Based on these instructions, we provide straightforward custom admonitions via this plugin.
Example:
!!! note
Begin
!!! :dev: "My Developer Tip"
Some notes for developers....
End
renders:
Note
Begin
"My Developer Tip"
Some notes for developers....
End
Currently the following custom admonitions are defined out of the box:
LP Source:
```python lp:python addsrc
import json
from lcdoc.mkdocs.replace.admons import cust_admons
print(json.dumps(cust_admons, indent=4))
```
Result:
{
"dev": {
"title": "Developer Tip",
"ico": "fontawesome/brands/dev.svg",
"col": "rgb(139, 209, 36)"
}
}
How To Add a Custom Admonition¤
In your mdreplace
file:
from lcdoc.mkdocs.replace import admons
# add ours to the predefined ones:
ico = '<svg ....' # raw svg from anywhere.
ico = 'https://twemoji.maxcdn.com/v/latest/svg/1f4f7.svg' # url
ico = 'material/camera-account.svg' # file in your site-directories/material/.icons
admons.cust_admons['myadmon'] = dict(title="My Default Title", ico=ico, col='rgb(0, 0, 255)', [bgcol=rgba...])
table = {...} # our other replace defs
table.update(admons.admons('dev', 'myadmon', ...))
See also the mdreplace.py file in this repo.
The style definition is added once into your page, per used custom admonition. We do not interfere with any custom style definition in your project.