#|default_exp frontmatter
A YAML and formatted-markdown frontmatter processor
#|export
from nbdev.imports import *
from nbdev.process import *
from execnb.nbio import *
from fastcore.imports import *
import yaml
#|hide
from fastcore.test import *
#|hide
_test_file = '../tests/docs_test.ipynb'
#|export
_re_fm = re.compile(r'''^---\s*
(.*\S+.*)
---\s*$''', flags=re.DOTALL)
def _fm2dict(s:str):
"Load YAML frontmatter into a `dict`"
match = _re_fm.search(s.strip())
return yaml.safe_load(match.group(1)) if match else {}
def _md2dict(s:str):
"Convert H1 formatted markdown cell to frontmatter dict"
if '#' not in s: return {}
m = re.search(r'^#\s+(\S.*?)\s*$', s, flags=re.MULTILINE)
if not m: return {}
res = {'title': m.group(1)}
m = re.search(r'^>\s+(\S.*?)\s*$', s, flags=re.MULTILINE)
if m: res['description'] = m.group(1)
r = re.findall(r'^-\s+(\S.*:.*\S)\s*$', s, flags=re.MULTILINE)
if r:
try: res.update(yaml.safe_load('\n'.join(r)))
except Exception as e: warn(f'Failed to create YAML dict for:\n{r}\n\n{e}\n')
return res
#|export
class FrontmatterProc(Processor):
"A YAML and formatted-markdown frontmatter processor"
def begin(self): self.fm = {}
def _default_exp_(self, cell, exp): self.default_exp = exp
def _update(self, f, cell):
s = cell.get('source')
if not s: return
d = f(s)
if not d: return
self.fm.update(d)
cell.source = None
def cell(self, cell):
if cell.cell_type=='raw': self._update(_fm2dict, cell)
elif cell.cell_type=='markdown' and 'title' not in self.fm: self._update(_md2dict, cell)
def end(self):
self.nb.frontmatter_ = self.fm
if not self.fm: return
exp = getattr(self, 'default_exp', None)
if exp: self.fm.update({'output-file': exp+'.html'})
s = f'---\n{yaml.dump(self.fm)}\n---'
self.nb.cells.insert(0, mk_cell(s, 'raw'))
YAML frontmatter can be added to notebooks in one of two ways:
---
as the first and last lines, and YAML between them, or#
(creating an H1 heading), and becomes the title. Then, optionally, a line beginning with >
(creating a quote block), which becomes the description. Finally, zero or more lines beginning with -
(creating a list), each of which contains YAML. (If you already have "title" defined in frontmatter in a raw cell, then markdown cells will be ignored.)For instance, our test notebook contains the following markdown cell:
# a title
> A description
- key1: value1
- key2: value2
- categories: [c1, c2]
It also contains the following raw cell:
---
execute:
echo: false
---
When we process with FrontmatterProc
, these will both be removed, and a single raw cell will be added to the top, containing the combined YAML frontmatter:
nbp = NBProcessor(_test_file, procs=FrontmatterProc)
nbp.process()
print(nbp.nb.cells[0].source)
--- categories: - c1 - c2 description: A description execute: echo: false key1: value1 key2: value2 output-file: foobar.html title: a title ---
In addition, a frontmatter_
attr will be added to the notebook, containing this information as a dict
:
d = nbp.nb.frontmatter_
d
{'execute': {'echo': False}, 'title': 'a title', 'description': 'A description', 'key1': 'value1', 'key2': 'value2', 'categories': ['c1', 'c2'], 'output-file': 'foobar.html'}
#|hide
test_eq(d['description'], 'A description')
test_eq(d['categories'], ['c1','c2'])
test_eq(d['output-file'], 'foobar.html')
#|hide
import nbdev; nbdev.nbdev_export()