Skip to content

Commit 967b264

Browse files
committed
Pyodide unzip/untar abilities + MicroPython unzip
1 parent 71abc15 commit 967b264

17 files changed

+174
-12
lines changed

docs/index.js

+2-2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/index.js.map

+1-1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/zip-CVv62MiA.js

+2
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

docs/zip-CVv62MiA.js.map

+1
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

esm/interpreter/_python.js

+9
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,15 @@ export const registerJSModule = (interpreter, name, value) => {
77
interpreter.registerJsModule(name, value);
88
};
99

10+
export const getFormat = (path, url) => {
11+
if (path.endsWith('/*')) {
12+
if (/\.(zip|tar(?:\.gz)?)$/.test(url))
13+
return RegExp.$1;
14+
throw new Error(`Unsupported archive ${url}`);
15+
}
16+
return '';
17+
};
18+
1019
export const run = (interpreter, code, ...args) => {
1120
try {
1221
return interpreter.runPython(dedent(code), ...args);

esm/interpreter/_utils.js

+9-2
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,9 @@ export const RUNNING_IN_WORKER = !globalThis.window;
1111
// This should be the only helper needed for all Emscripten based FS exports
1212
export const writeFile = ({ FS, PATH, PATH_FS }, path, buffer) => {
1313
const absPath = PATH_FS.resolve(path);
14-
FS.mkdirTree(PATH.dirname(absPath));
14+
const dirPath = PATH.dirname(absPath);
15+
if (FS.mkdirTree) FS.mkdirTree(dirPath);
16+
else mkdirTree(FS, dirPath);
1517
return FS.writeFile(absPath, new Uint8Array(buffer), {
1618
canOwn: true,
1719
});
@@ -141,7 +143,12 @@ export const fetchFiles = (module, interpreter, config_files) =>
141143
all(
142144
calculateFilesPaths(config_files).map(({ url, path }) =>
143145
fetchBuffer(config_files, url)
144-
.then((buffer) => module.writeFile(interpreter, path, buffer)),
146+
.then((buffer) => module.writeFile(
147+
interpreter,
148+
path,
149+
buffer,
150+
url,
151+
)),
145152
),
146153
);
147154

esm/interpreter/micropython.js

+56-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,9 @@
1+
// import fetch from '@webreflection/fetch';
12
import { fetchFiles, fetchJSModules, fetchPaths, writeFile } from './_utils.js';
2-
import { registerJSModule, run, runAsync, runEvent } from './_python.js';
3+
import { getFormat, registerJSModule, run, runAsync, runEvent } from './_python.js';
34
import { stdio } from './_io.js';
45
import mip from '../python/mip.js';
6+
import zip from '../zip.js';
57

68
const type = 'micropython';
79

@@ -33,7 +35,58 @@ export default {
3335
runAsync,
3436
runEvent,
3537
transform: (interpreter, value) => interpreter.PyProxy.toJs(value),
36-
writeFile: ({ FS, _module: { PATH, PATH_FS } }, path, buffer) =>
37-
writeFile({ FS, PATH, PATH_FS }, path, buffer),
38+
writeFile: (interpreter, path, buffer, url) => {
39+
const { FS, _module: { PATH, PATH_FS } } = interpreter;
40+
const fs = { FS, PATH, PATH_FS };
41+
const format = getFormat(path, url);
42+
if (format) {
43+
const extractDir = path.slice(0, -1);
44+
if (extractDir !== './') FS.mkdir(extractDir);
45+
switch (format) {
46+
case 'zip': {
47+
const blob = new Blob([buffer], { type: 'application/zip' });
48+
return zip().then(async ({ BlobReader, Uint8ArrayWriter, ZipReader }) => {
49+
const zipFileReader = new BlobReader(blob);
50+
const zipReader = new ZipReader(zipFileReader);
51+
for (const entry of await zipReader.getEntries()) {
52+
const { directory, filename } = entry;
53+
if (directory) {
54+
FS.mkdir(extractDir + filename);
55+
}
56+
else {
57+
const buffer = await entry.getData(new Uint8ArrayWriter);
58+
FS.writeFile(extractDir + filename, buffer, {
59+
canOwn: true,
60+
});
61+
}
62+
}
63+
zipReader.close();
64+
});
65+
}
66+
case 'tar.gz': {
67+
const TMP = './_.tar.gz';
68+
writeFile(fs, TMP, buffer);
69+
interpreter.runPython(`
70+
import os, gzip, tarfile
71+
tar = tarfile.TarFile(fileobj=gzip.GzipFile(fileobj=open("${TMP}", "rb")))
72+
for f in tar:
73+
name = f"${extractDir}{f.name[2:]}"
74+
if f.type == tarfile.DIRTYPE:
75+
if f.name != "./":
76+
os.mkdir(name.strip("/"))
77+
else:
78+
source = tar.extractfile(f)
79+
with open(name, "wb") as dest:
80+
dest.write(source.read())
81+
dest.close()
82+
tar.close()
83+
os.remove("${TMP}")
84+
`);
85+
return;
86+
}
87+
}
88+
}
89+
return writeFile(fs, path, buffer);
90+
},
3891
};
3992
/* c8 ignore stop */

esm/interpreter/pyodide.js

+11-3
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
import { create } from 'gc-hook';
22

33
import { RUNNING_IN_WORKER, fetchFiles, fetchJSModules, fetchPaths, writeFile } from './_utils.js';
4-
import { registerJSModule, run, runAsync, runEvent } from './_python.js';
4+
import { getFormat, registerJSModule, run, runAsync, runEvent } from './_python.js';
55
import { stdio } from './_io.js';
66

77
const type = 'pyodide';
@@ -109,7 +109,15 @@ export default {
109109
value.toJs(toJsOptions) :
110110
value
111111
),
112-
writeFile: ({ FS, PATH, _module: { PATH_FS } }, path, buffer) =>
113-
writeFile({ FS, PATH, PATH_FS }, path, buffer),
112+
writeFile: (interpreter, path, buffer, url) => {
113+
const format = getFormat(path, url);
114+
if (format) {
115+
return interpreter.unpackArchive(buffer, format, {
116+
extractDir: path.slice(0, -1)
117+
});
118+
}
119+
const { FS, PATH, _module: { PATH_FS } } = interpreter;
120+
return writeFile({ FS, PATH, PATH_FS }, path, buffer);
121+
},
114122
};
115123
/* c8 ignore stop */

esm/zip.js

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
/* c8 ignore start */
2+
export default () => import(/* webpackIgnore: true */'./3rd-party/zip.js');
3+
/* c8 ignore stop */

package.json

+1-1
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,6 @@
8888
"to-json-callback": "^0.1.1"
8989
},
9090
"worker": {
91-
"blob": "sha256-tV7jypF54M8P2vuzREc5ZRk1nPGj9AL1VmnxnE2az/s="
91+
"blob": "sha256-jf8fAU4aXz2BNme/XOUMyIliOinwW2o+moYgdacP8qs="
9292
}
9393
}

test/zip/index.html

+13
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
</head>
7+
<body>
8+
<ul>
9+
<li><a href="pyodide.html">pyodide</a></li>
10+
<li><a href="micropython.html">micropython</a></li>
11+
</ul>
12+
</body>
13+
</html>

test/zip/index.tar.gz

262 Bytes
Binary file not shown.

test/zip/index.zip

338 Bytes
Binary file not shown.

test/zip/micropython.html

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<script type="module" src="/dist/index.js"></script>
7+
<style>
8+
html.true::before {
9+
content: "OK"
10+
}
11+
</style>
12+
</head>
13+
<body>
14+
<script type="micropython" config="zip.toml" worker>
15+
from polyscript import xworker
16+
f = open("./package/index.html", "r")
17+
lines = f.readlines()
18+
xworker.window.document.documentElement.classList.add(
19+
lines[0] == "<!DOCTYPE html>\n"
20+
)
21+
</script>
22+
<script type="micropython" config="tar.gz.toml" worker>
23+
from polyscript import xworker
24+
f = open("./package/index.html", "r")
25+
lines = f.readlines()
26+
xworker.window.document.documentElement.classList.add(
27+
lines[0] == "<!DOCTYPE html>\n"
28+
)
29+
</script>
30+
</body>
31+
</html>

test/zip/pyodide.html

+31
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!DOCTYPE html>
2+
<html lang="en">
3+
<head>
4+
<meta charset="UTF-8">
5+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
6+
<script type="module" src="/dist/index.js"></script>
7+
<style>
8+
html.true::before {
9+
content: "OK"
10+
}
11+
</style>
12+
</head>
13+
<body>
14+
<script type="pyodide" config="zip.toml" worker>
15+
from polyscript import xworker
16+
f = open("./package/index.html", "r")
17+
lines = f.readlines()
18+
xworker.window.document.documentElement.classList.add(
19+
lines[0] == "<!DOCTYPE html>\n"
20+
)
21+
</script>
22+
<script type="pyodide" config="tar.gz.toml" worker>
23+
from polyscript import xworker
24+
f = open("./package/index.html", "r")
25+
lines = f.readlines()
26+
xworker.window.document.documentElement.classList.add(
27+
lines[0] == "<!DOCTYPE html>\n"
28+
)
29+
</script>
30+
</body>
31+
</html>

test/zip/tar.gz.toml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[files]
2+
"./index.tar.gz" = "./package/*"

test/zip/zip.toml

+2
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
[files]
2+
"./index.zip" = "./package/*"

0 commit comments

Comments
 (0)