From bf82e9bfc9b7e1f7998be84fecd7b1541e3d3034 Mon Sep 17 00:00:00 2001 From: Peter Butterfill Date: Thu, 20 Aug 2020 16:22:10 +0100 Subject: [PATCH] updates to how backtick names are converted to doc links --- nbdev/_nbdev.py | 2 + nbdev/export2html.py | 5 +- nbdev/showdoc.py | 33 ++++--- nbs/02_showdoc.ipynb | 186 ++++++++++++++++++++++++++++++++++----- nbs/03_export2html.ipynb | 40 ++++----- 5 files changed, 202 insertions(+), 64 deletions(-) diff --git a/nbdev/_nbdev.py b/nbdev/_nbdev.py index 683f6f40f..2f4cee44c 100644 --- a/nbdev/_nbdev.py +++ b/nbdev/_nbdev.py @@ -32,7 +32,9 @@ "diff_nb_script": "01_sync.ipynb", "is_enum": "02_showdoc.ipynb", "is_lib_module": "02_showdoc.ipynb", + "re_digits_first": "02_showdoc.ipynb", "try_external_doc_link": "02_showdoc.ipynb", + "is_doc_name": "02_showdoc.ipynb", "doc_link": "02_showdoc.ipynb", "add_doc_links": "02_showdoc.ipynb", "get_source_link": "02_showdoc.ipynb", diff --git a/nbdev/export2html.py b/nbdev/export2html.py index 5cec8554e..d14c7d270 100644 --- a/nbdev/export2html.py +++ b/nbdev/export2html.py @@ -512,13 +512,10 @@ def nbdev_exporter(cls=HTMLExporter, template_file=None): process_cells = [remove_fake_headers, remove_hidden, remove_empty] process_cell = [hide_cells, collapse_cells, remove_widget_state, add_jekyll_notes, escape_latex, cite2link] -# Cell -_re_digits = re.compile(r'^\d+\S*?_') - # Cell def _nb2htmlfname(nb_path, dest=None): if dest is None: dest = Config().doc_path - return Path(dest)/_re_digits.sub('', nb_path.with_suffix('.html').name) + return Path(dest)/re_digits_first.sub('', nb_path.with_suffix('.html').name) # Cell def convert_nb(fname, cls=HTMLExporter, template_file=None, exporter=None, dest=None): diff --git a/nbdev/showdoc.py b/nbdev/showdoc.py index fb0c44259..7ec77a36b 100644 --- a/nbdev/showdoc.py +++ b/nbdev/showdoc.py @@ -1,8 +1,8 @@ # AUTOGENERATED! DO NOT EDIT! File to edit: nbs/02_showdoc.ipynb (unless otherwise specified). -__all__ = ['is_enum', 'is_lib_module', 'try_external_doc_link', 'doc_link', 'add_doc_links', 'get_source_link', - 'colab_link', 'get_nb_source_link', 'nb_source_link', 'type_repr', 'format_param', 'show_doc', - 'parse_nbdev_show_doc', 'nbdev_show_doc', 'md2html', 'get_doc_link', 'doc'] +__all__ = ['is_enum', 'is_lib_module', 're_digits_first', 'try_external_doc_link', 'is_doc_name', 'doc_link', + 'add_doc_links', 'get_source_link', 'colab_link', 'get_nb_source_link', 'nb_source_link', 'type_repr', + 'format_param', 'show_doc', 'parse_nbdev_show_doc', 'nbdev_show_doc', 'md2html', 'get_doc_link', 'doc'] # Cell from .imports import * @@ -30,7 +30,7 @@ def is_lib_module(name): except: return False # Cell -_re_digits_first = re.compile('^[0-9]+[a-z]*_') +re_digits_first = re.compile('^[0-9]+[a-z]*_') # Cell def try_external_doc_link(name, packages): @@ -40,21 +40,28 @@ def try_external_doc_link(name, packages): mod = importlib.import_module(f"{p}._nbdev") try_pack = source_nb(name, is_name=True, mod=mod) if try_pack: - page = _re_digits_first.sub('', try_pack).replace('.ipynb', '') + page = re_digits_first.sub('', try_pack).replace('.ipynb', '') return f'{mod.doc_url}{page}#{name}' except ModuleNotFoundError: return None +# Cell +def is_doc_name(name): + "Test if `name` corresponds to a notebook that could be converted to a doc page" + for f in Config().nbs_path.glob(f'*{name}.ipynb'): + if re_digits_first.sub('', f.name) == f'{name}.ipynb': return True + return False + # Cell def doc_link(name, include_bt=True): "Create link to documentation for `name`." cname = f'`{name}`' if include_bt else name try: - #Link to modulesn - if is_lib_module(name): return f"[{cname}]({Config().doc_baseurl}{name}.html)" + #Link to modules + if is_lib_module(name) and is_doc_name(name): return f"[{cname}]({Config().doc_baseurl}{name}.html)" #Link to local functions try_local = source_nb(name, is_name=True) if try_local: - page = _re_digits_first.sub('', try_local).replace('.ipynb', '') + page = re_digits_first.sub('', try_local).replace('.ipynb', '') return f'[{cname}]({Config().doc_baseurl}{page}.html#{name})' ##Custom links mod = get_nbdev_module() @@ -81,9 +88,13 @@ def doc_link(name, include_bt=True): """, re.VERBOSE) # Cell -def add_doc_links(text): +def add_doc_links(text, elt=None): "Search for doc links for any item between backticks in `text` and isnter them" - def _replace_link(m): return doc_link(m.group(1) or m.group(2)) + def _replace_link(m): + try: + if m.group(2) in inspect.signature(elt).parameters: return f'`{m.group(2)}`' + except: pass + return doc_link(m.group(1) or m.group(2)) return _re_backticks.sub(_replace_link, text) # Cell @@ -253,7 +264,7 @@ def show_doc(elt, doc_string=True, name=None, title_level=None, disp=True, defau try: monospace = (Config().get('monospace_docstrings') == 'True') except: monospace = False # doc links don't work inside markdown pre/code blocks - s = f'```\n{s}\n```' if monospace else add_doc_links(s) + s = f'```\n{s}\n```' if monospace else add_doc_links(s, elt) doc += s if disp: display(Markdown(doc)) else: return doc diff --git a/nbs/02_showdoc.ipynb b/nbs/02_showdoc.ipynb index 012e69904..77714b5a7 100644 --- a/nbs/02_showdoc.ipynb +++ b/nbs/02_showdoc.ipynb @@ -133,7 +133,7 @@ "outputs": [], "source": [ "%nbdev_export\n", - "_re_digits_first = re.compile('^[0-9]+[a-z]*_')" + "re_digits_first = re.compile('^[0-9]+[a-z]*_')" ] }, { @@ -150,7 +150,7 @@ " mod = importlib.import_module(f\"{p}._nbdev\")\n", " try_pack = source_nb(name, is_name=True, mod=mod)\n", " if try_pack:\n", - " page = _re_digits_first.sub('', try_pack).replace('.ipynb', '')\n", + " page = re_digits_first.sub('', try_pack).replace('.ipynb', '')\n", " return f'{mod.doc_url}{page}#{name}'\n", " except ModuleNotFoundError: return None" ] @@ -168,10 +168,35 @@ "metadata": {}, "outputs": [], "source": [ - "#test\n", "test_eq(try_external_doc_link('get_name', ['nbdev']), 'https://nbdev.fast.ai/sync#get_name')" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%nbdev_export\n", + "def is_doc_name(name):\n", + " \"Test if `name` corresponds to a notebook that could be converted to a doc page\"\n", + " for f in Config().nbs_path.glob(f'*{name}.ipynb'):\n", + " if re_digits_first.sub('', f.name) == f'{name}.ipynb': return True\n", + " return False" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "test_eq(is_doc_name('flag_tests'),True)\n", + "test_eq(is_doc_name('flags'),False)\n", + "test_eq(is_doc_name('export'),True)\n", + "test_eq(is_doc_name('index'),True)" + ] + }, { "cell_type": "code", "execution_count": null, @@ -183,12 +208,12 @@ " \"Create link to documentation for `name`.\"\n", " cname = f'`{name}`' if include_bt else name\n", " try:\n", - " #Link to modulesn\n", - " if is_lib_module(name): return f\"[{cname}]({Config().doc_baseurl}{name}.html)\"\n", + " #Link to modules\n", + " if is_lib_module(name) and is_doc_name(name): return f\"[{cname}]({Config().doc_baseurl}{name}.html)\"\n", " #Link to local functions\n", " try_local = source_nb(name, is_name=True)\n", " if try_local:\n", - " page = _re_digits_first.sub('', try_local).replace('.ipynb', '')\n", + " page = re_digits_first.sub('', try_local).replace('.ipynb', '')\n", " return f'[{cname}]({Config().doc_baseurl}{page}.html#{name})'\n", " ##Custom links\n", " mod = get_nbdev_module()\n", @@ -201,14 +226,25 @@ "cell_type": "markdown", "metadata": {}, "source": [ - "This function will generate link for a module (pointing to the html conversion of the notebook that created it) and functions (pointing to the hmtl conversion of the notebook they were defined, with the first anchor found before). If the function/module is not part of the library you are writing, it will call the function `custom_doc_links` generated in `_nbdev` (you can customize it to your needs) and just return the name between backticks if that function returns `None`.\n", + "This function will generate links for modules (pointing to the html conversion of the corresponding notebook) and functions (pointing to the html conversion of the notebook where they were defined, with the first anchor found before). If the function/module is not part of the library you are writing, it will call the function `custom_doc_links` generated in `_nbdev` (you can customize it to your needs) and just return the name between backticks if that function returns `None`.\n", "\n", "For instance, fastai2 has the following `custom_doc_links` that tries to find a doc link for `name` in fastcore then nbdev (in this order):\n", "``` python\n", "def custom_doc_links(name): \n", " from nbdev.showdoc import try_external_doc_link\n", " return try_external_doc_link(name, ['fastcore', 'nbdev'])\n", - "```" + "```\n", + "\n", + "Please note that module links only work if your notebook names \"correspond\" to your module names:\n", + "\n", + "| Notebook name | Doc name | Module name | Module file | Can doc link? |\n", + "|--------------------|----------------|-------------|--------------|---------------|\n", + "| export.ipynb | export.html | export | export.py | Yes |\n", + "| 00_export.ipynb | export.html | export | export.py | Yes |\n", + "| 00a_export.ipynb | export.html | export | export.py | Yes |\n", + "| export_1.ipynb | export_1.html | export | export.py | No |\n", + "| 03_data.core.ipynb | data.core.html | data.core | data/core.py | Yes |\n", + "| 03_data_core.ipynb | data_core.html | data.core | data/core.py | No |" ] }, { @@ -222,7 +258,8 @@ "test_eq(doc_link('DocsTestClass.test'), f'[`DocsTestClass.test`](/export.html#DocsTestClass.test)')\n", "test_eq(doc_link('Tenso'),'`Tenso`')\n", "test_eq(doc_link('_nbdev'), f'`_nbdev`')\n", - "test_eq(doc_link('__main__'), f'`__main__`')" + "test_eq(doc_link('__main__'), f'`__main__`')\n", + "test_eq(doc_link('flags'), '`flags`') # we won't have a flags doc page even though we do have a flags module" ] }, { @@ -257,9 +294,13 @@ "outputs": [], "source": [ "%nbdev_export\n", - "def add_doc_links(text):\n", + "def add_doc_links(text, elt=None):\n", " \"Search for doc links for any item between backticks in `text` and isnter them\"\n", - " def _replace_link(m): return doc_link(m.group(1) or m.group(2))\n", + " def _replace_link(m): \n", + " try: \n", + " if m.group(2) in inspect.signature(elt).parameters: return f'`{m.group(2)}`'\n", + " except: pass\n", + " return doc_link(m.group(1) or m.group(2))\n", " return _re_backticks.sub(_replace_link, text)" ] }, @@ -282,6 +323,82 @@ "test_eq(tst, \"This is an example of [`DocsTestClass`](/export.html#DocsTestClass)\")" ] }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Names in backticks will not be converted to links if `elt` has a parameter of the same name" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def t(a,export):\n", + " \"Test func that uses 'export' as a parameter name and has `export` in its doc string\"\n", + "assert '[`export`](/export.html)' not in add_doc_links(t.__doc__, t)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "Names in backticks _used in markdown links_ will be updated like normal" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "def t(a,export):\n", + " \"Test func that uses 'export' as a parameter name and has [`export`]() in its doc string\"\n", + "assert '[`export`](/export.html)' in add_doc_links(t.__doc__, t)" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%nbdev_hide\n", + "# if the name in backticks is not a param name, links will be added/updated like normal\n", + "def t(a,exp):\n", + " \"Test func with `export` in its doc string\"\n", + "assert '[`export`](/export.html)' in add_doc_links(t.__doc__, t)\n", + "def t(a,exp):\n", + " \"Test func with [`export`]() in its doc string\"\n", + "assert '[`export`](/export.html)' in add_doc_links(t.__doc__, t)" + ] + }, + { + "cell_type": "markdown", + "metadata": {}, + "source": [ + "If `elt` is a class, `add_doc_links` looks at parameter names used in `__init__`" + ] + }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "class T:\n", + " def __init__(self, add_doc_links): pass\n", + "test_eq(add_doc_links('Lets talk about `add_doc_links`'), \n", + " 'Lets talk about [`add_doc_links`](/showdoc.html#add_doc_links)')\n", + "test_eq(add_doc_links('Lets talk about `add_doc_links`', T), 'Lets talk about `add_doc_links`')\n", + "test_eq(add_doc_links('Lets talk about `doc_link`'), \n", + " 'Lets talk about [`doc_link`](/showdoc.html#doc_link)')\n", + "test_eq(add_doc_links('Lets talk about `doc_link`', T), \n", + " 'Lets talk about [`doc_link`](/showdoc.html#doc_link)')" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -725,7 +842,7 @@ " try: monospace = (Config().get('monospace_docstrings') == 'True')\n", " except: monospace = False\n", " # doc links don't work inside markdown pre/code blocks\n", - " s = f'```\\n{s}\\n```' if monospace else add_doc_links(s)\n", + " s = f'```\\n{s}\\n```' if monospace else add_doc_links(s, elt)\n", " doc += s\n", " if disp: display(Markdown(doc))\n", " else: return doc" @@ -775,6 +892,21 @@ "show_doc(notebook2script)" ] }, + { + "cell_type": "code", + "execution_count": null, + "metadata": {}, + "outputs": [], + "source": [ + "%nbdev_hide\n", + "def t(a,exp):\n", + " \"Test func with `export` in its doc string\"\n", + "assert '[`export`](/export.html)' in show_doc(t, disp=False)\n", + "def t(a,export):\n", + " \"Test func that uses 'export' as a parameter name and has `export` in its doc string\"\n", + "assert '[`export`](/export.html)' not in show_doc(t, disp=False)" + ] + }, { "cell_type": "markdown", "metadata": {}, @@ -790,10 +922,11 @@ { "data": { "text/markdown": [ - "

class DocsTestClass[source]

\n", + "

class DocsTestClass[source]

\n", "\n", "> DocsTestClass()\n", - "\n" + "\n", + "for tests only" ], "text/plain": [ "" @@ -1143,10 +1276,11 @@ { "data": { "text/markdown": [ - "

class DocsTestClass[source]

\n", + "

class DocsTestClass[source]

\n", "\n", "> DocsTestClass()\n", - "\n" + "\n", + "for tests only" ], "text/plain": [ "" @@ -1203,10 +1337,11 @@ { "data": { "text/markdown": [ - "

class DocsTestClass[source]

\n", + "

class DocsTestClass[source]

\n", "\n", "> DocsTestClass()\n", - "\n" + "\n", + "for tests only" ], "text/plain": [ "" @@ -1254,10 +1389,11 @@ { "data": { "text/markdown": [ - "

class DocsTestClass[source]

\n", + "

class DocsTestClass[source]

\n", "\n", "> DocsTestClass()\n", - "\n" + "\n", + "for tests only" ], "text/plain": [ "" @@ -1326,10 +1462,11 @@ { "data": { "text/markdown": [ - "

class DocsTestClass[source]

\n", + "

class DocsTestClass[source]

\n", "\n", "> DocsTestClass()\n", - "\n" + "\n", + "for tests only" ], "text/plain": [ "" @@ -1415,10 +1552,11 @@ { "data": { "text/markdown": [ - "

class DocsTestClass[source]

\n", + "

class DocsTestClass[source]

\n", "\n", "> DocsTestClass()\n", - "\n" + "\n", + "for tests only" ], "text/plain": [ "" diff --git a/nbs/03_export2html.ipynb b/nbs/03_export2html.ipynb index 2c290459d..f89a67783 100644 --- a/nbs/03_export2html.ipynb +++ b/nbs/03_export2html.ipynb @@ -1774,16 +1774,6 @@ "process_cell = [hide_cells, collapse_cells, remove_widget_state, add_jekyll_notes, escape_latex, cite2link]" ] }, - { - "cell_type": "code", - "execution_count": null, - "metadata": {}, - "outputs": [], - "source": [ - "%nbdev_export\n", - "_re_digits = re.compile(r'^\\d+\\S*?_')" - ] - }, { "cell_type": "code", "execution_count": null, @@ -1793,7 +1783,7 @@ "%nbdev_export\n", "def _nb2htmlfname(nb_path, dest=None):\n", " if dest is None: dest = Config().doc_path\n", - " return Path(dest)/_re_digits.sub('', nb_path.with_suffix('.html').name)" + " return Path(dest)/re_digits_first.sub('', nb_path.with_suffix('.html').name)" ] }, { @@ -1929,20 +1919,20 @@ "name": "stdout", "output_type": "stream", "text": [ - "converting: /home/jhoward/git/nbdev/nbs/05a_conda.ipynb\n", - "converting: /home/jhoward/git/nbdev/nbs/tutorial.ipynb\n", - "converting: /home/jhoward/git/nbdev/nbs/09_nbdev_callback_test.ipynb\n", - "converting: /home/jhoward/git/nbdev/nbs/06_cli.ipynb\n", - "converting: /home/jhoward/git/nbdev/nbs/03_export2html.ipynb\n", - "converting: /home/jhoward/git/nbdev/nbs/04_test.ipynb\n", - "converting: /home/jhoward/git/nbdev/nbs/02_showdoc.ipynb\n", - "converting: /home/jhoward/git/nbdev/nbs/07_clean.ipynb\n", - "converting: /home/jhoward/git/nbdev/nbs/01_sync.ipynb\n", - "converting: /home/jhoward/git/nbdev/nbs/99_search.ipynb\n", - "converting: /home/jhoward/git/nbdev/nbs/00_export.ipynb\n", - "converting: /home/jhoward/git/nbdev/nbs/08_flag_tests.ipynb\n", - "converting: /home/jhoward/git/nbdev/nbs/index.ipynb\n", - "converting: /home/jhoward/git/nbdev/nbs/05_merge.ipynb\n" + "converting: /home/peter/github/pete88b/nbdev/nbs/04_test.ipynb\n", + "converting: /home/peter/github/pete88b/nbdev/nbs/02_showdoc.ipynb\n", + "converting: /home/peter/github/pete88b/nbdev/nbs/01_sync.ipynb\n", + "converting: /home/peter/github/pete88b/nbdev/nbs/05a_conda.ipynb\n", + "converting: /home/peter/github/pete88b/nbdev/nbs/03_export2html.ipynb\n", + "converting: /home/peter/github/pete88b/nbdev/nbs/00_export.ipynb\n", + "converting: /home/peter/github/pete88b/nbdev/nbs/09_nbdev_callback_test.ipynb\n", + "converting: /home/peter/github/pete88b/nbdev/nbs/05_merge.ipynb\n", + "converting: /home/peter/github/pete88b/nbdev/nbs/07_clean.ipynb\n", + "converting: /home/peter/github/pete88b/nbdev/nbs/index.ipynb\n", + "converting: /home/peter/github/pete88b/nbdev/nbs/08_flag_tests.ipynb\n", + "converting: /home/peter/github/pete88b/nbdev/nbs/tutorial.ipynb\n", + "converting: /home/peter/github/pete88b/nbdev/nbs/06_cli.ipynb\n", + "converting: /home/peter/github/pete88b/nbdev/nbs/99_search.ipynb\n" ] }, {