Book a Demo!
CoCalc Logo Icon
StoreFeaturesDocsShareSupportNewsAboutPoliciesSign UpSign In
quantum-kittens
GitHub Repository: quantum-kittens/platypus
Path: blob/main/scripts/content_checks/nb_vale.py
3855 views
1
"""This script runs 'prose linting' checks on the notebooks.
2
This includes spell checking, and writing best practices.
3
Requires Vale: https://vale.sh/docs/vale-cli/installation/
4
"""
5
import sys
6
import os
7
import shutil
8
import nbformat
9
import subprocess
10
import json
11
from pathlib import Path
12
from tools import parse_args, style, indent
13
14
15
NB_PATHS = './scripts/content_checks/notebook_paths.txt'
16
TEMP_DIR = './scripts/temp/md'
17
STYLE_DIR = './scripts/content_checks/style'
18
19
20
def lint_notebook(filepath, CI=False):
21
"""Perform Vale prose linting checks on
22
notebook at `filepath`. If `CI` then exit
23
early with code 1 on lint error."""
24
print(style('bold', filepath))
25
outdir = (
26
Path(TEMP_DIR)
27
/ Path(filepath).parent.stem
28
/ Path(filepath).stem
29
)
30
extract_markdown(filepath, outdir)
31
lint_markdown(outdir, CI)
32
33
34
def extract_markdown(filepath, outdir):
35
"""Extracts the markdown from the notebook
36
at `filepath` and saves each cell as a separate
37
file in the temp folder for Vale to lint."""
38
nb = nbformat.read(filepath, as_version=4)
39
if os.path.exists(outdir):
40
shutil.rmtree(outdir)
41
Path(outdir).mkdir(parents=True)
42
for idx, cell in enumerate(nb.cells):
43
if cell.cell_type == 'markdown':
44
# outpath e.g.: ./scripts/temp/intro/grover-intro/0002.md
45
outpath = Path(outdir) / (str(idx).rjust(4, '0') + '.md')
46
with open(outpath, 'w+') as f:
47
f.write(cell.source)
48
49
50
def lint_markdown(md_dir, CI=False):
51
"""Lints the markdown files in `md_dir`
52
using Vale linter. If `CI`, then will exit with
53
code 1 on any Vale error."""
54
55
# Run Vale on folder
56
files = os.listdir(md_dir)
57
path = Path(md_dir)
58
vale_result = subprocess.run(
59
['vale', path, '--output', 'JSON'],
60
capture_output=True)
61
vale_output = json.loads(vale_result.stdout)
62
63
# Parse output and print nicely
64
fail = False
65
notebook_msg = ''
66
for file, suggestions in vale_output.items():
67
notebook_msg += f"cell {int(Path(file).stem)}\n"
68
cell_msg = ''
69
for s in suggestions:
70
severity = s['Severity']
71
if severity == 'error':
72
fail = True
73
cell_msg += style(severity, severity.capitalize())
74
cell_msg += f": {s['Message']}\n"
75
if s['Match'] != '':
76
cell_msg += style('faint', f'"…{s["Match"]}…" ')
77
cell_msg += style('faint',
78
f"@l{s['Line']};c{s['Span'][0]} ({s['Check']})")
79
cell_msg += '\n'
80
notebook_msg += indent(cell_msg) + '\n'
81
if not CI and (notebook_msg!=''):
82
print(indent(notebook_msg))
83
if fail and CI:
84
print(indent(notebook_msg))
85
print(style('error', 'Prose linting error encountered; test failed.'))
86
sys.exit(1)
87
88
89
if __name__ == '__main__':
90
# usage: python3 nb_vale.py --CI notebook1.ipynb path/to/notebook2.ipynb
91
switches, filepaths = parse_args(sys.argv)
92
93
CI = '--CI' in switches
94
95
for path in filepaths:
96
lint_notebook(path, CI)
97
98