#hide
#default_exp lookup
#export
from fastcore.utils import *
from fastcore.foundation import *
import pkg_resources,importlib
from fastcore.all import *
from IPython.display import Markdown
Convert backticks to links
Python entry points are a way to register information that is available across all python projects in an environment. They are registered using a key in setuptools
scripts. They provide a simple key-value store. nbdev
uses an entry point group to make available an index of symbols for each nbdev project, as well as some Sphinx projects that have been converted, including the Python standard library. You can see all registered projects:
projs = L(pkg_resources.iter_entry_points(group='nbdev'))
projs
(#7) [EntryPoint.parse('index = showdoc._nbdev'),EntryPoint.parse('index = nbdev_stdlib._nbdev'),EntryPoint.parse('index = nbdev_scipy._nbdev'),EntryPoint.parse('index = nbdev_pytorch._nbdev'),EntryPoint.parse('index = nbdev_pandas._nbdev'),EntryPoint.parse('index = nbdev_numpy._nbdev'),EntryPoint.parse('index = nbdev._nbdev')]
An nbdev entry point can be loaded, an will return an _nbdev
module.
nbdev_idxs
is a mapping from module names to loaded nbdev entry points.
#export
def _try_load(o):
try: return o.dist.key,o.load()
except ImportError: None
nbdev_idxs = dict(L(pkg_resources.iter_entry_points(group='nbdev')).map_filter(_try_load))
nbmod = nbdev_idxs['nbdev']
This module contains modidx
, which includes the symbols built in this module:
nbmod.modidx['syms']['nbdev.sync']
{'nbdev.sync.nbdev_update_lib': 'https://nbdev.fast.ai/nbdev.sync#nbdev_update_lib'}
It also contains a copy of the information from the settings.ini
file:
nbmod.modidx['settings']['version']
'2.0.0'
#export
nbdev_idx_mods = {mod:ep.modidx for lib,ep in nbdev_idxs.items() for mod in ep.modidx['syms']}
nbdev_idx_mods
is a mapping from module names to the modidx
for the package that each module is defined in.
list(nbdev_idx_mods['nbdev.sync'])
['syms', 'settings']
#export
class ShowdocLookup:
"Mapping from symbol names to URLs with docs"
def __init__(self, strip_libs=None, incl_libs=None, skip_mods=None):
skip_mods,strip_libs = setify(skip_mods),L(strip_libs)
if incl_libs is not None: incl_libs = (L(incl_libs)+strip_libs).unique()
self.entries = filter_keys(nbdev_idxs, lambda k: incl_libs is None or k in incl_libs)
py_syms = merge(*L(o.modidx['syms'].values() for o in self.entries.values()).concat())
for m in strip_libs:
_d = self.entries[m].modidx
stripped = {remove_prefix(k,f"{mod}."):v
for mod,dets in _d['syms'].items() if mod not in skip_mods
for k,v in dets.items()}
py_syms = merge(stripped, py_syms)
self.syms = py_syms
def __getitem__(self, s): return self.syms.get(s, None)
Symbol names are taken from libraries registered using the 'nbdev' entry point. By default, all libraries with this entry point are searched, but full symbol names (including module prefix) are required.
c = ShowdocLookup()
assert c['nbdev.doclinks.DocLinks'].startswith('http')
assert c['numpy.array'].startswith('http')
assert not c['DocLinks']
Pass strip_libs
to list libraries which should be available without requiring a module prefix.
c = ShowdocLookup(strip_libs=['nbdev','nbdev-numpy'])
assert c['DocLinks'].startswith('http')
assert c['numpy.array'].startswith('http')
assert c['array'].startswith('http')
#export
from nbdev._nbdev import modidx
#export
_showdoc_lookup = None
def _settings(fr, mod=None):
if mod is None: mod = fr.f_globals['__name__']
return nested_idx(nbdev_idx_mods, mod, 'settings') or {}
def init_showdoc(settings=None, mod=None):
"Create an `ShowdocLookup` using values from settings"
if settings is None: settings = _settings(sys._getframe(1), mod)
strip_libs = settings.get('strip_libs',settings.get('lib_name','')).split()
incl_libs = settings.get('index_libs',None)
if incl_libs is not None: incl_libs = incl_libs.split()
global _showdoc_lookup
_showdoc_lookup = ShowdocLookup(strip_libs=strip_libs, incl_libs=incl_libs)
#export
def showdoc_lookup():
"`ShowdocLookup` singleton using settings from calling frame"
if not _showdoc_lookup: init_showdoc(_settings(sys._getframe(1)))
return _showdoc_lookup
strip_libs
is taken from settings if present, otherwise lib_name
is used. incl_libs
is taken from settings if present, otherwise all registered entry points are used.
__name__ = 'nbdev.export'
init_showdoc()
assert showdoc_lookup()['nbdev.doclinks.DocLinks'].startswith('http')
assert showdoc_lookup()['DocLinks'].startswith('http')
assert showdoc_lookup()['numpy.array'].startswith('http')
assert not showdoc_lookup()['array']
#export
@patch
def _link_sym(self:ShowdocLookup, skipped, m):
l = m.group(1)
if l in skipped: return m.group(0)
s = self[l]
if s is None: return m.group(0)
return rf"[{m.group(0)}]({s})"
_re_backticks = re.compile(r'`([^`\s]+)`')
@patch
def _link_line(self:ShowdocLookup, l, skipped):
return _re_backticks.sub(partial(self._link_sym, skipped), l)
@patch
def linkify(self:ShowdocLookup, md, skipped=None):
"Convert backtick code in `md` to doc links, except for symbols in `skipped`"
in_fence=False
lines = md.splitlines()
for i,l in enumerate(lines):
if l.startswith("```"): in_fence=not in_fence
elif not l.startswith(' ') and not in_fence: lines[i] = self._link_line(l, L(skipped))
return '\n'.join(lines)
md = """This is a link to `numpy.array` and to `read_nb` but not a link to `foobar`.
And not a link to <code>dict2nb</code>.
This is not a link to `read_nb`
```
This isn't a link to `read_nb` either
```"""
c = ShowdocLookup('nbdev')
Markdown(c.linkify(md))
This is a link to numpy.array
and to read_nb
but not a link to foobar
.
And not a link to dict2nb
.
This is not a link to `read_nb`
This isn't a link to `read_nb` either
from nbdev.doclinks import nbdev_build_lib
nbdev_build_lib()