Skip to content

Commit

Permalink
Frontmatter and rendering imporvements
Browse files Browse the repository at this point in the history
  • Loading branch information
Isaac-Flath committed Oct 10, 2024
1 parent f35a598 commit 087575b
Show file tree
Hide file tree
Showing 6 changed files with 252 additions and 147 deletions.
1 change: 1 addition & 0 deletions .gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
*.ipynb merge=nbdev-merge
11 changes: 11 additions & 0 deletions .gitconfig
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
# Generated by nbdev_install_hooks
#
# If you need to disable this instrumentation do:
# git config --local --unset include.path
#
# To restore:
# git config --local include.path ../.gitconfig
#
[merge "nbdev-merge"]
name = resolve conflicts with nbdev_fix
driver = nbdev_merge %O %A %B %P
1 change: 1 addition & 0 deletions nb2fasthtml/_modidx.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@
'nb2fasthtml.core.get_nb_lang': ('core.html#get_nb_lang', 'nb2fasthtml/core.py'),
'nb2fasthtml.core.render_code_output': ('core.html#render_code_output', 'nb2fasthtml/core.py'),
'nb2fasthtml.core.render_code_source': ('core.html#render_code_source', 'nb2fasthtml/core.py'),
'nb2fasthtml.core.render_frontmatter': ('core.html#render_frontmatter', 'nb2fasthtml/core.py'),
'nb2fasthtml.core.render_md': ('core.html#render_md', 'nb2fasthtml/core.py'),
'nb2fasthtml.core.render_md_cell': ('core.html#render_md_cell', 'nb2fasthtml/core.py'),
'nb2fasthtml.core.render_nb': ('core.html#render_nb', 'nb2fasthtml/core.py'),
Expand Down
115 changes: 65 additions & 50 deletions nb2fasthtml/core.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,44 +3,46 @@
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/00_core.ipynb.

# %% auto 0
__all__ = ['strip_list', 'render_md', 'render_md_cell', 'get_nb_lang', 'render_code_source', 'render_code_output', 'render_nb',
'get_frontmatter']
__all__ = ['hdrs', 'get_frontmatter_raw', 'get_frontmatter_md', 'strip_list', 'render_md', 'render_md_cell', 'get_nb_lang',
'render_code_source', 'render_code_output', 'get_frontmatter', 'render_frontmatter', 'render_nb']

# %% ../nbs/00_core.ipynb 4
# %% ../nbs/00_core.ipynb
from fasthtml.common import *
from pathlib import Path
import json, yaml
from execnb.nbio import *
from typing import Callable
from functools import partial

# %% ../nbs/00_core.ipynb 6
# %% ../nbs/00_core.ipynb
hdrs = (MarkdownJS(), HighlightJS(langs=['python', 'javascript', 'html', 'css']), )

# %% ../nbs/00_core.ipynb
def strip_list(l, val='\n'):
start, end = 0, len(l)
while start < end and l[start] == val: start += 1
while end > start and l[end - 1] == val: end -= 1
return l[start:end]

# %% ../nbs/00_core.ipynb 7
def render_md(c):
# TODO default to FastHTML's implementation
return c
# %% ../nbs/00_core.ipynb
def render_md(c,container=Div): return container(c,cls="marked")

# %% ../nbs/00_core.ipynb 8
def render_md_cell(cell):
# %% ../nbs/00_core.ipynb
def render_md_cell(cell,render_md=render_md):
assert cell['cell_type'] == 'markdown'
return Div(cls='marked')(render_md(''.join(strip_list(cell['source']))))
return render_md(''.join(strip_list(cell['source'])))

# %% ../nbs/00_core.ipynb 10
# %% ../nbs/00_core.ipynb
def get_nb_lang(nb): return nb['metadata']['kernelspec']['language']
# get_nb_lang(xt_nb)

# %% ../nbs/00_core.ipynb 11
def render_code_source(cell,lang='python'):
# %% ../nbs/00_core.ipynb
def render_code_source(cell,lang='python',render_md=render_md):
if cell['source']==[]: return ''
code = f'''```{lang}\n{''.join(strip_list(cell['source']))}'''
return Div(cls='marked')(render_md(code))
return render_md(code)

# %% ../nbs/00_core.ipynb 13
def render_code_output(cell,lang='python'):
# %% ../nbs/00_core.ipynb
def render_code_output(cell,lang='python', render_md=render_md):
res = []
if len(cell['outputs'])==0: ''
for output in cell['outputs']:
Expand All @@ -52,47 +54,25 @@ def render_code_output(cell,lang='python'):
res.append(''.join(strip_list(data['text/plain'])))
if output['output_type'] == 'stream':
res.append(''.join(strip_list(output['text'])))
return Footer(cls='marked')(*res)

# %% ../nbs/00_core.ipynb 16
def render_nb(fpath, # Path to Jupyter Notebook
wrapper=Main, #Wraps entire rendered NB, default is for pico
cls='container', # cls to be passed to wrapper, default is for pico
md_cell_wrapper=Div, # Wraps markdown cell
md_fn=render_md_cell, # md cell -> rendered html
code_cell_wrapper=Card, # Wraps Source Code (body) + Outputs (footer)
cd_fn=render_code_source, # code cell -> code source rendered html
out_fn=render_code_output, # code cell -> code output rendered html
**kwargs # Passed to wrapper
):
with open(fpath, 'r') as f: xt_nb = json.load(f)
fname = Path(fpath).name
res = []
for cell in xt_nb['cells']:
if cell['cell_type']=='code':
s,o = cd_fn(cell), out_fn(cell)
res.append(code_cell_wrapper(s,o))
elif cell['cell_type']=='markdown':
res.append(md_cell_wrapper(md_fn(cell)))
return wrapper(cls=cls)(*res)
if res: return render_md(*res, container=Footer)

# %% ../nbs/00_core.ipynb 18
# %% ../nbs/00_core.ipynb
_RE_FM_BASE=r'''^---\s*
(.*?\S+.*?)
---\s*'''

# %% ../nbs/00_core.ipynb 19
# %% ../nbs/00_core.ipynb
_re_fm_nb = re.compile(_RE_FM_BASE+'$', flags=re.DOTALL)
_re_fm_md = re.compile(_RE_FM_BASE, flags=re.DOTALL)

# %% ../nbs/00_core.ipynb 20
# %% ../nbs/00_core.ipynb
def _fm2dict(s:str, nb=True):
"Load YAML frontmatter into a `dict`"
re_fm = _re_fm_nb if nb else _re_fm_md
match = re_fm.search(s.strip())
return yaml.safe_load(match.group(1)) if match else {}

# %% ../nbs/00_core.ipynb 21
# %% ../nbs/00_core.ipynb
def _md2dict(s:str):
"Convert H1 formatted markdown cell to frontmatter dict"
if '#' not in s: return {}
Expand All @@ -107,11 +87,46 @@ def _md2dict(s:str):
except Exception as e: warn(f'Failed to create YAML dict for:\n{r}\n\n{e}\n')
return res

# %% ../nbs/00_core.ipynb 22
# %% ../nbs/00_core.ipynb
def get_frontmatter(source, # metatadata source (jupyter cell or md content)
nb=True, # Is jupyter nb or md file
nb_file=True, # Is jupyter nb or qmd file
md_fm=False # md or raw style frontmatter
):
if not nb: return _fm2dict(source)
if md_fm: return _md2dict(source.source)
return _fm2dict(cell.source, nb)
if not nb_file: return _fm2dict(source)
if md_fm: return _md2dict(source.source)
return _fm2dict(source.source, nb)

get_frontmatter_raw = partial(get_frontmatter, md_fm=False)
get_frontmatter_md = partial(get_frontmatter, md_fm=True)

# %% ../nbs/00_core.ipynb
def render_frontmatter(fm):
return Div(cls='frontmatter')(H1(fm['title']),P(fm['description']))

# %% ../nbs/00_core.ipynb
def render_nb(fpath, # Path to Jupyter Notebook
wrapper=Main, #Wraps entire rendered NB, default is for pico
cls='container', # cls to be passed to wrapper, default is for pico
md_cell_wrapper=Div, # Wraps markdown cell
md_fn=render_md_cell, # md cell -> rendered html
code_cell_wrapper=Card, # Wraps Source Code (body) + Outputs (footer)
cd_fn=render_code_source, # code cell -> code source rendered html
out_fn=render_code_output, # code cell -> code output rendered html
get_fm=get_frontmatter_md, # How to read frontmatter cell
fm_fn:None|Callable=render_frontmatter, # Frontmatter -> FT components
**kwargs # Passed to wrapper
):
nb = read_nb(fpath)
fname = Path(fpath).name
res, content_start_idx = [], 0
if fm_fn:
content_start_idx = 1
fm = get_fm(nb.cells[0])
res.append(fm_fn(fm))
for cell in nb.cells[content_start_idx:]:
if cell['cell_type']=='code':
s,o = cd_fn(cell), out_fn(cell)
res.append(code_cell_wrapper(s,o))
elif cell['cell_type']=='markdown':
res.append(md_cell_wrapper(md_fn(cell)))
return wrapper(cls=cls)(*res)
Loading

0 comments on commit 087575b

Please sign in to comment.