-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathcode-runners.ts
116 lines (106 loc) · 3.3 KB
/
code-runners.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
import { CodeRunnerOutput, defineCodeRunnersSetup } from '@slidev/types'
import { loadPyodide, PyodideInterface } from 'pyodide'
import { ref } from 'vue'
import { useNav } from '@slidev/client'
let pyodideCache: PyodideInterface | null = null
let pyodideOptionCache = "{}"
async function setupPyodide(options = {}, code) {
const {
installs = [],
prelude = "",
loadPackagesFromImports = true,
suppressDeprecationWarnings = true,
alwaysReload = false,
loadOptions = {},
} = options as any
if (alwaysReload || pyodideOptionCache !== JSON.stringify(options)) {
pyodideCache = null
pyodideOptionCache = JSON.stringify(options)
}
if (pyodideCache) {
if (loadPackagesFromImports) {
await pyodideCache.loadPackagesFromImports(code)
}
return pyodideCache
}
pyodideCache = await loadPyodide(loadOptions);
if (prelude) {
if (loadPackagesFromImports) {
await pyodideCache.loadPackagesFromImports(prelude)
}
await pyodideCache.runPythonAsync(prelude)
}
// Pandas always throws a DeprecationWarning
if (suppressDeprecationWarnings)
await pyodideCache.runPythonAsync(`
import warnings
warnings.filterwarnings("ignore", category=DeprecationWarning)
`)
if (installs.length) {
await pyodideCache.loadPackage('micropip')
await pyodideCache.runPythonAsync([
'import micropip',
'await micropip.install(' + JSON.stringify(installs) + ')',
].join('\n'))
}
if (loadPackagesFromImports) {
await pyodideCache.loadPackagesFromImports(code)
}
return pyodideCache
}
export default defineCodeRunnersSetup(() => {
const { slides } = useNav()
async function run(code: string) {
// @ts-expect-error
const pyodide = await setupPyodide(slides.value[0].meta.slide.frontmatter?.python, code)
const texts = ref([''])
const extras = ref<CodeRunnerOutput[]>([])
const decoder = new TextDecoder('utf-8');
function write(buffer: Uint8Array) {
const text = decoder.decode(buffer)
for (const line of text.split('\n')) {
texts.value[texts.value.length - 1] += line
texts.value.push('')
}
return buffer.length
}
pyodide.setStdout({
write: write,
isatty: true,
})
pyodide.setStderr({
write: write,
isatty: true,
})
pyodide.runPythonAsync(code).catch(err => {
console.error(err)
const str = err.toString()
const matchNotFoundError = str.match(/ModuleNotFoundError: No module named '(.*)'/)
if (matchNotFoundError) {
extras.value.push({
html: [
`<div class="text-red">${matchNotFoundError[0]}</div>`,
`<div class="text-blue">Tip: This may because of this package is not a <a href="https://pyodide.org/en/stable/usage/packages-in-pyodide.html">Pyodide builtin package</a>.`,
"<br>You may need to install it by adding the package name to the `python.installs` array in your headmatter.",
`</div>`
].join('')
})
} else {
for (const line of str.split('\n')) {
extras.value.push({
text: line,
class: 'text-red'
})
}
}
});
return () => [
...texts.value.map(text => ({ text, highlightLang: 'ansi' })),
...extras.value,
]
}
return {
python: run,
py: run,
}
})