nbdev 2.3.25__py3-none-any.whl → 2.4.8__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
nbdev/processors.py CHANGED
@@ -1,11 +1,13 @@
1
+ """Some processors for NBProcessor"""
2
+
1
3
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/10_processors.ipynb.
2
4
 
3
5
  # %% auto 0
4
6
  __all__ = ['populate_language', 'insert_warning', 'cell_lang', 'add_show_docs', 'fdiv', 'boxify', 'mv_exports', 'add_links',
5
- 'add_fold', 'strip_ansi', 'strip_hidden_metadata', 'hide_', 'hide_line', 'filter_stream_', 'clean_magics',
6
- 'rm_header_dash', 'rm_export', 'clean_show_doc', 'exec_show_docs', 'FilterDefaults']
7
+ 'add_fold', 'strip_ansi', 'strip_hidden_metadata', 'hide_', 'hide_line', 'filter_stream_', 'ai_magics',
8
+ 'clean_magics', 'rm_header_dash', 'rm_export', 'clean_show_doc', 'exec_show_docs', 'FilterDefaults']
7
9
 
8
- # %% ../nbs/api/10_processors.ipynb 2
10
+ # %% ../nbs/api/10_processors.ipynb
9
11
  import ast
10
12
  import importlib
11
13
 
@@ -23,7 +25,7 @@ from fastcore.imports import *
23
25
  from fastcore.xtras import *
24
26
  import sys,yaml
25
27
 
26
- # %% ../nbs/api/10_processors.ipynb 7
28
+ # %% ../nbs/api/10_processors.ipynb
27
29
  _langs = 'bash|html|javascript|js|latex|markdown|perl|ruby|sh|svg'
28
30
  _lang_pattern = re.compile(rf'^\s*%%\s*({_langs})\s*$', flags=re.MULTILINE)
29
31
 
@@ -36,13 +38,13 @@ class populate_language(Processor):
36
38
  if lang: cell.metadata.language = lang[0]
37
39
  else: cell.metadata.language = self.language
38
40
 
39
- # %% ../nbs/api/10_processors.ipynb 9
41
+ # %% ../nbs/api/10_processors.ipynb
40
42
  class insert_warning(Processor):
41
43
  "Insert Autogenerated Warning Into Notebook after the first cell."
42
44
  content = "<!-- WARNING: THIS FILE WAS AUTOGENERATED! DO NOT EDIT! -->"
43
45
  def begin(self): self.nb.cells.insert(1, mk_cell(self.content, 'markdown'))
44
46
 
45
- # %% ../nbs/api/10_processors.ipynb 13
47
+ # %% ../nbs/api/10_processors.ipynb
46
48
  _def_types = (ast.FunctionDef,ast.AsyncFunctionDef,ast.ClassDef)
47
49
  def _def_names(cell, shown):
48
50
  cellp = cell.parsed_()
@@ -55,7 +57,7 @@ def _get_nm(tree):
55
57
  else: val = try_attrs(i.value, 'id', 'func', 'attr')
56
58
  return f'{val}.{i.attr}' if isinstance(i, ast.Attribute) else i.id
57
59
 
58
- # %% ../nbs/api/10_processors.ipynb 14
60
+ # %% ../nbs/api/10_processors.ipynb
59
61
  def _show_docs(trees):
60
62
  return [t for t in trees if isinstance(t,ast.Expr) and nested_attr(t, 'value.func.id')=='show_doc']
61
63
 
@@ -82,20 +84,20 @@ class add_show_docs(Processor):
82
84
  nb.cells.insert(cell.idx_+1, new_cell)
83
85
  nb.has_docs_ = shown_docs or exports
84
86
 
85
- # %% ../nbs/api/10_processors.ipynb 17
87
+ # %% ../nbs/api/10_processors.ipynb
86
88
  def fdiv(attrs=''):
87
89
  "Create a fenced div markdown cell in quarto"
88
90
  if attrs: attrs = ' {'+attrs+'}'
89
91
  return mk_cell(':::'+attrs, cell_type='markdown')
90
92
 
91
- # %% ../nbs/api/10_processors.ipynb 19
93
+ # %% ../nbs/api/10_processors.ipynb
92
94
  def boxify(cells):
93
95
  "Add a box around `cells`"
94
96
  if not isinstance(cells, list): cells = [cells]
95
97
  res = [fdiv('.py-2 .px-3 .mb-4 fig-align="center" .border .rounded .shadow-sm')]
96
98
  return res+cells+[fdiv()]
97
99
 
98
- # %% ../nbs/api/10_processors.ipynb 20
100
+ # %% ../nbs/api/10_processors.ipynb
99
101
  class mv_exports(Processor):
100
102
  "Move `exports` cells to after the `show_doc`"
101
103
  def begin(self):
@@ -108,7 +110,7 @@ class mv_exports(Processor):
108
110
  srccell = cells.pop(idx)
109
111
  cells[idx:idx] = [doccell,srccell]
110
112
 
111
- # %% ../nbs/api/10_processors.ipynb 21
113
+ # %% ../nbs/api/10_processors.ipynb
112
114
  _re_defaultexp = re.compile(r'^\s*#\|\s*default_exp\s+(\S+)', flags=re.MULTILINE)
113
115
 
114
116
  def _default_exp(nb):
@@ -117,7 +119,7 @@ def _default_exp(nb):
117
119
  default_exp = first(code_src.filter().map(_re_defaultexp.search).filter())
118
120
  return default_exp.group(1) if default_exp else None
119
121
 
120
- # %% ../nbs/api/10_processors.ipynb 23
122
+ # %% ../nbs/api/10_processors.ipynb
121
123
  def add_links(cell):
122
124
  "Add links to markdown cells"
123
125
  nl = NbdevLookup()
@@ -126,13 +128,13 @@ def add_links(cell):
126
128
  if hasattr(o, 'data') and hasattr(o['data'], 'text/markdown'):
127
129
  o.data['text/markdown'] = [nl.link_line(s) for s in o.data['text/markdown']]
128
130
 
129
- # %% ../nbs/api/10_processors.ipynb 25
131
+ # %% ../nbs/api/10_processors.ipynb
130
132
  def add_fold(cell):
131
133
  "Add `code-fold` to `exports` cells"
132
134
  if cell.cell_type != 'code' or 'exports' not in cell.directives_: return
133
135
  cell.source = f'#| code-fold: show\n#| code-summary: "Exported source"\n{cell.source}'
134
136
 
135
- # %% ../nbs/api/10_processors.ipynb 28
137
+ # %% ../nbs/api/10_processors.ipynb
136
138
  _re_ansi_escape = re.compile(r'\x1B(?:[@-Z\\-_]|\[[0-?]*[ -/]*[@-~])')
137
139
 
138
140
  def strip_ansi(cell):
@@ -140,17 +142,17 @@ def strip_ansi(cell):
140
142
  for outp in cell.get('outputs', []):
141
143
  if outp.get('name')=='stdout': outp['text'] = [_re_ansi_escape.sub('', o) for o in outp.text]
142
144
 
143
- # %% ../nbs/api/10_processors.ipynb 30
145
+ # %% ../nbs/api/10_processors.ipynb
144
146
  def strip_hidden_metadata(cell):
145
147
  '''Strips "hidden" metadata property from code cells so it doesn't interfere with docs rendering'''
146
148
  if cell.cell_type == 'code' and 'metadata' in cell: cell.metadata.pop('hidden',None)
147
149
 
148
- # %% ../nbs/api/10_processors.ipynb 31
150
+ # %% ../nbs/api/10_processors.ipynb
149
151
  def hide_(cell):
150
152
  "Hide cell from output"
151
153
  del(cell['source'])
152
154
 
153
- # %% ../nbs/api/10_processors.ipynb 33
155
+ # %% ../nbs/api/10_processors.ipynb
154
156
  def _re_hideline(lang=None): return re.compile(fr'{langs[lang]}\|\s*hide_line\s*$', re.MULTILINE)
155
157
 
156
158
  def hide_line(cell):
@@ -159,7 +161,7 @@ def hide_line(cell):
159
161
  if cell.cell_type == 'code' and _re_hideline(lang).search(cell.source):
160
162
  cell.source = '\n'.join([c for c in cell.source.splitlines() if not _re_hideline(lang).search(c)])
161
163
 
162
- # %% ../nbs/api/10_processors.ipynb 36
164
+ # %% ../nbs/api/10_processors.ipynb
163
165
  def filter_stream_(cell, *words):
164
166
  "Remove output lines containing any of `words` in `cell` stream output"
165
167
  if not words: return
@@ -167,14 +169,23 @@ def filter_stream_(cell, *words):
167
169
  if outp.output_type == 'stream':
168
170
  outp['text'] = [l for l in outp.text if not re.search('|'.join(words), l)]
169
171
 
170
- # %% ../nbs/api/10_processors.ipynb 38
172
+ # %% ../nbs/api/10_processors.ipynb
173
+ _aimagics_pattern = re.compile(r'^\s*(%%ai.? |%%ai.?$)', re.MULTILINE)
174
+
175
+ def ai_magics(cell):
176
+ "A preprocessor to convert AI magics to markdown"
177
+ if cell.cell_type == 'code' and _aimagics_pattern.search(cell.source):
178
+ cell.cell_type ='markdown'
179
+ cell.source = '\n'.join(cell.source.splitlines()[1:])
180
+
181
+ # %% ../nbs/api/10_processors.ipynb
171
182
  _magics_pattern = re.compile(r'^\s*(%%|%).*', re.MULTILINE)
172
183
 
173
184
  def clean_magics(cell):
174
185
  "A preprocessor to remove cell magic commands"
175
186
  if cell.cell_type == 'code': cell.source = _magics_pattern.sub('', cell.source).strip()
176
187
 
177
- # %% ../nbs/api/10_processors.ipynb 40
188
+ # %% ../nbs/api/10_processors.ipynb
178
189
  _re_hdr_dash = re.compile(r'^#+\s+.*\s+-\s*$', re.MULTILINE)
179
190
 
180
191
  def rm_header_dash(cell):
@@ -183,26 +194,26 @@ def rm_header_dash(cell):
183
194
  src = cell.source.strip()
184
195
  if cell.cell_type == 'markdown' and src.startswith('#') and src.endswith(' -'): del(cell['source'])
185
196
 
186
- # %% ../nbs/api/10_processors.ipynb 42
197
+ # %% ../nbs/api/10_processors.ipynb
187
198
  _hide_dirs = {'export','exporti', 'hide','default_exp'}
188
199
 
189
200
  def rm_export(cell):
190
201
  "Remove cells that are exported or hidden"
191
202
  if cell.directives_ and (cell.directives_.keys() & _hide_dirs): del(cell['source'])
192
203
 
193
- # %% ../nbs/api/10_processors.ipynb 44
204
+ # %% ../nbs/api/10_processors.ipynb
194
205
  _re_showdoc = re.compile(r'^show_doc', re.MULTILINE)
195
206
  def _is_showdoc(cell): return cell['cell_type'] == 'code' and _re_showdoc.search(cell.source)
196
207
  def _add_directives(cell, d):
197
208
  for k,v in d.items():
198
- if not re.findall(f'#\| *{k}:', cell.source): cell.source = f'#| {k}: {v}\n' + cell.source
209
+ if not re.findall(fr'#\| *{k}:', cell.source): cell.source = f'#| {k}: {v}\n' + cell.source
199
210
 
200
211
  def clean_show_doc(cell):
201
212
  "Remove ShowDoc input cells"
202
213
  if not _is_showdoc(cell): return
203
214
  _add_directives(cell, {'output':'asis','echo':'false'})
204
215
 
205
- # %% ../nbs/api/10_processors.ipynb 45
216
+ # %% ../nbs/api/10_processors.ipynb
206
217
  def _ast_contains(trees, types):
207
218
  for tree in trees:
208
219
  for node in ast.walk(tree):
@@ -223,7 +234,7 @@ def _do_eval(cell):
223
234
  return True
224
235
  if _show_docs(trees): return True
225
236
 
226
- # %% ../nbs/api/10_processors.ipynb 46
237
+ # %% ../nbs/api/10_processors.ipynb
227
238
  class exec_show_docs(Processor):
228
239
  "Execute cells needed for `show_docs` output, including exported cells and imports"
229
240
  def begin(self):
@@ -238,7 +249,7 @@ class exec_show_docs(Processor):
238
249
  if _do_eval(cell): self.k.cell(cell)
239
250
  title = fm.get('title', '')
240
251
  if self.k.exc:
241
- raise Exception(f"Error{' in notebook: '+title if title else ''} in cell {cell.idx_} :\n{cell.source}") from self.k.exc[1]
252
+ raise Exception(f"Error{' in notebook: '+title if title else ''} in cell {cell.idx_} :\n{cell.source}") from self.k.exc
242
253
 
243
254
  def end(self):
244
255
  try: from ipywidgets import Widget
@@ -250,13 +261,13 @@ class exec_show_docs(Processor):
250
261
  widgets = {**old, **new, 'state': {**old.get('state', {}), **new['state']}}
251
262
  self.nb.metadata['widgets'] = {mimetype: widgets}
252
263
 
253
- # %% ../nbs/api/10_processors.ipynb 48
264
+ # %% ../nbs/api/10_processors.ipynb
254
265
  def _import_obj(s):
255
266
  mod_nm, obj_nm = s.split(':')
256
267
  mod = importlib.import_module(mod_nm)
257
268
  return getattr(mod, obj_nm)
258
269
 
259
- # %% ../nbs/api/10_processors.ipynb 49
270
+ # %% ../nbs/api/10_processors.ipynb
260
271
  class FilterDefaults:
261
272
  "Override `FilterDefaults` to change which notebook processors are used"
262
273
  def xtra_procs(self):
@@ -266,11 +277,13 @@ class FilterDefaults:
266
277
  def base_procs(self):
267
278
  return [FrontmatterProc, populate_language, add_show_docs, insert_warning,
268
279
  strip_ansi, hide_line, filter_stream_, rm_header_dash,
269
- clean_show_doc, exec_show_docs, rm_export, clean_magics, hide_, add_links, add_fold, mv_exports, strip_hidden_metadata]
280
+ clean_show_doc, exec_show_docs, rm_export, ai_magics, clean_magics, hide_, add_links,
281
+ add_fold, mv_exports, strip_hidden_metadata]
270
282
 
271
283
  def procs(self):
272
284
  "Processors for export"
273
- return self.base_procs() + self.xtra_procs()
285
+ skip_procs = get_config().get('skip_procs', '').split(',')
286
+ return L(self.base_procs()).filter(lambda x: x.__name__ not in skip_procs) + self.xtra_procs()
274
287
 
275
288
  def nb_proc(self, nb):
276
289
  "Get an `NBProcessor` with these processors"
nbdev/qmd.py CHANGED
@@ -1,3 +1,5 @@
1
+ """Basic qmd generation helpers (experimental)"""
2
+
1
3
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/15_qmd.ipynb.
2
4
 
3
5
  # %% ../nbs/api/15_qmd.ipynb 2
@@ -10,7 +12,7 @@ from fastcore.meta import delegates
10
12
  # %% auto 0
11
13
  __all__ = ['meta', 'div', 'img', 'btn', 'tbl_row', 'tbl_sep']
12
14
 
13
- # %% ../nbs/api/15_qmd.ipynb 4
15
+ # %% ../nbs/api/15_qmd.ipynb
14
16
  def meta(md, # Markdown to add meta to
15
17
  classes=None, # List of CSS classes to add
16
18
  style=None, # Dict of CSS styles to add
@@ -25,7 +27,7 @@ def meta(md, # Markdown to add meta to
25
27
  meta = ' '.join(meta)
26
28
  return md + ("{" + meta + "}" if meta else "")
27
29
 
28
- # %% ../nbs/api/15_qmd.ipynb 5
30
+ # %% ../nbs/api/15_qmd.ipynb
29
31
  def div(txt, # Markdown to add meta to
30
32
  classes=None, # List of CSS classes to add
31
33
  style=None, # Dict of CSS styles to add
@@ -33,7 +35,7 @@ def div(txt, # Markdown to add meta to
33
35
  "A qmd div with optional metadata section"
34
36
  return meta("::: ", classes=classes, style=style, **kwargs) + f"\n\n{txt}\n\n:::\n\n"
35
37
 
36
- # %% ../nbs/api/15_qmd.ipynb 6
38
+ # %% ../nbs/api/15_qmd.ipynb
37
39
  def img(fname, # Image to link to
38
40
  classes=None, # List of CSS classes to add
39
41
  style=None, # Dict of CSS styles to add
@@ -51,7 +53,7 @@ def img(fname, # Image to link to
51
53
  res = meta(f'![]({fname})', classes=classes, style=style, **kwargs)
52
54
  return f'[{res}]({fname})' if link else res
53
55
 
54
- # %% ../nbs/api/15_qmd.ipynb 7
56
+ # %% ../nbs/api/15_qmd.ipynb
55
57
  def btn(txt, # Button text
56
58
  link, # Button link URL
57
59
  classes=None, # List of CSS classes to add
@@ -60,20 +62,20 @@ def btn(txt, # Button text
60
62
  "A qmd button"
61
63
  return meta(f'[{txt}]({link})', classes=classes, style=style, role="button")
62
64
 
63
- # %% ../nbs/api/15_qmd.ipynb 8
65
+ # %% ../nbs/api/15_qmd.ipynb
64
66
  def tbl_row(cols:list, # Auto-stringified columns to show in the row
65
67
  ):
66
68
  "Create a markdown table row from `cols`"
67
69
  return '|' + '|'.join(str(c or '') for c in cols) + '|'
68
70
 
69
- # %% ../nbs/api/15_qmd.ipynb 9
71
+ # %% ../nbs/api/15_qmd.ipynb
70
72
  def tbl_sep(sizes:int|list=3 # List of column sizes, or single `int` if all sizes the same
71
73
  ):
72
74
  "Create a markdown table separator with relative column size `sizes`"
73
75
  if isinstance(sizes,int): sizes = [3]*sizes
74
76
  return tbl_row('-'*s for s in sizes)
75
77
 
76
- # %% ../nbs/api/15_qmd.ipynb 10
78
+ # %% ../nbs/api/15_qmd.ipynb
77
79
  def _install_nbdev():
78
80
  return div('''#### pip
79
81
 
nbdev/quarto.py CHANGED
@@ -1,3 +1,5 @@
1
+ """Install and interact with Quarto from nbdev"""
2
+
1
3
  # AUTOGENERATED! DO NOT EDIT! File to edit: ../nbs/api/14_quarto.ipynb.
2
4
 
3
5
  # %% ../nbs/api/14_quarto.ipynb 3
@@ -19,14 +21,15 @@ import yaml
19
21
 
20
22
  # %% auto 0
21
23
  __all__ = ['BASE_QUARTO_URL', 'install_quarto', 'install', 'IndentDumper', 'nbdev_sidebar', 'refresh_quarto_yml',
22
- 'nbdev_proc_nbs', 'nbdev_readme', 'nbdev_docs', 'prepare', 'fs_watchdog', 'nbdev_preview']
24
+ 'nbdev_proc_nbs', 'nbdev_readme', 'nbdev_contributing', 'nbdev_docs', 'prepare', 'fs_watchdog',
25
+ 'nbdev_preview']
23
26
 
24
- # %% ../nbs/api/14_quarto.ipynb 5
27
+ # %% ../nbs/api/14_quarto.ipynb
25
28
  def _sprun(cmd):
26
29
  try: subprocess.check_output(cmd, shell=True)
27
30
  except subprocess.CalledProcessError as cpe: sys.exit(cpe.returncode)
28
31
 
29
- # %% ../nbs/api/14_quarto.ipynb 7
32
+ # %% ../nbs/api/14_quarto.ipynb
30
33
  BASE_QUARTO_URL='https://www.quarto.org/download/latest/'
31
34
 
32
35
  def _install_linux():
@@ -53,7 +56,7 @@ def install_quarto():
53
56
  elif 'linux' in sys.platform: _install_linux()
54
57
  finally: system('sudo rm -f .installing')
55
58
 
56
- # %% ../nbs/api/14_quarto.ipynb 8
59
+ # %% ../nbs/api/14_quarto.ipynb
57
60
  @call_parse
58
61
  def install():
59
62
  "Install Quarto and the current library"
@@ -61,14 +64,14 @@ def install():
61
64
  d = get_config().lib_path
62
65
  if (d/'__init__.py').exists(): system(f'pip install -e "{d.parent}[dev]"')
63
66
 
64
- # %% ../nbs/api/14_quarto.ipynb 10
67
+ # %% ../nbs/api/14_quarto.ipynb
65
68
  def _pre(p,b=True): return ' ' * (len(p.parts)) + ('- ' if b else ' ')
66
69
  def _sort(a):
67
70
  x,y = a
68
71
  if y.startswith('index.'): return x,'00'
69
72
  return a
70
- #|export
71
- _def_file_re = '\.(?:ipynb|qmd|html)$'
73
+ #| export
74
+ _def_file_re = r'\.(?:ipynb|qmd|html)$'
72
75
 
73
76
  @delegates(nbglob_cli)
74
77
  def _nbglob_docs(
@@ -78,7 +81,7 @@ def _nbglob_docs(
78
81
  **kwargs):
79
82
  return nbglob(path, file_glob=file_glob, file_re=file_re, **kwargs)
80
83
 
81
- # %% ../nbs/api/14_quarto.ipynb 11
84
+ # %% ../nbs/api/14_quarto.ipynb
82
85
  def _recursive_parser(
83
86
  dir_dict: dict, # Directory structure as a dict.
84
87
  contents: list, # `contents` list from `sidebar.yaml` template dict.
@@ -87,12 +90,12 @@ def _recursive_parser(
87
90
  set_index: bool = True): # If `True`, `index` file will be set to href.
88
91
  for name, val in dir_dict.items():
89
92
  if type(val) is str:
90
- if re.search('index\..*', re.sub('^\d+_', '', val)) and set_index and section:
93
+ if re.search(r'index\..*', re.sub(r'^\d+_', '', val)) and set_index and section:
91
94
  section.update({'href': str(dirpath/val)})
92
95
  else:
93
96
  contents.append(str(dirpath/val))
94
97
  elif type(val) is dict:
95
- name = re.sub('^\d+_', '', name)
98
+ name = re.sub(r'^\d+_', '', name)
96
99
  section = {'section': name, 'contents': []}
97
100
  contents.append(section)
98
101
  _recursive_parser(val, section['contents'], dirpath/name, section=section)
@@ -101,14 +104,14 @@ class IndentDumper(yaml.Dumper):
101
104
  def increase_indent(self, flow=False, indentless=False):
102
105
  return super(IndentDumper, self).increase_indent(flow, False)
103
106
 
104
- # %% ../nbs/api/14_quarto.ipynb 12
107
+ # %% ../nbs/api/14_quarto.ipynb
105
108
  @call_parse
106
109
  @delegates(_nbglob_docs)
107
110
  def nbdev_sidebar(
108
111
  path:str=None, # Path to notebooks
109
112
  printit:bool=False, # Print YAML for debugging
110
113
  force:bool=False, # Create sidebar even if settings.ini custom_sidebar=False
111
- skip_folder_re:str='(?:^[_.]|^www\$)', # Skip folders matching regex
114
+ skip_folder_re:str=r'(?:^[_.]|^www\$)', # Skip folders matching regex
112
115
  **kwargs):
113
116
  "Create sidebar.yml"
114
117
  if not force and get_config().custom_sidebar: return
@@ -136,7 +139,7 @@ def nbdev_sidebar(
136
139
  if printit: return print(yml)
137
140
  yml_path.write_text(yml)
138
141
 
139
- # %% ../nbs/api/14_quarto.ipynb 15
142
+ # %% ../nbs/api/14_quarto.ipynb
140
143
  _quarto_yml="""project:
141
144
  type: website
142
145
 
@@ -145,6 +148,8 @@ format:
145
148
  theme: cosmo
146
149
  css: styles.css
147
150
  toc: true
151
+ keep-md: true
152
+ commonmark: default
148
153
 
149
154
  website:
150
155
  twitter-card: true
@@ -158,7 +163,7 @@ website:
158
163
 
159
164
  metadata-files: [nbdev.yml, sidebar.yml]"""
160
165
 
161
- # %% ../nbs/api/14_quarto.ipynb 16
166
+ # %% ../nbs/api/14_quarto.ipynb
162
167
  _nbdev_yml="""project:
163
168
  output-dir: {doc_path}
164
169
 
@@ -170,7 +175,7 @@ website:
170
175
  repo-url: "{git_url}"
171
176
  """
172
177
 
173
- # %% ../nbs/api/14_quarto.ipynb 17
178
+ # %% ../nbs/api/14_quarto.ipynb
174
179
  def refresh_quarto_yml():
175
180
  "Generate `_quarto.yml` from `settings.ini`."
176
181
  cfg = get_config()
@@ -184,13 +189,13 @@ def refresh_quarto_yml():
184
189
  if qy.exists() and not str2bool(cfg.get('custom_quarto_yml', True)): qy.unlink()
185
190
  if not qy.exists(): qy.write_text(_quarto_yml)
186
191
 
187
- # %% ../nbs/api/14_quarto.ipynb 18
192
+ # %% ../nbs/api/14_quarto.ipynb
188
193
  def _ensure_quarto():
189
194
  if shutil.which('quarto'): return
190
195
  print("Quarto is not installed. We will download and install it for you.")
191
196
  install.__wrapped__()
192
197
 
193
- # %% ../nbs/api/14_quarto.ipynb 19
198
+ # %% ../nbs/api/14_quarto.ipynb
194
199
  def _pre_docs(path=None, n_workers:int=defaults.cpus, **kwargs):
195
200
  cfg = get_config()
196
201
  path = Path(path) if path else cfg.nbs_path
@@ -202,21 +207,21 @@ def _pre_docs(path=None, n_workers:int=defaults.cpus, **kwargs):
202
207
  cache = proc_nbs(path, n_workers=n_workers, **kwargs)
203
208
  return cache,cfg,path
204
209
 
205
- # %% ../nbs/api/14_quarto.ipynb 20
210
+ # %% ../nbs/api/14_quarto.ipynb
206
211
  @call_parse
207
212
  @delegates(proc_nbs)
208
213
  def nbdev_proc_nbs(**kwargs):
209
214
  "Process notebooks in `path` for docs rendering"
210
215
  _pre_docs(**kwargs)[0]
211
216
 
212
- # %% ../nbs/api/14_quarto.ipynb 22
213
- def _readme_mtime_not_older(readme_path, readme_nb_path):
217
+ # %% ../nbs/api/14_quarto.ipynb
218
+ def _doc_mtime_not_older(readme_path, readme_nb_path):
214
219
  if not readme_nb_path.exists():
215
220
  print(f"Could not find {readme_nb_path}")
216
221
  return True
217
222
  return readme_path.exists() and readme_path.stat().st_mtime>=readme_nb_path.stat().st_mtime
218
223
 
219
- # %% ../nbs/api/14_quarto.ipynb 23
224
+ # %% ../nbs/api/14_quarto.ipynb
220
225
  class _SidebarYmlRemoved:
221
226
  "Context manager for `nbdev_readme` to avoid rendering whole docs website"
222
227
  def __init__(self,path): self._path=path
@@ -229,14 +234,14 @@ class _SidebarYmlRemoved:
229
234
  def __exit__(self, exc_type, exc_value, exc_tb):
230
235
  if self._moved: (self._path/'sidebar.yml.bak').rename(self._yml_path)
231
236
 
232
- # %% ../nbs/api/14_quarto.ipynb 24
237
+ # %% ../nbs/api/14_quarto.ipynb
233
238
  def _copytree(a,b):
234
239
  if sys.version_info.major >=3 and sys.version_info.minor >=8: copytree(a, b, dirs_exist_ok=True)
235
240
  else:
236
241
  from distutils.dir_util import copy_tree
237
242
  copy_tree(a, b)
238
243
 
239
- # %% ../nbs/api/14_quarto.ipynb 25
244
+ # %% ../nbs/api/14_quarto.ipynb
240
245
  def _save_cached_readme(cache, cfg):
241
246
  tmp_doc_path = cache/cfg.doc_path.name
242
247
  readme = tmp_doc_path/'README.md'
@@ -247,7 +252,7 @@ def _save_cached_readme(cache, cfg):
247
252
  _rdmi = tmp_doc_path/((cache/cfg.readme_nb).stem + '_files') # Supporting files for README
248
253
  if _rdmi.exists(): _copytree(_rdmi, cfg.config_path/_rdmi.name)
249
254
 
250
- # %% ../nbs/api/14_quarto.ipynb 26
255
+ # %% ../nbs/api/14_quarto.ipynb
251
256
  @call_parse
252
257
  def nbdev_readme(
253
258
  path:str=None, # Path to notebooks
@@ -255,7 +260,7 @@ def nbdev_readme(
255
260
  "Create README.md from readme_nb (index.ipynb by default)"
256
261
  cfg = get_config()
257
262
  path = Path(path) if path else cfg.nbs_path
258
- if chk_time and _readme_mtime_not_older(cfg.config_path/'README.md', path/cfg.readme_nb): return
263
+ if chk_time and _doc_mtime_not_older(cfg.config_path/'README.md', path/cfg.readme_nb): return
259
264
 
260
265
  with _SidebarYmlRemoved(path): # to avoid rendering whole website
261
266
  cache = proc_nbs(path)
@@ -263,7 +268,39 @@ def nbdev_readme(
263
268
 
264
269
  _save_cached_readme(cache, cfg)
265
270
 
266
- # %% ../nbs/api/14_quarto.ipynb 29
271
+ # %% ../nbs/api/14_quarto.ipynb
272
+ def _save_cached_contributing(cache, cfg, contrib_nb):
273
+ "Move CONTRIBUTING.md (and any `_files` assets) from the Quarto build cache to the repo root."
274
+ tmp_doc_path = cache / cfg.doc_path.name
275
+ contrib_file = tmp_doc_path / 'CONTRIBUTING.md'
276
+ if contrib_file.exists():
277
+ final_path = cfg.config_path / 'CONTRIBUTING.md'
278
+ if final_path.exists(): final_path.unlink() # py37 doesn't have `missing_ok`
279
+ move(contrib_file, final_path)
280
+ assets_folder = tmp_doc_path / (Path(contrib_nb).stem + '_files') # Supporting files for CONTRIBUTING
281
+ if assets_folder.exists(): _copytree(assets_folder, cfg.config_path / assets_folder.name)
282
+
283
+ # %% ../nbs/api/14_quarto.ipynb
284
+ @call_parse
285
+ def nbdev_contributing(
286
+ path:str=None, # Path to notebooks
287
+ chk_time:bool=False # Only build if out-of-date
288
+ ):
289
+ """Create CONTRIBUTING.md from contributing_nb (defaults to 'contributing.ipynb' if present). Skips if the file doesn't exist."""
290
+ cfg = get_config()
291
+ path = Path(path) if path else cfg.nbs_path
292
+ contrib_nb_name = cfg.get('contributing_nb', 'contributing.ipynb')
293
+ contrib_nb_path = path / contrib_nb_name
294
+ if not contrib_nb_path.exists(): return
295
+ if chk_time and _doc_mtime_not_older(cfg.config_path / 'CONTRIBUTING.md' , contrib_nb_path): return
296
+
297
+ with _SidebarYmlRemoved(path): # to avoid rendering whole website
298
+ cache = proc_nbs(path)
299
+ _sprun(f'cd "{cache}" && quarto render "{cache/contrib_nb_name}" -o CONTRIBUTING.md -t gfm --no-execute')
300
+
301
+ _save_cached_contributing(cache, cfg, contrib_nb_name)
302
+
303
+ # %% ../nbs/api/14_quarto.ipynb
267
304
  @call_parse
268
305
  @delegates(_nbglob_docs)
269
306
  def nbdev_docs(
@@ -273,11 +310,12 @@ def nbdev_docs(
273
310
  "Create Quarto docs and README.md"
274
311
  cache,cfg,path = _pre_docs(path, n_workers=n_workers, **kwargs)
275
312
  nbdev_readme.__wrapped__(path=path, chk_time=True)
313
+ nbdev_contributing.__wrapped__(path=path, chk_time=True)
276
314
  _sprun(f'cd "{cache}" && quarto render --no-cache')
277
315
  shutil.rmtree(cfg.doc_path, ignore_errors=True)
278
316
  move(cache/cfg.doc_path.name, cfg.config_path)
279
317
 
280
- # %% ../nbs/api/14_quarto.ipynb 31
318
+ # %% ../nbs/api/14_quarto.ipynb
281
319
  @call_parse
282
320
  def prepare():
283
321
  "Export, test, and clean notebooks, and render README if needed"
@@ -287,8 +325,9 @@ def prepare():
287
325
  nbdev.clean.nbdev_clean.__wrapped__()
288
326
  refresh_quarto_yml()
289
327
  nbdev_readme.__wrapped__(chk_time=True)
328
+ nbdev_contributing.__wrapped__(chk_time=True)
290
329
 
291
- # %% ../nbs/api/14_quarto.ipynb 33
330
+ # %% ../nbs/api/14_quarto.ipynb
292
331
  @contextmanager
293
332
  def fs_watchdog(func, path, recursive:bool=True):
294
333
  "File system watchdog dispatching to `func`"
@@ -304,7 +343,7 @@ def fs_watchdog(func, path, recursive:bool=True):
304
343
  observer.stop()
305
344
  observer.join()
306
345
 
307
- # %% ../nbs/api/14_quarto.ipynb 34
346
+ # %% ../nbs/api/14_quarto.ipynb
308
347
  @call_parse
309
348
  @delegates(_nbglob_docs)
310
349
  def nbdev_preview(