Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pseudocli #71

Draft
wants to merge 3 commits into
base: main
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions include/pyjs/pre_js/init.js
Original file line number Diff line number Diff line change
Expand Up @@ -133,6 +133,8 @@ Module['init_phase_1'] = async function(prefix, python_version) {
return ret['ret']
}
};


}

Module['init_phase_2'] = function(prefix, python_version) {
Expand Down Expand Up @@ -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)
Expand Down
52 changes: 43 additions & 9 deletions include/pyjs/pre_js/load_pkg.js
Original file line number Diff line number Diff line change
Expand Up @@ -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]
)
}
}
}
Expand Down
104 changes: 104 additions & 0 deletions module/pyjs/cli.py
Original file line number Diff line number Diff line change
@@ -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"


2 changes: 1 addition & 1 deletion src/js_timestamp.cpp
Original file line number Diff line number Diff line change
@@ -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"
23 changes: 23 additions & 0 deletions tests/js_tests/test_main.js
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
Loading