Skip to content

Commit 2db4d63

Browse files
authored
Merge pull request #85 from Distributive-Network/Xmader/feat/atob-btoa
Implement `atob`/`btoa` APIs
2 parents c3b6ff2 + 71d029c commit 2db4d63

File tree

6 files changed

+140
-10
lines changed

6 files changed

+140
-10
lines changed

python/pythonmonkey/__init__.py

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,5 +6,6 @@
66
import importlib.metadata
77
__version__= importlib.metadata.version(__name__)
88

9-
# Load the module by default to make `console` globally available
9+
# Load the module by default to make `console`/`atob`/`btoa` globally available
1010
require("console")
11+
require("base64")
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
/**
2+
* @file base64.d.ts
3+
* @brief TypeScript type declarations for base64.py
4+
* @author Tom Tang <[email protected]>
5+
* @date July 2023
6+
*/
7+
8+
/**
9+
* Decode base64 string
10+
* @param b64 A string containing base64-encoded data.
11+
* @see https://html.spec.whatwg.org/multipage/webappapis.html#dom-atob-dev
12+
*/
13+
export declare function atob(b64: string): string;
14+
15+
/**
16+
* Create a base64-encoded ASCII string from a binary string
17+
* @param data The binary string to encode.
18+
* @see https://html.spec.whatwg.org/multipage/webappapis.html#dom-btoa-dev
19+
*/
20+
export declare function btoa(data: string): string;
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
# @file base64.py
2+
# @author Tom Tang <[email protected]>, Hamada Gasmallah <[email protected]>
3+
# @date July 2023
4+
5+
import base64
6+
7+
atob = lambda b64: str(base64.standard_b64decode(b64), 'latin1')
8+
btoa = lambda data: str(base64.standard_b64encode(bytes(data, 'latin1')), 'latin1')
9+
10+
# Make `atob`/`btoa` globally available
11+
import pythonmonkey as pm
12+
pm.eval(r"""(atob, btoa) => {
13+
if (!globalThis.atob) {
14+
globalThis.atob = atob;
15+
}
16+
if (!globalThis.btoa) {
17+
globalThis.btoa = btoa;
18+
}
19+
}""")(atob, btoa)
20+
21+
# Module exports
22+
exports['atob'] = atob # type: ignore
23+
exports['btoa'] = btoa # type: ignore

python/pythonmonkey/builtin_modules/console.js

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
const { customInspectSymbol, format } = require("util");
77

88
/** @typedef {(str: string) => void} WriteFn */
9+
/** @typedef {{ write: WriteFn }} IOWriter */
910

1011
/**
1112
* @see https://developer.mozilla.org/en-US/docs/Web/API/Console_API
@@ -21,14 +22,20 @@ class Console {
2122

2223
/**
2324
* Console constructor, form 1
24-
* @param {object} stdout - object with write method
25-
* @param {object} stderr - object with write method
26-
* @param {boolean} ignoreErrors - currently unused in PythonMonkey
25+
* @overload
26+
* @param {IOWriter} stdout - object with write method
27+
* @param {IOWriter} stderr - object with write method
28+
* @param {boolean=} ignoreErrors - currently unused in PythonMonkey
2729
* @see https://nodejs.org/api/console.html#new-consolestdout-stderr-ignoreerrors
2830
*/
2931
/**
3032
* Console constructor, form 2
31-
* @param {object} options - options object
33+
* @overload
34+
* @param {ConsoleConstructorOptions} options - options object
35+
* @typedef {object} ConsoleConstructorOptions
36+
* @property {IOWriter} stdout - object with write method
37+
* @property {IOWriter} stderr - object with write method
38+
* @property {boolean=} ignoreErrors - currently unused in PythonMonkey
3239
*/
3340
constructor(stdout, stderr, ignoreErrors)
3441
{

python/pythonmonkey/global.d.ts

Lines changed: 26 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -11,12 +11,28 @@ declare const python: {
1111
};
1212
/** Python `print` */
1313
print(...values: any): void;
14-
/** Python `sys.stdout.write`. Write the given string to stdout. */
15-
stdout_write(s: string): void;
16-
/** Python `sys.stderr.write`. Write the given string to stderr. */
17-
stderr_write(s: string): void;
14+
/** Python `eval` */
15+
eval(code: string, globals?: Record<string, any>, locals?: Record<string, any>): any;
16+
/** Python `exec` */
17+
exec(code: string, globals?: Record<string, any>, locals?: Record<string, any>): void;
18+
/** Python `sys.stdout`. */
19+
stdout: {
20+
/** Write the given string to stdout. */
21+
write(s: string): number;
22+
read(n: number): string;
23+
};
24+
/** Python `sys.stderr`. */
25+
stderr: {
26+
/** Write the given string to stderr. */
27+
write(s: string): number;
28+
read(n: number): string;
29+
};
1830
/** Python `os.getenv`. Get an environment variable, return undefined if it doesn't exist. */
1931
getenv(key: string): string | undefined;
32+
/** Python `exit`. Exit the program. */
33+
exit(exitCode: number): never;
34+
/** Loads a python module using importlib, prefills it with an exports object and returns the module. */
35+
load(filename: string): object;
2036
/** Python `sys.path` */
2137
paths: string[];
2238
};
@@ -25,7 +41,12 @@ declare const python: {
2541
declare function pmEval(code: string): any;
2642

2743
// Expose our own `console` as a property of the global object
28-
declare const console: import("console").Console;
44+
// XXX: ↓↓↓ we must use "var" here
45+
declare var console: import("console").Console;
46+
47+
// Expose `atob`/`btoa` as properties of the global object
48+
declare var atob: typeof import("base64").atob;
49+
declare var btoa: typeof import("base64").btoa;
2950

3051
// Keep this in sync with both https://hg.mozilla.org/releases/mozilla-esr102/file/a03fde6/js/public/Promise.h#l331
3152
// and https://github.com/nodejs/node/blob/v20.2.0/deps/v8/include/v8-promise.h#L30

tests/js/test-atob-btoa.simple

Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
/**
2+
* @file test-atob-btoa.simple
3+
* Simple test which ensures atob/btoa works
4+
* @author Tom Tang <[email protected]>
5+
* @date July 2023
6+
*/
7+
8+
function expect(a) {
9+
return {
10+
toBe(b) {
11+
if (a !== b) throw new Error(`'${a}' does not equal to '${b}'`);
12+
}
13+
}
14+
}
15+
16+
/*!
17+
* Modified from https://github.com/oven-sh/bun/blob/bun-v0.6.12/test/js/web/util/atob.test.js
18+
* Bun
19+
* MIT License
20+
*/
21+
22+
//
23+
// atob
24+
//
25+
expect(atob("YQ==")).toBe("a");
26+
expect(atob("YWI=")).toBe("ab");
27+
expect(atob("YWJj")).toBe("abc");
28+
expect(atob("YWJjZA==")).toBe("abcd");
29+
expect(atob("YWJjZGU=")).toBe("abcde");
30+
expect(atob("YWJjZGVm")).toBe("abcdef");
31+
expect(atob("zzzz")).toBe("Ï<ó");
32+
expect(atob("")).toBe("");
33+
expect(atob("null")).toBe("žée");
34+
expect(atob("6ek=")).toBe("éé");
35+
// expect(atob("6ek")).toBe("éé"); // FIXME (Tom Tang): binascii.Error: Incorrect padding
36+
expect(atob("gIE=")).toBe("€");
37+
// expect(atob("zz")).toBe("Ï"); // FIXME (Tom Tang): same here
38+
// expect(atob("zzz")).toBe("Ï<");
39+
expect(atob("zzz=")).toBe("Ï<");
40+
expect(atob(" YQ==")).toBe("a");
41+
expect(atob("YQ==\u000a")).toBe("a");
42+
43+
//
44+
// btoa
45+
//
46+
expect(btoa("a")).toBe("YQ==");
47+
expect(btoa("ab")).toBe("YWI=");
48+
expect(btoa("abc")).toBe("YWJj");
49+
expect(btoa("abcd")).toBe("YWJjZA==");
50+
expect(btoa("abcde")).toBe("YWJjZGU=");
51+
expect(btoa("abcdef")).toBe("YWJjZGVm");
52+
expect(typeof btoa).toBe("function");
53+
expect(btoa("")).toBe("");
54+
expect(btoa("null")).toBe("bnVsbA==");
55+
expect(btoa("undefined")).toBe("dW5kZWZpbmVk");
56+
expect(btoa("[object Window]")).toBe("W29iamVjdCBXaW5kb3dd");
57+
expect(btoa("éé")).toBe("6ek=");
58+
expect(btoa("\u0080\u0081")).toBe("gIE=");

0 commit comments

Comments
 (0)