Skip to content

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:

mytitle

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.

Back to top