diff --git a/include/pyjs/pre_js/init.js b/include/pyjs/pre_js/init.js index a784dce..ae5330f 100644 --- a/include/pyjs/pre_js/init.js +++ b/include/pyjs/pre_js/init.js @@ -133,6 +133,8 @@ Module['init_phase_1'] = async function(prefix, python_version) { return ret['ret'] } }; + + } Module['init_phase_2'] = function(prefix, python_version) { @@ -160,6 +162,16 @@ except Exception as e: return Module._py_exec_eval.py_call(script, globals, locals) } + + + + Module['cli'] = function(args){ + let run_cmd = Module.exec_eval(`from pyjs.cli import run_cmd;run_cmd`) + const ret = run_cmd.py_call(args) + run_cmd.delete() + return ret + } + // ansync execute a script and return the value of the last expression Module._py_async_exec_eval = Module.eval("pyjs.async_exec_eval") Module._py_objects.push(Module._py_async_exec_eval) diff --git a/include/pyjs/pre_js/load_pkg.js b/include/pyjs/pre_js/load_pkg.js index 635f064..f4f9dc4 100644 --- a/include/pyjs/pre_js/load_pkg.js +++ b/include/pyjs/pre_js/load_pkg.js @@ -171,23 +171,57 @@ Module["bootstrap_from_empack_packed_environment"] = async function Module.init_phase_2(prefix, python_version); + const lib_path = prefix == "/" ? "/lib" : `${prefix}/lib`; + + function dir_name_from_path(path) { + return path.split("/").slice(0, -1).join("/"); + } + + function is_plain_shared(filename) { + // check if filename is in dir lib_path + return dir_name_from_path(filename) == lib_path || dir_name_from_path(filename) == "/"; + } + if(!skip_loading_shared_libs){ + + // FIRST PASS only contains libraries in /lib/ // instantiate all packages for (let i = 0; i < packages.length; i++) { // if we have any shared libraries, load them if (shared_libs[i].length > 0) { - for (let j = 0; j < shared_libs[i].length; j++) { - let sl = shared_libs[i][j]; + // filter shared to only contain shared libraries + let filtered_shared = shared_libs[i].filter(is_plain_shared); + console.log("loading shared libs (I)",filtered_shared) + if(filtered_shared.length > 0){ + await Module._loadDynlibsFromPackage( + prefix, + python_version, + packages[i].name, + true, + filtered_shared + ) + } + } + } + // SECOND PASS contains all other shared libraries + for (let i = 0; i < packages.length; i++) { + + // if we have any shared libraries, load them + if (shared_libs[i].length > 0) { + // filter shared to only contain shared libraries + let filtered_shared = shared_libs[i].filter(x => !is_plain_shared(x)); + if(filtered_shared.length > 0){ + console.log("loading shared libs (II)",filtered_shared) + await Module._loadDynlibsFromPackage( + prefix, + python_version, + packages[i].name, + false, + filtered_shared + ) } - await Module._loadDynlibsFromPackage( - prefix, - python_version, - packages[i].name, - false, - shared_libs[i] - ) } } } diff --git a/module/pyjs/cli.py b/module/pyjs/cli.py new file mode 100644 index 0000000..1ff299a --- /dev/null +++ b/module/pyjs/cli.py @@ -0,0 +1,104 @@ +# fake a python executable +import argparse +import traceback +from .convert import to_py +from pyjs_core import JsValue + +def run_cmd(args): + + + if isinstance(args, JsValue): + args = to_py(args) + + if not isinstance(args, list): + raise TypeError("args should be a list") + + parser = argparse.ArgumentParser( + description="A Python script with various command-line options.", + usage='python [option] ... [-c cmd | -m mod | file | -] [arg] ...', + add_help=False + ) + + # Options + parser.add_argument('-b', action='store_true', help='Issue warnings about converting bytes/bytearray to str and comparing bytes/bytearray with str or bytes with int. (-bb: issue errors)') + parser.add_argument('-B', action='store_true', help="Don't write .pyc files on import; also PYTHONDONTWRITEBYTECODE=x") + parser.add_argument('-c', metavar='cmd', help='Program passed in as string (terminates option list)') + parser.add_argument('-d', action='store_true', help='Turn on parser debugging output (for experts only, only works on debug builds); also PYTHONDEBUG=x') + parser.add_argument('-E', action='store_true', help='Ignore PYTHON* environment variables (such as PYTHONPATH)') + parser.add_argument('-h','--help', action='store_true', help='Print this help message and exit (also -? or --help)') + parser.add_argument('-i', action='store_true', help='Inspect interactively after running script; forces a prompt even if stdin does not appear to be a terminal; also PYTHONINSPECT=x') + parser.add_argument('-I', action='store_true', help="Isolate Python from the user's environment (implies -E and -s)") + parser.add_argument('-m', metavar='mod', help='Run library module as a script (terminates option list)') + parser.add_argument('-O', action='store_true', help='Remove assert and __debug__-dependent statements; add .opt-1 before .pyc extension; also PYTHONOPTIMIZE=x') + parser.add_argument('-OO', action='store_true', help='Do -O changes and also discard docstrings; add .opt-2 before .pyc extension') + parser.add_argument('-P', action='store_true', help="Don't prepend a potentially unsafe path to sys.path; also PYTHONSAFEPATH") + parser.add_argument('-q', action='store_true', help="Don't print version and copyright messages on interactive startup") + parser.add_argument('-s', action='store_true', help="Don't add user site directory to sys.path; also PYTHONNOUSERSITE=x") + parser.add_argument('-S', action='store_true', help="Don't imply 'import site' on initialization") + parser.add_argument('-u', action='store_true', help='Force the stdout and stderr streams to be unbuffered; this option has no effect on stdin; also PYTHONUNBUFFERED=x') + parser.add_argument('-v', action='count', help='Verbose (trace import statements); also PYTHONVERBOSE=x. Can be supplied multiple times to increase verbosity') + parser.add_argument('-V', '--version', action='store_true', help='Print the Python version number and exit. When given twice, print more information about the build') + parser.add_argument('-W', metavar='arg', help='Warning control; arg is action:message:category:module:lineno. Also PYTHONWARNINGS=arg') + parser.add_argument('-x', action='store_true', help='Skip first line of source, allowing use of non-Unix forms of #!cmd') + parser.add_argument('-X', metavar='opt', help='Set implementation-specific option') + parser.add_argument('--check-hash-based-pycs', choices=['always', 'default', 'never'], help='Control how Python invalidates hash-based .pyc files') + parser.add_argument('--help-env', action='store_true', help='Print help about Python environment variables and exit') + parser.add_argument('--help-xoptions', action='store_true', help='Print help about implementation-specific -X options and exit') + parser.add_argument('--help-all', action='store_true', help='Print complete help information and exit') + + # Arguments + parser.add_argument('file', nargs='?', help='Program read from script file') + parser.add_argument('-', dest='stdin', action='store_true', help='Program read from stdin (default; interactive mode if a tty)') + parser.add_argument('args', nargs=argparse.REMAINDER, help='Arguments passed to program in sys.argv[1:]') + + + parsed_args = parser.parse_args(args) + # print(parsed_args) + + if parsed_args.help or parsed_args.help_env or parsed_args.help_xoptions or parsed_args.help_all: + parser.print_help() + return 0 + + if parsed_args.version: + import sys + print(sys.version) + return 0 + + if parsed_args.c: + cmd = parsed_args.c + cmd = cmd.strip() + + # if the command is sourrounded by quotes, remove them + if (cmd[0] == '"' and cmd[-1] == '"') or (cmd[0] == "'" and cmd[-1] == "'"): + cmd = cmd[1:-1] + try: + exec(cmd) + except Exception as e: + # print Exception traceback + import traceback + print(traceback.format_exc()) + return 1 + return 0 + + return 1 + +# if __name__ == "__main__": +# from contextlib import redirect_stdout + +# with io.StringIO() as buf, redirect_stdout(buf): +# ret = run_cmd(['-h']) +# assert ret == 0 +# assert "usage" in buf.getvalue() + +# with io.StringIO() as buf, redirect_stdout(buf): +# ret = run_cmd(['-V']) +# assert ret == 0 +# assert "3." in buf.getvalue() + +# with io.StringIO() as buf, redirect_stdout(buf): +# ret = run_cmd(['-c', 'a=1+1;print(a)']) +# assert ret == 0 +# ret = str(buf.getvalue()) +# assert buf.getvalue() == "2\n" + + diff --git a/src/js_timestamp.cpp b/src/js_timestamp.cpp index bf8632c..fa8bd94 100644 --- a/src/js_timestamp.cpp +++ b/src/js_timestamp.cpp @@ -1 +1 @@ -#define PYJS_JS_UTC_TIMESTAMP "2024-03-19 09:56:38.004136" +#define PYJS_JS_UTC_TIMESTAMP "2024-07-17 05:10:39.535405" \ No newline at end of file diff --git a/tests/js_tests/test_main.js b/tests/js_tests/test_main.js index 7e4a67d..95d301f 100644 --- a/tests/js_tests/test_main.js +++ b/tests/js_tests/test_main.js @@ -115,6 +115,29 @@ await asyncio.sleep(0.5) `); assert_eq(res, 42); } + +tests.test_cli_exec_code_from_string = async function(){ + let return_code = pyjs.cli(["-c","print(42)"]) + assert_eq(return_code, 0); +} + +// tests.test_cli_exec_code_from_file = async function(){ +// // create a file from wihthin python +// pyjs.exec(` +// content = ''' +// print(42) +// if __name__ == "__main__": +// print("hello world") +// ''' +// with open('/test.py','w') as f: +// f.write(content) +// `) +// let return_code = pyjs.cli(["test.py"]) +// assert_eq(return_code, 0); +// } + + + async function async_main(pyjs_module){ pyjs = pyjs_module var name;