Skip to content

Commit f8b0928

Browse files
committed
fixes #911
1 parent 2a5e3ee commit f8b0928

File tree

9 files changed

+140
-635
lines changed

9 files changed

+140
-635
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
nbdev/_modidx.py
12
nbdev/tutorial.py
23
tests/settings.ini
34
idx/

nbdev/_modidx.py

Lines changed: 0 additions & 582 deletions
This file was deleted.

nbdev/doclinks.py

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -137,7 +137,7 @@ def _settings_libs():
137137

138138
# %% ../nbs/04b_doclinks.ipynb 19
139139
class NbdevLookup:
140-
"Mapping from symbol names to URLs with docs"
140+
"Mapping from symbol names to docs and source URLs"
141141
def __init__(self, strip_libs=None, incl_libs=None, skip_mods=None):
142142
if strip_libs is None: strip_libs = _settings_libs()
143143
skip_mods = setify(skip_mods)
@@ -156,15 +156,16 @@ def __init__(self, strip_libs=None, incl_libs=None, skip_mods=None):
156156
py_syms = merge(stripped, py_syms)
157157
self.syms = py_syms
158158

159-
def __getitem__(self, s):
160-
res = self.syms.get(s, None)
159+
def __getitem__(self, s): return self.syms.get(s, None)
160+
def doc(self, s):
161+
res = self[s]
161162
return res[0] if isinstance(res, tuple) else res
162163

163164
# %% ../nbs/04b_doclinks.ipynb 27
164165
@patch
165166
def _link_sym(self:NbdevLookup, m):
166167
l = m.group(1)
167-
s = self[l]
168+
s = self.doc(l)
168169
if s is None: return m.group(0)
169170
l = l.replace('\\', r'\\')
170171
return rf"[`{l}`]({s})"

nbdev/merge.py

Lines changed: 8 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
# AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/06_merge.ipynb.
22

33
# %% auto 0
4-
__all__ = ['conf_re', 'nbdev_fix', 'unpatch', 'nbdev_merge']
4+
__all__ = ['conf_re', 'unpatch', 'nbdev_fix', 'nbdev_merge']
55

66
# %% ../nbs/06_merge.ipynb 2
77
from .imports import *
@@ -54,7 +54,8 @@ def _merge_cells(a, b, brancha, branchb, theirs):
5454
return res,conflict
5555

5656
# %% ../nbs/06_merge.ipynb 23
57-
def _nbdev_fix(nbname:str, # Notebook filename to fix
57+
@call_parse
58+
def nbdev_fix(nbname:str, # Notebook filename to fix
5859
outname:str=None, # Filename of output notebook (defaults to `nbname`)
5960
nobackup:bool_arg=True, # Do not backup `nbname` to `nbname`.bak if `outname` not provided
6061
theirs:bool=False, # Use their outputs and metadata instead of ours
@@ -74,15 +75,12 @@ def _nbdev_fix(nbname:str, # Notebook filename to fix
7475
else: print("Successfully merged conflicts!")
7576
return conflict
7677

77-
# %% ../nbs/06_merge.ipynb 24
78-
nbdev_fix = call_parse(_nbdev_fix)
79-
80-
# %% ../nbs/06_merge.ipynb 28
78+
# %% ../nbs/06_merge.ipynb 27
8179
def _git_branch_merge():
8280
try: return only(v for k,v in os.environ.items() if k.startswith('GITHEAD'))
8381
except ValueError: return
8482

85-
# %% ../nbs/06_merge.ipynb 29
83+
# %% ../nbs/06_merge.ipynb 28
8684
def _git_rebase_head():
8785
for d in ('apply','merge'):
8886
d = Path(f'.git/rebase-{d}')
@@ -91,17 +89,17 @@ def _git_rebase_head():
9189
msg = run(f'git show-branch --no-name {cmt}')
9290
return f'{cmt[:7]} ({msg})'
9391

94-
# %% ../nbs/06_merge.ipynb 30
92+
# %% ../nbs/06_merge.ipynb 29
9593
def _git_merge_file(base, ours, theirs):
9694
"`git merge-file` with expected labels depending on if a `merge` or `rebase` is in-progress"
9795
l_theirs = _git_rebase_head() or _git_branch_merge() or 'THEIRS'
9896
cmd = f"git merge-file -L HEAD -L BASE -L '{l_theirs}' {ours} {base} {theirs}"
9997
return subprocess.run(cmd, shell=True, capture_output=True, text=True)
10098

101-
# %% ../nbs/06_merge.ipynb 31
99+
# %% ../nbs/06_merge.ipynb 30
102100
@call_parse
103101
def nbdev_merge(base:str, ours:str, theirs:str, path:str):
104102
"Git merge driver for notebooks"
105103
if not _git_merge_file(base, ours, theirs).returncode: return
106104
theirs = str2bool(os.environ.get('THEIRS', False))
107-
return _nbdev_fix(ours, theirs=theirs)
105+
return nbdev_fix.__wrapped__(ours, theirs=theirs)

nbdev/quarto.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66

77
from .config import *
88
from .doclinks import *
9+
from .doclinks import _build_modidx
910

1011
from fastcore.utils import *
1112
from fastcore.script import call_parse
@@ -239,6 +240,7 @@ def nbdev_quarto(
239240
nbdev_sidebar.__wrapped__(path, file_glob=file_glob, **kwargs)
240241
pys = globtastic(path, file_glob='*.py', **kwargs).filter(_is_qpy)
241242
for py in pys: _exec_py(py)
243+
_build_modidx()
242244
if preview: os.system(f'cd "{path}" && quarto preview --port {port}')
243245
else: _sprun(f'cd "{path}" && quarto render')
244246
if not preview:

nbs/04b_doclinks.ipynb

Lines changed: 11 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -327,7 +327,7 @@
327327
"source": [
328328
"#|export\n",
329329
"class NbdevLookup:\n",
330-
" \"Mapping from symbol names to URLs with docs\"\n",
330+
" \"Mapping from symbol names to docs and source URLs\"\n",
331331
" def __init__(self, strip_libs=None, incl_libs=None, skip_mods=None):\n",
332332
" if strip_libs is None: strip_libs = _settings_libs()\n",
333333
" skip_mods = setify(skip_mods)\n",
@@ -346,8 +346,9 @@
346346
" py_syms = merge(stripped, py_syms)\n",
347347
" self.syms = py_syms\n",
348348
"\n",
349-
" def __getitem__(self, s):\n",
350-
" res = self.syms.get(s, None)\n",
349+
" def __getitem__(self, s): return self.syms.get(s, None)\n",
350+
" def doc(self, s):\n",
351+
" res = self[s]\n",
351352
" return res[0] if isinstance(res, tuple) else res"
352353
]
353354
},
@@ -365,10 +366,10 @@
365366
"outputs": [],
366367
"source": [
367368
"c = NbdevLookup()\n",
368-
"assert c['nbdev.doclinks.NbdevLookup'].startswith('http')\n",
369-
"assert c['numpy.array'].startswith('http')\n",
370-
"assert c['NbdevLookup'].startswith('http')\n",
371-
"assert not c['array']"
369+
"assert c.doc('nbdev.doclinks.NbdevLookup').startswith('http')\n",
370+
"assert c.doc('numpy.array').startswith('http')\n",
371+
"assert c.doc('NbdevLookup').startswith('http')\n",
372+
"assert not c.doc('array')"
372373
]
373374
},
374375
{
@@ -385,7 +386,7 @@
385386
"outputs": [],
386387
"source": [
387388
"c = NbdevLookup(strip_libs=['nbdev', 'nbdev_numpy'])\n",
388-
"assert c['array'].startswith('http')"
389+
"assert c.doc('array').startswith('http')"
389390
]
390391
},
391392
{
@@ -401,10 +402,7 @@
401402
"metadata": {},
402403
"outputs": [],
403404
"source": [
404-
"_nbdev_lookup = NbdevLookup()\n",
405-
"assert _nbdev_lookup['NbdevLookup'].startswith('http')\n",
406-
"assert _nbdev_lookup['numpy.array'].startswith('http')\n",
407-
"assert not _nbdev_lookup['array']"
405+
"assert NbdevLookup().doc('NbdevLookup').startswith('http')"
408406
]
409407
},
410408
{
@@ -424,7 +422,7 @@
424422
"@patch\n",
425423
"def _link_sym(self:NbdevLookup, m):\n",
426424
" l = m.group(1)\n",
427-
" s = self[l]\n",
425+
" s = self.doc(l)\n",
428426
" if s is None: return m.group(0)\n",
429427
" l = l.replace('\\\\', r'\\\\')\n",
430428
" return rf\"[`{l}`]({s})\"\n",

nbs/06_merge.ipynb

Lines changed: 103 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@
6262
"cell_type": "markdown",
6363
"metadata": {},
6464
"source": [
65-
"When working with jupyter notebooks (which are json files behind the scenes) and GitHub, it is very common that a merge conflict (that will add new lines in the notebook source file) will break some notebooks you are working on. This module defines the function `fix_conflicts` to fix those notebooks for you, and attempt to automatically merge standard conflicts. The remaining ones will be delimited by markdown cells like this:"
65+
"When working with jupyter notebooks (which are json files behind the scenes) and GitHub, it is very common that a merge conflict (that will add new lines in the notebook source file) will break some notebooks you are working on. This module defines the function `nbdev_fix` to fix those notebooks for you, and attempt to automatically merge standard conflicts. The remaining ones will be delimited by markdown cells like this:"
6666
]
6767
},
6868
{
@@ -273,7 +273,30 @@
273273
"data": {
274274
"text/markdown": [
275275
"```json\n",
276-
"{ 'cells': [{'cell_type': 'code', 'execution_count': 6, 'metadata': {}, 'outputs': [{'data': {'text/plain': ['3']}, 'execution_count': 6, 'metadata': {}, 'output_type': 'execute_result'}], 'source': 'z=3\\nz', 'idx_': 0}, {'cell_type': 'code', 'execution_count': 5, 'metadata': {}, 'outputs': [{'data': {'text/plain': ['6']}, 'execution_count': 7, 'metadata': {}, 'output_type': 'execute_result'}], 'source': 'x=3\\ny=3\\nx+y', 'idx_': 1}, {'cell_type': 'code', 'execution_count': None, 'metadata': {}, 'outputs': [], 'source': '', 'idx_': 2}],\n",
276+
"{ 'cells': [ { 'cell_type': 'code',\n",
277+
" 'execution_count': 6,\n",
278+
" 'idx_': 0,\n",
279+
" 'metadata': {},\n",
280+
" 'outputs': [ { 'data': {'text/plain': ['3']},\n",
281+
" 'execution_count': 6,\n",
282+
" 'metadata': {},\n",
283+
" 'output_type': 'execute_result'}],\n",
284+
" 'source': 'z=3\\nz'},\n",
285+
" { 'cell_type': 'code',\n",
286+
" 'execution_count': 5,\n",
287+
" 'idx_': 1,\n",
288+
" 'metadata': {},\n",
289+
" 'outputs': [ { 'data': {'text/plain': ['6']},\n",
290+
" 'execution_count': 7,\n",
291+
" 'metadata': {},\n",
292+
" 'output_type': 'execute_result'}],\n",
293+
" 'source': 'x=3\\ny=3\\nx+y'},\n",
294+
" { 'cell_type': 'code',\n",
295+
" 'execution_count': None,\n",
296+
" 'idx_': 2,\n",
297+
" 'metadata': {},\n",
298+
" 'outputs': [],\n",
299+
" 'source': ''}],\n",
277300
" 'metadata': { 'kernelspec': { 'display_name': 'Python 3',\n",
278301
" 'language': 'python',\n",
279302
" 'name': 'python3'}},\n",
@@ -282,7 +305,30 @@
282305
"```"
283306
],
284307
"text/plain": [
285-
"{'cells': (#3) [{'cell_type': 'code', 'execution_count': 6, 'metadata': {}, 'outputs': [{'data': {'text/plain': ['3']}, 'execution_count': 6, 'metadata': {}, 'output_type': 'execute_result'}], 'source': 'z=3\\nz', 'idx_': 0},{'cell_type': 'code', 'execution_count': 5, 'metadata': {}, 'outputs': [{'data': {'text/plain': ['6']}, 'execution_count': 7, 'metadata': {}, 'output_type': 'execute_result'}], 'source': 'x=3\\ny=3\\nx+y', 'idx_': 1},{'cell_type': 'code', 'execution_count': None, 'metadata': {}, 'outputs': [], 'source': '', 'idx_': 2}],\n",
308+
"{'cells': [{'cell_type': 'code',\n",
309+
" 'execution_count': 6,\n",
310+
" 'metadata': {},\n",
311+
" 'outputs': [{'data': {'text/plain': ['3']},\n",
312+
" 'execution_count': 6,\n",
313+
" 'metadata': {},\n",
314+
" 'output_type': 'execute_result'}],\n",
315+
" 'source': 'z=3\\nz',\n",
316+
" 'idx_': 0},\n",
317+
" {'cell_type': 'code',\n",
318+
" 'execution_count': 5,\n",
319+
" 'metadata': {},\n",
320+
" 'outputs': [{'data': {'text/plain': ['6']},\n",
321+
" 'execution_count': 7,\n",
322+
" 'metadata': {},\n",
323+
" 'output_type': 'execute_result'}],\n",
324+
" 'source': 'x=3\\ny=3\\nx+y',\n",
325+
" 'idx_': 1},\n",
326+
" {'cell_type': 'code',\n",
327+
" 'execution_count': None,\n",
328+
" 'metadata': {},\n",
329+
" 'outputs': [],\n",
330+
" 'source': '',\n",
331+
" 'idx_': 2}],\n",
286332
" 'metadata': {'kernelspec': {'display_name': 'Python 3',\n",
287333
" 'language': 'python',\n",
288334
" 'name': 'python3'}},\n",
@@ -309,7 +355,30 @@
309355
"data": {
310356
"text/markdown": [
311357
"```json\n",
312-
"{ 'cells': [{'cell_type': 'code', 'execution_count': 6, 'metadata': {}, 'outputs': [{'data': {'text/plain': ['3']}, 'execution_count': 6, 'metadata': {}, 'output_type': 'execute_result'}], 'source': 'z=2\\nz', 'idx_': 0}, {'cell_type': 'code', 'execution_count': 5, 'metadata': {}, 'outputs': [{'data': {'text/plain': ['6']}, 'execution_count': 5, 'metadata': {}, 'output_type': 'execute_result'}], 'source': 'x=3\\ny=3\\nx+y', 'idx_': 1}, {'cell_type': 'code', 'execution_count': None, 'metadata': {}, 'outputs': [], 'source': '', 'idx_': 2}],\n",
358+
"{ 'cells': [ { 'cell_type': 'code',\n",
359+
" 'execution_count': 6,\n",
360+
" 'idx_': 0,\n",
361+
" 'metadata': {},\n",
362+
" 'outputs': [ { 'data': {'text/plain': ['3']},\n",
363+
" 'execution_count': 6,\n",
364+
" 'metadata': {},\n",
365+
" 'output_type': 'execute_result'}],\n",
366+
" 'source': 'z=2\\nz'},\n",
367+
" { 'cell_type': 'code',\n",
368+
" 'execution_count': 5,\n",
369+
" 'idx_': 1,\n",
370+
" 'metadata': {},\n",
371+
" 'outputs': [ { 'data': {'text/plain': ['6']},\n",
372+
" 'execution_count': 5,\n",
373+
" 'metadata': {},\n",
374+
" 'output_type': 'execute_result'}],\n",
375+
" 'source': 'x=3\\ny=3\\nx+y'},\n",
376+
" { 'cell_type': 'code',\n",
377+
" 'execution_count': None,\n",
378+
" 'idx_': 2,\n",
379+
" 'metadata': {},\n",
380+
" 'outputs': [],\n",
381+
" 'source': ''}],\n",
313382
" 'metadata': { 'kernelspec': { 'display_name': 'Python 3',\n",
314383
" 'language': 'python',\n",
315384
" 'name': 'python3'}},\n",
@@ -318,7 +387,30 @@
318387
"```"
319388
],
320389
"text/plain": [
321-
"{'cells': (#3) [{'cell_type': 'code', 'execution_count': 6, 'metadata': {}, 'outputs': [{'data': {'text/plain': ['3']}, 'execution_count': 6, 'metadata': {}, 'output_type': 'execute_result'}], 'source': 'z=2\\nz', 'idx_': 0},{'cell_type': 'code', 'execution_count': 5, 'metadata': {}, 'outputs': [{'data': {'text/plain': ['6']}, 'execution_count': 5, 'metadata': {}, 'output_type': 'execute_result'}], 'source': 'x=3\\ny=3\\nx+y', 'idx_': 1},{'cell_type': 'code', 'execution_count': None, 'metadata': {}, 'outputs': [], 'source': '', 'idx_': 2}],\n",
390+
"{'cells': [{'cell_type': 'code',\n",
391+
" 'execution_count': 6,\n",
392+
" 'metadata': {},\n",
393+
" 'outputs': [{'data': {'text/plain': ['3']},\n",
394+
" 'execution_count': 6,\n",
395+
" 'metadata': {},\n",
396+
" 'output_type': 'execute_result'}],\n",
397+
" 'source': 'z=2\\nz',\n",
398+
" 'idx_': 0},\n",
399+
" {'cell_type': 'code',\n",
400+
" 'execution_count': 5,\n",
401+
" 'metadata': {},\n",
402+
" 'outputs': [{'data': {'text/plain': ['6']},\n",
403+
" 'execution_count': 5,\n",
404+
" 'metadata': {},\n",
405+
" 'output_type': 'execute_result'}],\n",
406+
" 'source': 'x=3\\ny=3\\nx+y',\n",
407+
" 'idx_': 1},\n",
408+
" {'cell_type': 'code',\n",
409+
" 'execution_count': None,\n",
410+
" 'metadata': {},\n",
411+
" 'outputs': [],\n",
412+
" 'source': '',\n",
413+
" 'idx_': 2}],\n",
322414
" 'metadata': {'kernelspec': {'display_name': 'Python 3',\n",
323415
" 'language': 'python',\n",
324416
" 'name': 'python3'}},\n",
@@ -386,7 +478,8 @@
386478
"outputs": [],
387479
"source": [
388480
"#|export\n",
389-
"def _nbdev_fix(nbname:str, # Notebook filename to fix\n",
481+
"@call_parse\n",
482+
"def nbdev_fix(nbname:str, # Notebook filename to fix\n",
390483
" outname:str=None, # Filename of output notebook (defaults to `nbname`)\n",
391484
" nobackup:bool_arg=True, # Do not backup `nbname` to `nbname`.bak if `outname` not provided\n",
392485
" theirs:bool=False, # Use their outputs and metadata instead of ours\n",
@@ -407,16 +500,6 @@
407500
" return conflict"
408501
]
409502
},
410-
{
411-
"cell_type": "code",
412-
"execution_count": null,
413-
"metadata": {},
414-
"outputs": [],
415-
"source": [
416-
"#|export\n",
417-
"nbdev_fix = call_parse(_nbdev_fix)"
418-
]
419-
},
420503
{
421504
"cell_type": "markdown",
422505
"metadata": {},
@@ -507,20 +590,15 @@
507590
" \"Git merge driver for notebooks\"\n",
508591
" if not _git_merge_file(base, ours, theirs).returncode: return\n",
509592
" theirs = str2bool(os.environ.get('THEIRS', False))\n",
510-
" return _nbdev_fix(ours, theirs=theirs)"
511-
]
512-
},
513-
{
514-
"cell_type": "markdown",
515-
"metadata": {},
516-
"source": [
517-
"This implements a [git merge driver](https://git-scm.com/docs/gitattributes#_defining_a_custom_merge_driver) for notebooks that automatically resolves conflicting metadata and outputs, and splits remaining conflicts as separate cells so that the notebook can be viewed and fixed in Jupyter. The easiest way to install it is by running `nbdev_install_hooks`."
593+
" return nbdev_fix.__wrapped__(ours, theirs=theirs)"
518594
]
519595
},
520596
{
521597
"cell_type": "markdown",
522598
"metadata": {},
523599
"source": [
600+
"This implements a [git merge driver](https://git-scm.com/docs/gitattributes#_defining_a_custom_merge_driver) for notebooks that automatically resolves conflicting metadata and outputs, and splits remaining conflicts as separate cells so that the notebook can be viewed and fixed in Jupyter. The easiest way to install it is by running `nbdev_install_hooks`.\n",
601+
"\n",
524602
"This works by first running Git's default merge driver, and then `nbdev_fix` if there are still conflicts. You can set `nbdev_fix`'s `theirs` argument using the `THEIRS` environment variable, for example:\n",
525603
"\n",
526604
" THEIRS=True git merge branch"
@@ -540,8 +618,7 @@
540618
"outputs": [],
541619
"source": [
542620
"#|hide\n",
543-
"from nbdev.doclinks import nbdev_export\n",
544-
"nbdev_export()"
621+
"import nbdev.doclinks; nbdev.nbdev_export()"
545622
]
546623
},
547624
{

nbs/13_quarto.ipynb

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
"\n",
3434
"from nbdev.config import *\n",
3535
"from nbdev.doclinks import *\n",
36+
"from nbdev.doclinks import _build_modidx\n",
3637
"\n",
3738
"from fastcore.utils import *\n",
3839
"from fastcore.script import call_parse\n",
@@ -435,6 +436,7 @@
435436
" nbdev_sidebar.__wrapped__(path, file_glob=file_glob, **kwargs)\n",
436437
" pys = globtastic(path, file_glob='*.py', **kwargs).filter(_is_qpy)\n",
437438
" for py in pys: _exec_py(py)\n",
439+
" _build_modidx()\n",
438440
" if preview: os.system(f'cd \"{path}\" && quarto preview --port {port}')\n",
439441
" else: _sprun(f'cd \"{path}\" && quarto render')\n",
440442
" if not preview:\n",

0 commit comments

Comments
 (0)