Skip to content

Commit 8bb9f0b

Browse files
committed
fixes #890
1 parent 28071ce commit 8bb9f0b

File tree

14 files changed

+443
-222
lines changed

14 files changed

+443
-222
lines changed

nbdev/_modidx.py

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,10 @@
109109
'nbdev.export.nb_export': 'https://nbdev.fast.ai/export.html#nb_export'},
110110
'nbdev.extract_attachments': { 'nbdev.extract_attachments.ExtractAttachmentsPreprocessor': 'https://nbdev.fast.ai/extract_attachments.html#extractattachmentspreprocessor',
111111
'nbdev.extract_attachments.ExtractAttachmentsPreprocessor.preprocess_cell': 'https://nbdev.fast.ai/extract_attachments.html#extractattachmentspreprocessor.preprocess_cell'},
112+
'nbdev.frontmatter': { 'nbdev.frontmatter.FrontmatterProc': 'https://nbdev.fast.ai/frontmatter.html#frontmatterproc',
113+
'nbdev.frontmatter.FrontmatterProc.begin': 'https://nbdev.fast.ai/frontmatter.html#frontmatterproc.begin',
114+
'nbdev.frontmatter.FrontmatterProc.cell': 'https://nbdev.fast.ai/frontmatter.html#frontmatterproc.cell',
115+
'nbdev.frontmatter.FrontmatterProc.end': 'https://nbdev.fast.ai/frontmatter.html#frontmatterproc.end'},
112116
'nbdev.imports': {},
113117
'nbdev.maker': { 'nbdev.maker.ModuleMaker': 'https://nbdev.fast.ai/maker.html#modulemaker',
114118
'nbdev.maker.ModuleMaker._last_future': 'https://nbdev.fast.ai/maker.html#modulemaker._last_future',
@@ -157,8 +161,6 @@
157161
'nbdev.processors.filter_stream_': 'https://nbdev.fast.ai/processors.html#filter_stream_',
158162
'nbdev.processors.hide_': 'https://nbdev.fast.ai/processors.html#hide_',
159163
'nbdev.processors.hide_line': 'https://nbdev.fast.ai/processors.html#hide_line',
160-
'nbdev.processors.infer_frontmatter': 'https://nbdev.fast.ai/processors.html#infer_frontmatter',
161-
'nbdev.processors.infer_frontmatter.begin': 'https://nbdev.fast.ai/processors.html#infer_frontmatter.begin',
162164
'nbdev.processors.insert_frontmatter': 'https://nbdev.fast.ai/processors.html#insert_frontmatter',
163165
'nbdev.processors.insert_warning': 'https://nbdev.fast.ai/processors.html#insert_warning',
164166
'nbdev.processors.insert_warning.begin': 'https://nbdev.fast.ai/processors.html#insert_warning.begin',

nbdev/cli.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,7 @@
1111
from .test import *
1212
from .clean import *
1313
from .quarto import refresh_quarto_yml
14+
from .frontmatter import FrontmatterProc
1415

1516
from execnb.nbio import *
1617
from fastcore.meta import *
@@ -40,7 +41,7 @@ class FilterDefaults:
4041
def xtra_procs(self): return []
4142

4243
def base_procs(self):
43-
return [populate_language, infer_frontmatter, add_show_docs, insert_warning,
44+
return [FrontmatterProc, populate_language, add_show_docs, insert_warning,
4445
strip_ansi, hide_line, filter_stream_, rm_header_dash,
4546
clean_show_doc, exec_show_docs, rm_export, clean_magics, hide_, add_links, strip_hidden_metadata]
4647

nbdev/frontmatter.py

Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/09a_frontmatter.ipynb.
2+
3+
# %% auto 0
4+
__all__ = ['FrontmatterProc']
5+
6+
# %% ../nbs/09a_frontmatter.ipynb 2
7+
from .imports import *
8+
from .process import *
9+
10+
from execnb.nbio import *
11+
from fastcore.imports import *
12+
import yaml
13+
14+
# %% ../nbs/09a_frontmatter.ipynb 5
15+
_re_fm = re.compile(r'''^---\s*
16+
(.*\S+.*)
17+
---\s*$''', flags=re.DOTALL)
18+
19+
def _fm2dict(s:str):
20+
"Load YAML frontmatter into a `dict`"
21+
match = _re_fm.search(s.strip())
22+
return yaml.safe_load(match.group(1)) if match else {}
23+
24+
def _md2dict(s:str):
25+
"Convert H1 formatted markdown cell to frontmatter dict"
26+
if '#' not in s: return {}
27+
m = re.search(r'^#\s+(\S.*?)\s*$', s, flags=re.MULTILINE)
28+
if not m: return {}
29+
res = {'title': m.group(1)}
30+
m = re.search(r'^>\s+(\S.*?)\s*$', s, flags=re.MULTILINE)
31+
if m: res['description'] = m.group(1)
32+
r = re.findall(r'^-\s+(\S.*?)\s*$', s, flags=re.MULTILINE)
33+
if r:
34+
try: res.update(yaml.safe_load('\n'.join(r)))
35+
except Exception as e: warn(f'Failed to create YAML dict for:\n{r}\n\n{e}\n')
36+
return res
37+
38+
# %% ../nbs/09a_frontmatter.ipynb 6
39+
class FrontmatterProc(Processor):
40+
"A YAML and formatted-markdown frontmatter processor"
41+
def begin(self): self.fm = {}
42+
def _default_exp_(self, cell, exp): self.default_exp = exp
43+
44+
def _update(self, f, cell):
45+
s = cell.get('source')
46+
if not s: return
47+
d = f(cell.source)
48+
if not d: return
49+
self.fm.update(d)
50+
cell.source = None
51+
52+
def cell(self, cell):
53+
if cell.cell_type=='raw': self._update(_fm2dict, cell)
54+
elif cell.cell_type=='markdown' and 'title' not in self.fm: self._update(_md2dict, cell)
55+
56+
def end(self):
57+
self.nb.frontmatter_ = self.fm
58+
if not self.fm: return
59+
exp = getattr(self, 'default_exp', None)
60+
if exp: self.fm.update({'output-file': exp+'.html'})
61+
s = f'---\n{yaml.dump(self.fm)}\n---'
62+
self.nb.cells.insert(0, mk_cell(s, 'raw'))

nbdev/processors.py

Lines changed: 31 additions & 46 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,12 @@
1-
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/09_processors.ipynb.
1+
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/09b_processors.ipynb.
22

33
# %% auto 0
44
__all__ = ['is_frontmatter', 'yml2dict', 'populate_language', 'insert_warning', 'cell_lang', 'add_show_docs', 'yaml_str',
5-
'nb_fmdict', 'filter_fm', 'construct_fm', 'insert_frontmatter', 'infer_frontmatter', 'add_links',
6-
'strip_ansi', 'strip_hidden_metadata', 'hide_', 'hide_line', 'filter_stream_', 'clean_magics',
7-
'rm_header_dash', 'rm_export', 'clean_show_doc', 'exec_show_docs']
5+
'nb_fmdict', 'filter_fm', 'construct_fm', 'insert_frontmatter', 'add_links', 'strip_ansi',
6+
'strip_hidden_metadata', 'hide_', 'hide_line', 'filter_stream_', 'clean_magics', 'rm_header_dash',
7+
'rm_export', 'clean_show_doc', 'exec_show_docs']
88

9-
# %% ../nbs/09_processors.ipynb 2
9+
# %% ../nbs/09b_processors.ipynb 2
1010
import ast
1111

1212
from .config import *
@@ -21,7 +21,7 @@
2121
from fastcore.xtras import *
2222
import sys,yaml
2323

24-
# %% ../nbs/09_processors.ipynb 7
24+
# %% ../nbs/09b_processors.ipynb 7
2525
_re_fm = re.compile(r'^---(.*\S+.*)---', flags=re.DOTALL)
2626

2727
def is_frontmatter(nb):
@@ -39,7 +39,7 @@ def _get_frontmatter(nb):
3939
cell = first(is_frontmatter(nb))
4040
return cell,(yml2dict(cell.source) if cell else {})
4141

42-
# %% ../nbs/09_processors.ipynb 8
42+
# %% ../nbs/09b_processors.ipynb 8
4343
_langs = 'bash|html|javascript|js|latex|markdown|perl|ruby|sh|svg'
4444
_lang_pattern = re.compile(rf'^\s*%%\s*({_langs})\s*$', flags=re.MULTILINE)
4545

@@ -52,13 +52,13 @@ def cell(self, cell):
5252
if lang: cell.metadata.language = lang[0]
5353
else: cell.metadata.language = self.language
5454

55-
# %% ../nbs/09_processors.ipynb 10
55+
# %% ../nbs/09b_processors.ipynb 10
5656
class insert_warning(Processor):
5757
"Insert Autogenerated Warning Into Notebook after the first cell."
5858
content = "<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->"
5959
def begin(self): self.nb.cells.insert(1, mk_cell(self.content, 'markdown'))
6060

61-
# %% ../nbs/09_processors.ipynb 14
61+
# %% ../nbs/09b_processors.ipynb 14
6262
_def_types = (ast.FunctionDef,ast.AsyncFunctionDef,ast.ClassDef)
6363
def _def_names(cell, shown):
6464
return [showdoc_nm(o) for o in concat(cell.parsed_())
@@ -70,7 +70,7 @@ def _get_nm(tree):
7070
else: val = try_attrs(i.value, 'id', 'func', 'attr')
7171
return f'{val}.{i.attr}' if isinstance(i, ast.Attribute) else i.id
7272

73-
# %% ../nbs/09_processors.ipynb 15
73+
# %% ../nbs/09b_processors.ipynb 15
7474
def _show_docs(trees):
7575
return [t for t in trees if isinstance(t,ast.Expr) and nested_attr(t, 'value.func.id')=='show_doc']
7676

@@ -90,22 +90,22 @@ def begin(self):
9090
for nm in _def_names(cell, shown_docs): nb.cells.insert(cell.idx_+1, mk_cell(f'show_doc({nm})'))
9191
nb.has_docs_ = shown_docs or exports
9292

93-
# %% ../nbs/09_processors.ipynb 19
93+
# %% ../nbs/09b_processors.ipynb 19
9494
def yaml_str(s:str):
9595
"Create a valid YAML string from `s`"
9696
if s[0]=='"' and s[-1]=='"': return s
9797
res = s.replace('\\', '\\\\').replace('"', r'\"')
9898
return f'"{res}"'
9999

100-
# %% ../nbs/09_processors.ipynb 20
100+
# %% ../nbs/09b_processors.ipynb 20
101101
_re_title = re.compile(r'^#\s+(.*)[\n\r]+(?:^>\s+(.*))?', flags=re.MULTILINE)
102102

103103
def _celltyp(nb, cell_type): return L(nb.cells).filter(lambda c: c.cell_type == cell_type)
104104
def _istitle(cell):
105105
txt = cell.get('source', '')
106106
return bool(_re_title.search(txt)) if txt else False
107107

108-
# %% ../nbs/09_processors.ipynb 21
108+
# %% ../nbs/09b_processors.ipynb 21
109109
def nb_fmdict(nb, remove=True):
110110
"Infer the front matter from a notebook's markdown formatting"
111111
md_cells = _celltyp(nb, 'markdown').filter(_istitle)
@@ -122,7 +122,7 @@ def nb_fmdict(nb, remove=True):
122122
return yml2dict('\n'.join([f"{k}: {flags[k]}" for k in flags]))
123123
else: return {}
124124

125-
# %% ../nbs/09_processors.ipynb 24
125+
# %% ../nbs/09b_processors.ipynb 24
126126
def _replace_fm(d:dict, # dictionary you wish to conditionally change
127127
k:str, # key to check
128128
val:str,# value to check if d[k] == v
@@ -140,33 +140,33 @@ def _fp_alias(d):
140140
d = _replace_fm(d, 'hide', 'true', {'draft': 'true'})
141141
return d
142142

143-
# %% ../nbs/09_processors.ipynb 26
143+
# %% ../nbs/09b_processors.ipynb 26
144144
def _fp_image(d):
145145
"Correct path of fastpages images"
146146
prefix = 'images/copied_from_nb/'
147147
if d.get('image', '').startswith(prefix): d['image'] = d['image'].replace(prefix, '')
148148
return d
149149

150-
# %% ../nbs/09_processors.ipynb 28
150+
# %% ../nbs/09b_processors.ipynb 28
151151
def filter_fm(fmdict:dict):
152152
"Filter front matter"
153153
keys = ['title', 'description', 'author', 'image', 'categories', 'output-file', 'aliases', 'search', 'draft', 'comments']
154154
if not fmdict: return {}
155155
return filter_keys(fmdict, in_(keys))
156156

157-
# %% ../nbs/09_processors.ipynb 29
157+
# %% ../nbs/09b_processors.ipynb 29
158158
def construct_fm(fmdict:dict):
159159
"Construct front matter from a dictionary"
160160
if not fmdict: return None
161161
return '---\n'+yaml.dump(fmdict)+'\n---'
162162

163-
# %% ../nbs/09_processors.ipynb 31
163+
# %% ../nbs/09b_processors.ipynb 31
164164
def insert_frontmatter(nb, fm_dict:dict):
165165
"Add frontmatter into notebook based on `filter_keys` that exist in `fmdict`."
166166
fm = construct_fm(fm_dict)
167167
if fm: nb.cells.insert(0, NbCell(0, dict(cell_type='raw', metadata={}, source=fm, directives_={})))
168168

169-
# %% ../nbs/09_processors.ipynb 33
169+
# %% ../nbs/09b_processors.ipynb 33
170170
_re_defaultexp = re.compile(r'^\s*#\|\s*default_exp\s+(\S+)', flags=re.MULTILINE)
171171

172172
def _default_exp(nb):
@@ -175,22 +175,7 @@ def _default_exp(nb):
175175
default_exp = first(code_src.filter().map(_re_defaultexp.search).filter())
176176
return default_exp.group(1) if default_exp else None
177177

178-
# %% ../nbs/09_processors.ipynb 35
179-
_fp_convert = compose(_fp_alias, _fp_image)
180-
181-
class infer_frontmatter(Processor):
182-
"Insert front matter if it doesn't exist automatically from nbdev styled markdown."
183-
def begin(self):
184-
nb = self.nb
185-
raw_fm_cell,raw_fm = _get_frontmatter(nb)
186-
_exp = _default_exp(nb)
187-
_fmdict = merge(_fp_convert(filter_fm(nb_fmdict(nb))), {'output-file': _exp+'.html'} if _exp else {}, raw_fm)
188-
if not _fmdict: return
189-
if raw_fm: raw_fm_cell['source'] = None
190-
insert_frontmatter(nb, fm_dict=_fmdict)
191-
nb.frontmatter_=_fmdict
192-
193-
# %% ../nbs/09_processors.ipynb 44
178+
# %% ../nbs/09b_processors.ipynb 35
194179
def add_links(cell):
195180
"Add links to markdown cells"
196181
nl = NbdevLookup()
@@ -199,25 +184,25 @@ def add_links(cell):
199184
if hasattr(o, 'data') and hasattr(o['data'], 'text/markdown'):
200185
o.data['text/markdown'] = [nl.link_line(s) for s in o.data['text/markdown']]
201186

202-
# %% ../nbs/09_processors.ipynb 47
187+
# %% ../nbs/09b_processors.ipynb 38
203188
_re_ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
204189

205190
def strip_ansi(cell):
206191
"Strip Ansi Characters."
207192
for outp in cell.get('outputs', []):
208193
if outp.get('name')=='stdout': outp['text'] = [_re_ansi_escape.sub('', o) for o in outp.text]
209194

210-
# %% ../nbs/09_processors.ipynb 49
195+
# %% ../nbs/09b_processors.ipynb 40
211196
def strip_hidden_metadata(cell):
212197
'''Strips "hidden" metadata property from code cells so it doesn't interfere with docs rendering'''
213198
if cell.cell_type == 'code' and 'metadata' in cell: cell.metadata.pop('hidden',None)
214199

215-
# %% ../nbs/09_processors.ipynb 50
200+
# %% ../nbs/09b_processors.ipynb 41
216201
def hide_(cell):
217202
"Hide cell from output"
218203
del(cell['source'])
219204

220-
# %% ../nbs/09_processors.ipynb 52
205+
# %% ../nbs/09b_processors.ipynb 43
221206
def _re_hideline(lang=None): return re.compile(fr'{langs[lang]}\|\s*hide_line\s*$', re.MULTILINE)
222207

223208
def hide_line(cell):
@@ -226,22 +211,22 @@ def hide_line(cell):
226211
if cell.cell_type == 'code' and _re_hideline(lang).search(cell.source):
227212
cell.source = '\n'.join([c for c in cell.source.splitlines() if not _re_hideline(lang).search(c)])
228213

229-
# %% ../nbs/09_processors.ipynb 55
214+
# %% ../nbs/09b_processors.ipynb 46
230215
def filter_stream_(cell, *words):
231216
"Remove output lines containing any of `words` in `cell` stream output"
232217
if not words: return
233218
for outp in cell.get('outputs', []):
234219
if outp.output_type == 'stream':
235220
outp['text'] = [l for l in outp.text if not re.search('|'.join(words), l)]
236221

237-
# %% ../nbs/09_processors.ipynb 57
222+
# %% ../nbs/09b_processors.ipynb 48
238223
_magics_pattern = re.compile(r'^\s*(%%|%).*', re.MULTILINE)
239224

240225
def clean_magics(cell):
241226
"A preprocessor to remove cell magic commands"
242227
if cell.cell_type == 'code': cell.source = _magics_pattern.sub('', cell.source).strip()
243228

244-
# %% ../nbs/09_processors.ipynb 59
229+
# %% ../nbs/09b_processors.ipynb 50
245230
_re_hdr_dash = re.compile(r'^#+\s+.*\s+-\s*$', re.MULTILINE)
246231

247232
def rm_header_dash(cell):
@@ -250,14 +235,14 @@ def rm_header_dash(cell):
250235
src = cell.source.strip()
251236
if cell.cell_type == 'markdown' and src.startswith('#') and src.endswith(' -'): del(cell['source'])
252237

253-
# %% ../nbs/09_processors.ipynb 61
238+
# %% ../nbs/09b_processors.ipynb 52
254239
_hide_dirs = {'export','exporti', 'hide','default_exp'}
255240

256241
def rm_export(cell):
257242
"Remove cells that are exported or hidden"
258243
if cell.directives_ and (cell.directives_.keys() & _hide_dirs): del(cell['source'])
259244

260-
# %% ../nbs/09_processors.ipynb 63
245+
# %% ../nbs/09b_processors.ipynb 54
261246
_re_showdoc = re.compile(r'^show_doc', re.MULTILINE)
262247
def _is_showdoc(cell): return cell['cell_type'] == 'code' and _re_showdoc.search(cell.source)
263248

@@ -266,7 +251,7 @@ def clean_show_doc(cell):
266251
if not _is_showdoc(cell): return
267252
cell.source = '#|output: asis\n#| echo: false\n' + cell.source
268253

269-
# %% ../nbs/09_processors.ipynb 64
254+
# %% ../nbs/09b_processors.ipynb 55
270255
def _ast_contains(trees, types):
271256
for tree in trees:
272257
for node in ast.walk(tree):
@@ -287,7 +272,7 @@ def _do_eval(cell):
287272
return True
288273
if _show_docs(trees): return True
289274

290-
# %% ../nbs/09_processors.ipynb 65
275+
# %% ../nbs/09b_processors.ipynb 56
291276
class exec_show_docs:
292277
"Execute cells needed for `show_docs` output, including exported cells and imports"
293278
def __init__(self, nb):

nbdev/test.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
from .config import *
1515
from .doclinks import *
1616
from .process import NBProcessor, nb_lang
17-
from .processors import infer_frontmatter
17+
from .frontmatter import FrontmatterProc
1818
from logging import warning
1919

2020
from execnb.nbio import *
@@ -31,7 +31,7 @@ def test_nb(fn, # file name of notebook to test
3131
if basepath: sys.path.insert(0, str(basepath))
3232
if not IN_NOTEBOOK: os.environ["IN_TEST"] = '1'
3333
flags=set(L(skip_flags)) - set(L(force_flags))
34-
nb = NBProcessor(fn, preprocs=infer_frontmatter, process=True).nb
34+
nb = NBProcessor(fn, preprocs=FrontmatterProc, process=True).nb
3535
fm = getattr(nb, 'frontmatter_', {})
3636
if str2bool(fm.get('skip_exec', False)) or nb_lang(nb) != 'python': return True, 0
3737

nbs/04b_doclinks.ipynb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@
1414
"metadata": {},
1515
"source": [
1616
"# doclinks\n",
17-
"- Generating a documentation index from a module"
17+
"> Generating a documentation index from a module"
1818
]
1919
},
2020
{

0 commit comments

Comments
 (0)