pll-python 0.1.2 → 0.2.0
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.
- package/cli.cjs +89 -23
- package/package.json +1 -1
- package/worker.cjs +24 -781
package/cli.cjs
CHANGED
|
@@ -252,7 +252,17 @@ function userTracebackFrames(traceback) {
|
|
|
252
252
|
}
|
|
253
253
|
return frames;
|
|
254
254
|
}
|
|
255
|
-
function
|
|
255
|
+
function sourceIndent(source, lineNumber) {
|
|
256
|
+
if (!source) {
|
|
257
|
+
return 0;
|
|
258
|
+
}
|
|
259
|
+
const line = source.split(/\r?\n/)[lineNumber - 1];
|
|
260
|
+
if (line === void 0) {
|
|
261
|
+
return 0;
|
|
262
|
+
}
|
|
263
|
+
return line.length - line.trimStart().length;
|
|
264
|
+
}
|
|
265
|
+
function parsePythonError(rawTraceback, source) {
|
|
256
266
|
const traceback = rawTraceback.trimEnd();
|
|
257
267
|
const lines = traceback.split(/\r?\n/);
|
|
258
268
|
let errorType = "Error";
|
|
@@ -286,10 +296,11 @@ function parsePythonError(rawTraceback) {
|
|
|
286
296
|
if (lineNumber !== null) {
|
|
287
297
|
for (let i = 0; i < lines.length - 1; i++) {
|
|
288
298
|
if (/^\s*\^+\s*$/.test(lines[i + 1] ?? "")) {
|
|
289
|
-
const
|
|
290
|
-
const caretIdx =
|
|
299
|
+
const sourceLine = lines[i];
|
|
300
|
+
const caretIdx = lines[i + 1].indexOf("^");
|
|
291
301
|
if (caretIdx >= 0) {
|
|
292
|
-
|
|
302
|
+
const echoed = sourceLine.length - sourceLine.trimStart().length;
|
|
303
|
+
column = Math.max(0, caretIdx - echoed) + sourceIndent(source, lineNumber);
|
|
293
304
|
}
|
|
294
305
|
}
|
|
295
306
|
}
|
|
@@ -689,11 +700,11 @@ function findRuntimeFinding(source, fileName, level, parsedError) {
|
|
|
689
700
|
// src/common/analyzers/runtimeFinding.ts
|
|
690
701
|
function findingForErrorEvent(event, source, fileName, level) {
|
|
691
702
|
const traceback = event.traceback || `${event.errorType}: ${event.message}`;
|
|
692
|
-
const parsed = parsePythonError(traceback);
|
|
693
|
-
if (parsed.lineNumber === null && event.lineNumber
|
|
703
|
+
const parsed = parsePythonError(traceback, source);
|
|
704
|
+
if (parsed.lineNumber === null && event.lineNumber != null) {
|
|
694
705
|
parsed.lineNumber = event.lineNumber;
|
|
695
706
|
}
|
|
696
|
-
if (parsed.column === null && event.column
|
|
707
|
+
if (parsed.column === null && event.column != null) {
|
|
697
708
|
parsed.column = event.column;
|
|
698
709
|
}
|
|
699
710
|
if (parsed.fileName === null && event.fileName) {
|
|
@@ -704,6 +715,11 @@ function findingForErrorEvent(event, source, fileName, level) {
|
|
|
704
715
|
|
|
705
716
|
// src/common/pyodideRunner.ts
|
|
706
717
|
var ANY_IMPORT_RE = /(^|\n)[ \t]*(import|from)[ \t]+\S/;
|
|
718
|
+
var PANDAS_METHOD_RE = /\.\s*to_pandas\s*\(/;
|
|
719
|
+
var NEEDS_PACKAGES_RE_LIST = [PANDAS_METHOD_RE];
|
|
720
|
+
function needsPackages(code) {
|
|
721
|
+
return ANY_IMPORT_RE.test(code) || NEEDS_PACKAGES_RE_LIST.some((re) => re.test(code));
|
|
722
|
+
}
|
|
707
723
|
|
|
708
724
|
// src/cli/files.ts
|
|
709
725
|
var fs = __toESM(require("node:fs/promises"));
|
|
@@ -721,8 +737,17 @@ var MOUNT_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
|
721
737
|
".md",
|
|
722
738
|
".dat",
|
|
723
739
|
".xml",
|
|
724
|
-
".py"
|
|
740
|
+
".py",
|
|
741
|
+
// Pictures, for `load_image`. `.svg` is not in BINARY_EXTENSIONS below
|
|
742
|
+
// because it is text, and mounting it as text is lossless.
|
|
743
|
+
".svg",
|
|
744
|
+
".png",
|
|
745
|
+
".jpg",
|
|
746
|
+
".jpeg",
|
|
747
|
+
".gif",
|
|
748
|
+
".webp"
|
|
725
749
|
]);
|
|
750
|
+
var BINARY_EXTENSIONS = /* @__PURE__ */ new Set([".png", ".jpg", ".jpeg", ".gif", ".webp"]);
|
|
726
751
|
var WRITEBACK_EXTENSIONS = /* @__PURE__ */ new Set([
|
|
727
752
|
".csv",
|
|
728
753
|
".txt",
|
|
@@ -757,9 +782,15 @@ function isMountableName(name) {
|
|
|
757
782
|
function isWritebackName(name) {
|
|
758
783
|
return isSafeBasename(name) && WRITEBACK_EXTENSIONS.has(extensionOf(name));
|
|
759
784
|
}
|
|
785
|
+
function isBinaryMountName(name) {
|
|
786
|
+
return BINARY_EXTENSIONS.has(extensionOf(name));
|
|
787
|
+
}
|
|
760
788
|
function utf8ByteLength(text) {
|
|
761
789
|
return new TextEncoder().encode(text).byteLength;
|
|
762
790
|
}
|
|
791
|
+
function contentsByteLength(contents) {
|
|
792
|
+
return typeof contents === "string" ? utf8ByteLength(contents) : contents.byteLength;
|
|
793
|
+
}
|
|
763
794
|
function decodeMountContents(data) {
|
|
764
795
|
if (data.byteLength > MAX_FILE_BYTES) {
|
|
765
796
|
return void 0;
|
|
@@ -781,27 +812,33 @@ function selectMountableFiles(candidates) {
|
|
|
781
812
|
if (!isMountableName(candidate.name)) {
|
|
782
813
|
continue;
|
|
783
814
|
}
|
|
784
|
-
let
|
|
785
|
-
if (
|
|
815
|
+
let contents;
|
|
816
|
+
if (isBinaryMountName(candidate.name)) {
|
|
817
|
+
const raw = typeof candidate.contents === "string" ? new TextEncoder().encode(candidate.contents) : candidate.contents;
|
|
818
|
+
if (raw.byteLength > MAX_FILE_BYTES) {
|
|
819
|
+
continue;
|
|
820
|
+
}
|
|
821
|
+
contents = raw;
|
|
822
|
+
} else if (typeof candidate.contents === "string") {
|
|
786
823
|
if (utf8ByteLength(candidate.contents) > MAX_FILE_BYTES) {
|
|
787
824
|
continue;
|
|
788
825
|
}
|
|
789
826
|
if (candidate.contents.includes("\0")) {
|
|
790
827
|
continue;
|
|
791
828
|
}
|
|
792
|
-
|
|
829
|
+
contents = candidate.contents;
|
|
793
830
|
} else {
|
|
794
831
|
const decoded = decodeMountContents(candidate.contents);
|
|
795
832
|
if (decoded === void 0) {
|
|
796
833
|
continue;
|
|
797
834
|
}
|
|
798
|
-
|
|
835
|
+
contents = decoded;
|
|
799
836
|
}
|
|
800
|
-
const size =
|
|
837
|
+
const size = contentsByteLength(contents);
|
|
801
838
|
if (total + size > MAX_TOTAL_BYTES) {
|
|
802
839
|
continue;
|
|
803
840
|
}
|
|
804
|
-
selected.push({ name: candidate.name, contents
|
|
841
|
+
selected.push({ name: candidate.name, contents });
|
|
805
842
|
total += size;
|
|
806
843
|
}
|
|
807
844
|
return selected;
|
|
@@ -816,6 +853,9 @@ function selectWritebackFiles(files) {
|
|
|
816
853
|
if (!isWritebackName(file.name)) {
|
|
817
854
|
continue;
|
|
818
855
|
}
|
|
856
|
+
if (typeof file.contents !== "string") {
|
|
857
|
+
continue;
|
|
858
|
+
}
|
|
819
859
|
const size = utf8ByteLength(file.contents);
|
|
820
860
|
if (size > MAX_FILE_BYTES || total + size > MAX_TOTAL_BYTES) {
|
|
821
861
|
continue;
|
|
@@ -855,6 +895,9 @@ async function writeBackSiblingFiles(scriptPath, files) {
|
|
|
855
895
|
const written = [];
|
|
856
896
|
for (const file of selectWritebackFiles(files)) {
|
|
857
897
|
try {
|
|
898
|
+
if (typeof file.contents !== "string") {
|
|
899
|
+
continue;
|
|
900
|
+
}
|
|
858
901
|
await fs.writeFile(path.join(folder, file.name), file.contents, "utf8");
|
|
859
902
|
written.push(file.name);
|
|
860
903
|
} catch {
|
|
@@ -934,7 +977,7 @@ async function staticChecks(runtime, view, source, fileName, level) {
|
|
|
934
977
|
return true;
|
|
935
978
|
}
|
|
936
979
|
async function ensurePackages(runtime, view, source) {
|
|
937
|
-
if (!
|
|
980
|
+
if (!needsPackages(source)) {
|
|
938
981
|
return;
|
|
939
982
|
}
|
|
940
983
|
try {
|
|
@@ -1302,18 +1345,33 @@ function asPlain(value) {
|
|
|
1302
1345
|
}
|
|
1303
1346
|
return value;
|
|
1304
1347
|
}
|
|
1305
|
-
function
|
|
1348
|
+
function friendlyTypeCheckMessage(message, level) {
|
|
1349
|
+
const marker = message.indexOf("TypeCheckError:");
|
|
1350
|
+
if (marker < 0) {
|
|
1351
|
+
return message;
|
|
1352
|
+
}
|
|
1353
|
+
const detail = message.slice(marker + "TypeCheckError:".length).trim();
|
|
1354
|
+
if (!detail) {
|
|
1355
|
+
return message;
|
|
1356
|
+
}
|
|
1357
|
+
const explanation = explainTypeCheckError(detail, null, level);
|
|
1358
|
+
return [explanation.headline, ...explanation.howToFix.map((line) => `- ${line}`)].join(
|
|
1359
|
+
"\n"
|
|
1360
|
+
);
|
|
1361
|
+
}
|
|
1362
|
+
function adaptTestCase(raw, level) {
|
|
1306
1363
|
const row = asPlain(raw) ?? {};
|
|
1307
1364
|
const line = row.line_number;
|
|
1365
|
+
const message = row.message == null ? null : String(row.message);
|
|
1308
1366
|
return {
|
|
1309
1367
|
name: String(row.name ?? ""),
|
|
1310
1368
|
outcome: String(row.outcome ?? "failed"),
|
|
1311
1369
|
lineNumber: typeof line === "number" ? line : null,
|
|
1312
|
-
message:
|
|
1370
|
+
message: message === null ? null : friendlyTypeCheckMessage(message, level),
|
|
1313
1371
|
stdout: row.stdout == null ? null : String(row.stdout)
|
|
1314
1372
|
};
|
|
1315
1373
|
}
|
|
1316
|
-
function deliverTestResult(result, onEvent, fileName) {
|
|
1374
|
+
function deliverTestResult(result, onEvent, fileName, level) {
|
|
1317
1375
|
if (result.internal_error && result.error_type) {
|
|
1318
1376
|
onEvent({
|
|
1319
1377
|
kind: "error",
|
|
@@ -1327,7 +1385,7 @@ function deliverTestResult(result, onEvent, fileName) {
|
|
|
1327
1385
|
onEvent({ kind: "done" });
|
|
1328
1386
|
return;
|
|
1329
1387
|
}
|
|
1330
|
-
const tests = Array.isArray(result.tests) ? result.tests.map(adaptTestCase) : [];
|
|
1388
|
+
const tests = Array.isArray(result.tests) ? result.tests.map((row) => adaptTestCase(row, level)) : [];
|
|
1331
1389
|
onEvent({
|
|
1332
1390
|
kind: "testReport",
|
|
1333
1391
|
fileName,
|
|
@@ -1470,7 +1528,7 @@ var WorkerPythonRuntime = class {
|
|
|
1470
1528
|
},
|
|
1471
1529
|
"testResult"
|
|
1472
1530
|
);
|
|
1473
|
-
deliverTestResult(result, onEvent, request.fileName);
|
|
1531
|
+
deliverTestResult(result, onEvent, request.fileName, request.level);
|
|
1474
1532
|
}
|
|
1475
1533
|
async staticAnalyze(request) {
|
|
1476
1534
|
await this.initialize();
|
|
@@ -1690,11 +1748,12 @@ function findingLocation(finding) {
|
|
|
1690
1748
|
if (finding.fileName === "<repl>" || finding.fileName === "<input>") {
|
|
1691
1749
|
return null;
|
|
1692
1750
|
}
|
|
1693
|
-
const
|
|
1751
|
+
const column = typeof finding.column === "number" && Number.isFinite(finding.column) ? finding.column : null;
|
|
1752
|
+
const label = column !== null ? `${finding.fileName}:${finding.lineNumber}:${column + 1}` : `${finding.fileName}:${finding.lineNumber}`;
|
|
1694
1753
|
return {
|
|
1695
1754
|
fileName: finding.fileName,
|
|
1696
1755
|
line: finding.lineNumber,
|
|
1697
|
-
column
|
|
1756
|
+
column,
|
|
1698
1757
|
label
|
|
1699
1758
|
};
|
|
1700
1759
|
}
|
|
@@ -1845,6 +1904,13 @@ var CliView = class {
|
|
|
1845
1904
|
this.problem(" " + line);
|
|
1846
1905
|
}
|
|
1847
1906
|
}
|
|
1907
|
+
const printed = (test.stdout ?? "").replace(/\s+$/, "");
|
|
1908
|
+
if (printed) {
|
|
1909
|
+
this.problem(this.dim(" output:"));
|
|
1910
|
+
for (const line of printed.split("\n")) {
|
|
1911
|
+
this.problem(this.dim(" " + line));
|
|
1912
|
+
}
|
|
1913
|
+
}
|
|
1848
1914
|
}
|
|
1849
1915
|
}
|
|
1850
1916
|
};
|
|
@@ -1965,7 +2031,7 @@ async function main(argv) {
|
|
|
1965
2031
|
return EXIT.ok;
|
|
1966
2032
|
}
|
|
1967
2033
|
if (args.version) {
|
|
1968
|
-
process.stdout.write("0.
|
|
2034
|
+
process.stdout.write("0.2.0\n");
|
|
1969
2035
|
return EXIT.ok;
|
|
1970
2036
|
}
|
|
1971
2037
|
if (args.error !== void 0) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pll-python",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "Run Python with PLL's language levels from the command line: level checks, in-file tests, friendly errors and the built-in image and table libraries, all on Pyodide. No Python install needed.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|