diff --git a/src/lib.rs b/src/lib.rs new file mode 100644 index 000000000..2ef1def6b --- /dev/null +++ b/src/lib.rs @@ -0,0 +1,630 @@ +//! This is the `rustpython` binary. If you're looking to embed RustPython into your application, +//! you're likely looking for the [`rustpython-vm`](https://docs.rs/rustpython-vm) crate. +//! +//! You can install `rustpython` with `cargo install rustpython`, or if you'd like to inject your +//! own native modules you can make a binary crate that depends on the `rustpython` crate (and +//! probably `rustpython-vm`, too), and make a `main.rs` that looks like: +//! +//! ```no_run +//! use rustpython_vm::{pymodule, py_freeze}; +//! fn main() { +//! rustpython::run(|vm| { +//! vm.add_native_module("mymod".to_owned(), Box::new(mymod::make_module)); +//! vm.add_frozen(py_freeze!(source = "def foo(): pass", module_name = "otherthing")); +//! }); +//! } +//! +//! #[pymodule] +//! mod mymod { +//! use rustpython_vm::builtins::PyStrRef; +//TODO: use rustpython_vm::prelude::*; +//! use rustpython_vm::common::borrow::BorrowValue; +//! +//! #[pyfunction] +//! fn do_thing(x: i32) -> i32 { +//! x + 1 +//! } +//! +//! #[pyfunction] +//! fn other_thing(s: PyStrRef) -> (String, usize) { +//! let new_string = format!("hello from rust, {}!", s); +//! let prev_len = s.borrow_value().len(); +//! (new_string, prev_len) +//! } +//! } +//! ``` +//! +//! The binary will have all the standard arguments of a python interpreter (including a REPL!) but +//! it will have your modules loaded into the vm. +#![allow(clippy::needless_doctest_main)] + +#[macro_use] +extern crate clap; +extern crate env_logger; +#[macro_use] +extern crate log; + +use clap::{App, AppSettings, Arg, ArgMatches}; +use rustpython_vm::{ + builtins::PyInt, + compile, + exceptions::print_exception, + match_class, + pyobject::{BorrowValue, ItemProtocol, PyResult, TypeProtocol}, + scope::Scope, + sysmodule, util, InitParameter, Interpreter, PySettings, VirtualMachine, +}; + +use std::convert::TryInto; +use std::env; +use std::path::PathBuf; +use std::process; +use std::str::FromStr; + +mod shell; + +pub use rustpython_vm; + +/// The main cli of the `rustpython` interpreter. This function will exit with `process::exit()` +/// based on the return code of the python code ran through the cli. +pub fn run(init: F) -> ! +where + F: FnOnce(&mut VirtualMachine), +{ + #[cfg(feature = "flame-it")] + let main_guard = flame::start_guard("RustPython main"); + env_logger::init(); + let app = App::new("RustPython"); + let matches = parse_arguments(app); + let settings = create_settings(&matches); + + // don't translate newlines (\r\n <=> \n) + #[cfg(windows)] + { + extern "C" { + fn _setmode(fd: i32, flags: i32) -> i32; + } + unsafe { + _setmode(0, libc::O_BINARY); + _setmode(1, libc::O_BINARY); + _setmode(2, libc::O_BINARY); + } + } + + // We only include the standard library bytecode in WASI when initializing + let init_param = if cfg!(target_os = "wasi") { + InitParameter::Internal + } else { + InitParameter::External + }; + + let interp = Interpreter::new_with_init(settings, |vm| { + init(vm); + init_param + }); + + let exitcode = interp.enter(move |vm| { + let res = run_rustpython(vm, &matches); + + flush_std(vm); + + #[cfg(feature = "flame-it")] + { + main_guard.end(); + if let Err(e) = write_profile(&matches) { + error!("Error writing profile information: {}", e); + } + } + + // See if any exception leaked out: + let exitcode = match res { + Ok(()) => 0, + Err(err) if err.isinstance(&vm.ctx.exceptions.system_exit) => { + let args = err.args(); + match args.borrow_value().len() { + 0 => 0, + 1 => match_class!(match args.borrow_value()[0].clone() { + i @ PyInt => { + use num_traits::cast::ToPrimitive; + i.borrow_value().to_i32().unwrap_or(0) + } + arg => { + if vm.is_none(&arg) { + 0 + } else { + if let Ok(s) = vm.to_str(&arg) { + eprintln!("{}", s); + } + 1 + } + } + }), + _ => { + if let Ok(r) = vm.to_repr(args.as_object()) { + eprintln!("{}", r); + } + 1 + } + } + } + Err(err) => { + print_exception(&vm, err); + 1 + } + }; + + let _ = vm.run_atexit_funcs(); + + flush_std(vm); + + exitcode + }); + + process::exit(exitcode) +} + +fn flush_std(vm: &VirtualMachine) { + if let Ok(stdout) = sysmodule::get_stdout(vm) { + let _ = vm.call_method(&stdout, "flush", ()); + } + if let Ok(stderr) = sysmodule::get_stderr(vm) { + let _ = vm.call_method(&stderr, "flush", ()); + } +} + +fn parse_arguments<'a>(app: App<'a, '_>) -> ArgMatches<'a> { + let app = app + .setting(AppSettings::TrailingVarArg) + .version(crate_version!()) + .author(crate_authors!()) + .about("Rust implementation of the Python language") + .usage("rustpython [OPTIONS] [-c CMD | -m MODULE | FILE] [PYARGS]...") + .arg( + Arg::with_name("script") + .required(false) + .allow_hyphen_values(true) + .multiple(true) + .value_name("script, args") + .min_values(1), + ) + .arg( + Arg::with_name("c") + .short("c") + .takes_value(true) + .allow_hyphen_values(true) + .multiple(true) + .value_name("cmd, args") + .min_values(1) + .help("run the given string as a program"), + ) + .arg( + Arg::with_name("m") + .short("m") + .takes_value(true) + .allow_hyphen_values(true) + .multiple(true) + .value_name("module, args") + .min_values(1) + .help("run library module as script"), + ) + .arg( + Arg::with_name("optimize") + .short("O") + .multiple(true) + .help("Optimize. Set __debug__ to false. Remove debug statements."), + ) + .arg( + Arg::with_name("verbose") + .short("v") + .multiple(true) + .help("Give the verbosity (can be applied multiple times)"), + ) + .arg(Arg::with_name("debug").short("d").help("Debug the parser.")) + .arg( + Arg::with_name("quiet") + .short("q") + .help("Be quiet at startup."), + ) + .arg( + Arg::with_name("inspect") + .short("i") + .help("Inspect interactively after running the script."), + ) + .arg( + Arg::with_name("no-user-site") + .short("s") + .help("don't add user site directory to sys.path."), + ) + .arg( + Arg::with_name("no-site") + .short("S") + .help("don't imply 'import site' on initialization"), + ) + .arg( + Arg::with_name("dont-write-bytecode") + .short("B") + .help("don't write .pyc files on import"), + ) + .arg( + Arg::with_name("ignore-environment") + .short("E") + .help("Ignore environment variables PYTHON* such as PYTHONPATH"), + ) + .arg( + Arg::with_name("isolate") + .short("I") + .help("isolate Python from the user's environment (implies -E and -s)"), + ) + .arg( + Arg::with_name("implementation-option") + .short("X") + .takes_value(true) + .multiple(true) + .help("set implementation-specific option"), + ) + .arg( + Arg::with_name("warning-control") + .short("W") + .takes_value(true) + .multiple(true) + .help("warning control; arg is action:message:category:module:lineno"), + ) + .arg( + Arg::with_name("bytes-warning") + .short("b") + .multiple(true) + .help("issue warnings about using bytes where strings are usually expected (-bb: issue errors)"), + ); + #[cfg(feature = "flame-it")] + let app = app + .arg( + Arg::with_name("profile_output") + .long("profile-output") + .takes_value(true) + .help("the file to output the profiling information to"), + ) + .arg( + Arg::with_name("profile_format") + .long("profile-format") + .takes_value(true) + .help("the profile format to output the profiling information in"), + ); + app.get_matches() +} + +/// Create settings by examining command line arguments and environment +/// variables. +fn create_settings(matches: &ArgMatches) -> PySettings { + let mut settings = PySettings::default(); + settings.isolated = matches.is_present("isolate"); + settings.ignore_environment = matches.is_present("ignore-environment"); + let ignore_environment = settings.ignore_environment || settings.isolated; + + settings.interactive = !matches.is_present("c") + && !matches.is_present("m") + && (!matches.is_present("script") || matches.is_present("inspect")); + + // add the current directory to sys.path + settings.path_list.push("".to_owned()); + + // BUILDTIME_RUSTPYTHONPATH should be set when distributing + if let Some(paths) = option_env!("BUILDTIME_RUSTPYTHONPATH") { + settings.path_list.extend( + std::env::split_paths(paths).map(|path| path.into_os_string().into_string().unwrap()), + ) + } else { + #[cfg(all(feature = "pylib", not(feature = "freeze-stdlib")))] + settings.path_list.push(pylib::LIB_PATH.to_owned()); + } + + if !ignore_environment { + settings.path_list.extend(get_paths("RUSTPYTHONPATH")); + settings.path_list.extend(get_paths("PYTHONPATH")); + } + + // Now process command line flags: + if matches.is_present("debug") || (!ignore_environment && env::var_os("PYTHONDEBUG").is_some()) + { + settings.debug = true; + } + + if matches.is_present("inspect") + || (!ignore_environment && env::var_os("PYTHONINSPECT").is_some()) + { + settings.inspect = true; + } + + if matches.is_present("optimize") { + settings.optimize = matches.occurrences_of("optimize").try_into().unwrap(); + } else if !ignore_environment { + if let Ok(value) = get_env_var_value("PYTHONOPTIMIZE") { + settings.optimize = value; + } + } + + if matches.is_present("verbose") { + settings.verbose = matches.occurrences_of("verbose").try_into().unwrap(); + } else if !ignore_environment { + if let Ok(value) = get_env_var_value("PYTHONVERBOSE") { + settings.verbose = value; + } + } + + settings.bytes_warning = matches.occurrences_of("bytes-warning"); + + settings.no_site = matches.is_present("no-site"); + + if matches.is_present("no-user-site") + || matches.is_present("isolate") + || (!ignore_environment && env::var_os("PYTHONNOUSERSITE").is_some()) + { + settings.no_user_site = true; + } + + if matches.is_present("quiet") { + settings.quiet = true; + } + + if matches.is_present("dont-write-bytecode") + || (!ignore_environment && env::var_os("PYTHONDONTWRITEBYTECODE").is_some()) + { + settings.dont_write_bytecode = true; + } + + let mut dev_mode = false; + if let Some(xopts) = matches.values_of("implementation-option") { + settings.xopts.extend(xopts.map(|s| { + let mut parts = s.splitn(2, '='); + let name = parts.next().unwrap().to_owned(); + if name == "dev" { + dev_mode = true + } + let value = parts.next().map(ToOwned::to_owned); + (name, value) + })); + } + settings.dev_mode = dev_mode; + + if dev_mode { + settings.warnopts.push("default".to_owned()) + } + if settings.bytes_warning > 0 { + let warn = if settings.bytes_warning > 1 { + "error::BytesWarning" + } else { + "default::BytesWarning" + }; + settings.warnopts.push(warn.to_owned()); + } + if let Some(warnings) = matches.values_of("warning-control") { + settings.warnopts.extend(warnings.map(ToOwned::to_owned)); + } + + let argv = if let Some(script) = matches.values_of("script") { + script.map(ToOwned::to_owned).collect() + } else if let Some(module) = matches.values_of("m") { + std::iter::once("PLACEHOLDER".to_owned()) + .chain(module.skip(1).map(ToOwned::to_owned)) + .collect() + } else if let Some(cmd) = matches.values_of("c") { + std::iter::once("-c".to_owned()) + .chain(cmd.skip(1).map(ToOwned::to_owned)) + .collect() + } else { + vec!["".to_owned()] + }; + + let hash_seed = match env::var("PYTHONHASHSEED") { + Ok(s) if s == "random" => Some(None), + Ok(s) => s.parse::().ok().map(Some), + Err(_) => Some(None), + }; + settings.hash_seed = hash_seed.unwrap_or_else(|| { + error!("Fatal Python init error: PYTHONHASHSEED must be \"random\" or an integer in range [0; 4294967295]"); + process::exit(1) + }); + + settings.argv = argv; + + settings +} + +/// Get environment variable and turn it into integer. +fn get_env_var_value(name: &str) -> Result { + env::var(name).map(|value| { + if let Ok(value) = u8::from_str(&value) { + value + } else { + 1 + } + }) +} + +/// Helper function to retrieve a sequence of paths from an environment variable. +fn get_paths(env_variable_name: &str) -> impl Iterator + '_ { + env::var_os(env_variable_name) + .into_iter() + .flat_map(move |paths| { + env::split_paths(&paths) + .map(|path| { + path.into_os_string() + .into_string() + .unwrap_or_else(|_| panic!("{} isn't valid unicode", env_variable_name)) + }) + .collect::>() + }) +} + +#[cfg(feature = "flame-it")] +fn write_profile(matches: &ArgMatches) -> Result<(), Box> { + use std::{fs, io}; + + enum ProfileFormat { + Html, + Text, + Speedscope, + } + + let profile_output = matches.value_of_os("profile_output"); + + let profile_format = match matches.value_of("profile_format") { + Some("html") => ProfileFormat::Html, + Some("text") => ProfileFormat::Text, + None if profile_output == Some("-".as_ref()) => ProfileFormat::Text, + Some("speedscope") | None => ProfileFormat::Speedscope, + Some(other) => { + error!("Unknown profile format {}", other); + process::exit(1); + } + }; + + let profile_output = profile_output.unwrap_or_else(|| match profile_format { + ProfileFormat::Html => "flame-graph.html".as_ref(), + ProfileFormat::Text => "flame.txt".as_ref(), + ProfileFormat::Speedscope => "flamescope.json".as_ref(), + }); + + let profile_output: Box = if profile_output == "-" { + Box::new(io::stdout()) + } else { + Box::new(fs::File::create(profile_output)?) + }; + + let profile_output = io::BufWriter::new(profile_output); + + match profile_format { + ProfileFormat::Html => flame::dump_html(profile_output)?, + ProfileFormat::Text => flame::dump_text_to_writer(profile_output)?, + ProfileFormat::Speedscope => flamescope::dump(profile_output)?, + } + + Ok(()) +} + +fn run_rustpython(vm: &VirtualMachine, matches: &ArgMatches) -> PyResult<()> { + let scope = vm.new_scope_with_builtins(); + let main_module = vm.new_module("__main__", scope.globals.clone()); + + vm.get_attribute(vm.sys_module.clone(), "modules")? + .set_item("__main__", main_module, vm)?; + + let site_result = vm.import("site", &[], 0); + + if site_result.is_err() { + warn!( + "Failed to import site, consider adding the Lib directory to your RUSTPYTHONPATH \ + environment variable", + ); + } + + // Figure out if a -c option was given: + if let Some(command) = matches.value_of("c") { + run_command(&vm, scope, command.to_owned())?; + } else if let Some(module) = matches.value_of("m") { + run_module(&vm, module)?; + } else if let Some(filename) = matches.value_of("script") { + run_script(&vm, scope.clone(), filename)?; + if matches.is_present("inspect") { + shell::run_shell(&vm, scope)?; + } + } else { + println!( + "Welcome to the magnificent Rust Python {} interpreter \u{1f631} \u{1f596}", + crate_version!() + ); + shell::run_shell(&vm, scope)?; + } + + Ok(()) +} + +fn _run_string(vm: &VirtualMachine, scope: Scope, source: &str, source_path: String) -> PyResult { + let code_obj = vm + .compile(source, compile::Mode::Exec, source_path.clone()) + .map_err(|err| vm.new_syntax_error(&err))?; + // trace!("Code object: {:?}", code_obj.borrow()); + scope + .globals + .set_item("__file__", vm.ctx.new_str(source_path), vm)?; + vm.run_code_obj(code_obj, scope) +} + +fn run_command(vm: &VirtualMachine, scope: Scope, source: String) -> PyResult<()> { + debug!("Running command {}", source); + _run_string(vm, scope, &source, "".to_owned())?; + Ok(()) +} + +fn run_module(vm: &VirtualMachine, module: &str) -> PyResult<()> { + debug!("Running module {}", module); + let runpy = vm.import("runpy", &[], 0)?; + let run_module_as_main = vm.get_attribute(runpy, "_run_module_as_main")?; + vm.invoke(&run_module_as_main, (module,))?; + Ok(()) +} + +fn run_script(vm: &VirtualMachine, scope: Scope, script_file: &str) -> PyResult<()> { + debug!("Running file {}", script_file); + // Parse an ast from it: + let file_path = PathBuf::from(script_file); + let file_path = if file_path.is_file() { + file_path + } else if file_path.is_dir() { + let main_file_path = file_path.join("__main__.py"); + if main_file_path.is_file() { + main_file_path + } else { + error!( + "can't find '__main__' module in '{}'", + file_path.to_str().unwrap() + ); + process::exit(1); + } + } else { + error!( + "can't open file '{}': No such file or directory", + file_path.to_str().unwrap() + ); + process::exit(1); + }; + + let dir = file_path.parent().unwrap().to_str().unwrap().to_owned(); + let sys_path = vm.get_attribute(vm.sys_module.clone(), "path").unwrap(); + vm.call_method(&sys_path, "insert", (0, dir))?; + + match util::read_file(&file_path) { + Ok(source) => { + _run_string(vm, scope, &source, file_path.to_str().unwrap().to_owned())?; + } + Err(err) => { + error!( + "Failed reading file '{}': {:?}", + file_path.to_str().unwrap(), + err.kind() + ); + process::exit(1); + } + } + Ok(()) +} + +#[test] +fn test_run_script() { + Interpreter::default().enter(|vm| { + // test file run + let r = run_script( + vm, + vm.new_scope_with_builtins(), + "extra_tests/snippets/dir_main/__main__.py", + ); + assert!(r.is_ok()); + + // test module run + let r = run_script( + vm, + vm.new_scope_with_builtins(), + "extra_tests/snippets/dir_main", + ); + assert!(r.is_ok()); + }) +} diff --git a/src/main.rs b/src/main.rs index 064b44567..fdc9f7037 100644 --- a/src/main.rs +++ b/src/main.rs @@ -1,580 +1,3 @@ -#[macro_use] -extern crate clap; -extern crate env_logger; -#[macro_use] -extern crate log; - -use clap::{App, AppSettings, Arg, ArgMatches}; -use rustpython_vm::{ - builtins::PyInt, - compile, - exceptions::print_exception, - match_class, - pyobject::{BorrowValue, ItemProtocol, PyResult, TypeProtocol}, - scope::Scope, - sysmodule, util, InitParameter, Interpreter, PySettings, VirtualMachine, -}; - -use std::convert::TryInto; -use std::env; -use std::path::PathBuf; -use std::process; -use std::str::FromStr; - -mod shell; - -fn main() { - #[cfg(feature = "flame-it")] - let main_guard = flame::start_guard("RustPython main"); - env_logger::init(); - let app = App::new("RustPython"); - let matches = parse_arguments(app); - let settings = create_settings(&matches); - - // don't translate newlines (\r\n <=> \n) - #[cfg(windows)] - { - extern "C" { - fn _setmode(fd: i32, flags: i32) -> i32; - } - unsafe { - _setmode(0, libc::O_BINARY); - _setmode(1, libc::O_BINARY); - _setmode(2, libc::O_BINARY); - } - } - - // We only include the standard library bytecode in WASI when initializing - let init = if cfg!(target_os = "wasi") { - InitParameter::Internal - } else { - InitParameter::External - }; - - let interp = Interpreter::new(settings, init); - - let exitcode = interp.enter(move |vm| { - let res = run_rustpython(vm, &matches); - - flush_std(vm); - - #[cfg(feature = "flame-it")] - { - main_guard.end(); - if let Err(e) = write_profile(&matches) { - error!("Error writing profile information: {}", e); - } - } - - // See if any exception leaked out: - let exitcode = match res { - Ok(()) => 0, - Err(err) if err.isinstance(&vm.ctx.exceptions.system_exit) => { - let args = err.args(); - match args.borrow_value().len() { - 0 => 0, - 1 => match_class!(match args.borrow_value()[0].clone() { - i @ PyInt => { - use num_traits::cast::ToPrimitive; - i.borrow_value().to_i32().unwrap_or(0) - } - arg => { - if vm.is_none(&arg) { - 0 - } else { - if let Ok(s) = vm.to_str(&arg) { - eprintln!("{}", s); - } - 1 - } - } - }), - _ => { - if let Ok(r) = vm.to_repr(args.as_object()) { - eprintln!("{}", r); - } - 1 - } - } - } - Err(err) => { - print_exception(&vm, err); - 1 - } - }; - - let _ = vm.run_atexit_funcs(); - - flush_std(vm); - - exitcode - }); - - process::exit(exitcode); -} - -fn flush_std(vm: &VirtualMachine) { - if let Ok(stdout) = sysmodule::get_stdout(vm) { - let _ = vm.call_method(&stdout, "flush", ()); - } - if let Ok(stderr) = sysmodule::get_stderr(vm) { - let _ = vm.call_method(&stderr, "flush", ()); - } -} - -fn parse_arguments<'a>(app: App<'a, '_>) -> ArgMatches<'a> { - let app = app - .setting(AppSettings::TrailingVarArg) - .version(crate_version!()) - .author(crate_authors!()) - .about("Rust implementation of the Python language") - .usage("rustpython [OPTIONS] [-c CMD | -m MODULE | FILE] [PYARGS]...") - .arg( - Arg::with_name("script") - .required(false) - .allow_hyphen_values(true) - .multiple(true) - .value_name("script, args") - .min_values(1), - ) - .arg( - Arg::with_name("c") - .short("c") - .takes_value(true) - .allow_hyphen_values(true) - .multiple(true) - .value_name("cmd, args") - .min_values(1) - .help("run the given string as a program"), - ) - .arg( - Arg::with_name("m") - .short("m") - .takes_value(true) - .allow_hyphen_values(true) - .multiple(true) - .value_name("module, args") - .min_values(1) - .help("run library module as script"), - ) - .arg( - Arg::with_name("optimize") - .short("O") - .multiple(true) - .help("Optimize. Set __debug__ to false. Remove debug statements."), - ) - .arg( - Arg::with_name("verbose") - .short("v") - .multiple(true) - .help("Give the verbosity (can be applied multiple times)"), - ) - .arg(Arg::with_name("debug").short("d").help("Debug the parser.")) - .arg( - Arg::with_name("quiet") - .short("q") - .help("Be quiet at startup."), - ) - .arg( - Arg::with_name("inspect") - .short("i") - .help("Inspect interactively after running the script."), - ) - .arg( - Arg::with_name("no-user-site") - .short("s") - .help("don't add user site directory to sys.path."), - ) - .arg( - Arg::with_name("no-site") - .short("S") - .help("don't imply 'import site' on initialization"), - ) - .arg( - Arg::with_name("dont-write-bytecode") - .short("B") - .help("don't write .pyc files on import"), - ) - .arg( - Arg::with_name("ignore-environment") - .short("E") - .help("Ignore environment variables PYTHON* such as PYTHONPATH"), - ) - .arg( - Arg::with_name("isolate") - .short("I") - .help("isolate Python from the user's environment (implies -E and -s)"), - ) - .arg( - Arg::with_name("implementation-option") - .short("X") - .takes_value(true) - .multiple(true) - .help("set implementation-specific option"), - ) - .arg( - Arg::with_name("warning-control") - .short("W") - .takes_value(true) - .multiple(true) - .help("warning control; arg is action:message:category:module:lineno"), - ) - .arg( - Arg::with_name("bytes-warning") - .short("b") - .multiple(true) - .help("issue warnings about using bytes where strings are usually expected (-bb: issue errors)"), - ); - #[cfg(feature = "flame-it")] - let app = app - .arg( - Arg::with_name("profile_output") - .long("profile-output") - .takes_value(true) - .help("the file to output the profiling information to"), - ) - .arg( - Arg::with_name("profile_format") - .long("profile-format") - .takes_value(true) - .help("the profile format to output the profiling information in"), - ); - app.get_matches() -} - -/// Create settings by examining command line arguments and environment -/// variables. -fn create_settings(matches: &ArgMatches) -> PySettings { - let mut settings = PySettings::default(); - settings.isolated = matches.is_present("isolate"); - settings.ignore_environment = matches.is_present("ignore-environment"); - let ignore_environment = settings.ignore_environment || settings.isolated; - - settings.interactive = !matches.is_present("c") - && !matches.is_present("m") - && (!matches.is_present("script") || matches.is_present("inspect")); - - // add the current directory to sys.path - settings.path_list.push("".to_owned()); - - // BUILDTIME_RUSTPYTHONPATH should be set when distributing - if let Some(paths) = option_env!("BUILDTIME_RUSTPYTHONPATH") { - settings.path_list.extend( - std::env::split_paths(paths).map(|path| path.into_os_string().into_string().unwrap()), - ) - } else { - #[cfg(all(feature = "pylib", not(feature = "freeze-stdlib")))] - settings.path_list.push(pylib::LIB_PATH.to_owned()); - } - - if !ignore_environment { - settings.path_list.extend(get_paths("RUSTPYTHONPATH")); - settings.path_list.extend(get_paths("PYTHONPATH")); - } - - // Now process command line flags: - if matches.is_present("debug") || (!ignore_environment && env::var_os("PYTHONDEBUG").is_some()) - { - settings.debug = true; - } - - if matches.is_present("inspect") - || (!ignore_environment && env::var_os("PYTHONINSPECT").is_some()) - { - settings.inspect = true; - } - - if matches.is_present("optimize") { - settings.optimize = matches.occurrences_of("optimize").try_into().unwrap(); - } else if !ignore_environment { - if let Ok(value) = get_env_var_value("PYTHONOPTIMIZE") { - settings.optimize = value; - } - } - - if matches.is_present("verbose") { - settings.verbose = matches.occurrences_of("verbose").try_into().unwrap(); - } else if !ignore_environment { - if let Ok(value) = get_env_var_value("PYTHONVERBOSE") { - settings.verbose = value; - } - } - - settings.bytes_warning = matches.occurrences_of("bytes-warning"); - - settings.no_site = matches.is_present("no-site"); - - if matches.is_present("no-user-site") - || matches.is_present("isolate") - || (!ignore_environment && env::var_os("PYTHONNOUSERSITE").is_some()) - { - settings.no_user_site = true; - } - - if matches.is_present("quiet") { - settings.quiet = true; - } - - if matches.is_present("dont-write-bytecode") - || (!ignore_environment && env::var_os("PYTHONDONTWRITEBYTECODE").is_some()) - { - settings.dont_write_bytecode = true; - } - - let mut dev_mode = false; - if let Some(xopts) = matches.values_of("implementation-option") { - settings.xopts.extend(xopts.map(|s| { - let mut parts = s.splitn(2, '='); - let name = parts.next().unwrap().to_owned(); - if name == "dev" { - dev_mode = true - } - let value = parts.next().map(ToOwned::to_owned); - (name, value) - })); - } - settings.dev_mode = dev_mode; - - if dev_mode { - settings.warnopts.push("default".to_owned()) - } - if settings.bytes_warning > 0 { - let warn = if settings.bytes_warning > 1 { - "error::BytesWarning" - } else { - "default::BytesWarning" - }; - settings.warnopts.push(warn.to_owned()); - } - if let Some(warnings) = matches.values_of("warning-control") { - settings.warnopts.extend(warnings.map(ToOwned::to_owned)); - } - - let argv = if let Some(script) = matches.values_of("script") { - script.map(ToOwned::to_owned).collect() - } else if let Some(module) = matches.values_of("m") { - std::iter::once("PLACEHOLDER".to_owned()) - .chain(module.skip(1).map(ToOwned::to_owned)) - .collect() - } else if let Some(cmd) = matches.values_of("c") { - std::iter::once("-c".to_owned()) - .chain(cmd.skip(1).map(ToOwned::to_owned)) - .collect() - } else { - vec!["".to_owned()] - }; - - let hash_seed = match env::var("PYTHONHASHSEED") { - Ok(s) if s == "random" => Some(None), - Ok(s) => s.parse::().ok().map(Some), - Err(_) => Some(None), - }; - settings.hash_seed = hash_seed.unwrap_or_else(|| { - error!("Fatal Python init error: PYTHONHASHSEED must be \"random\" or an integer in range [0; 4294967295]"); - process::exit(1) - }); - - settings.argv = argv; - - settings -} - -/// Get environment variable and turn it into integer. -fn get_env_var_value(name: &str) -> Result { - env::var(name).map(|value| { - if let Ok(value) = u8::from_str(&value) { - value - } else { - 1 - } - }) -} - -/// Helper function to retrieve a sequence of paths from an environment variable. -fn get_paths(env_variable_name: &str) -> impl Iterator + '_ { - env::var_os(env_variable_name) - .into_iter() - .flat_map(move |paths| { - env::split_paths(&paths) - .map(|path| { - path.into_os_string() - .into_string() - .unwrap_or_else(|_| panic!("{} isn't valid unicode", env_variable_name)) - }) - .collect::>() - }) -} - -#[cfg(feature = "flame-it")] -fn write_profile(matches: &ArgMatches) -> Result<(), Box> { - use std::{fs, io}; - - enum ProfileFormat { - Html, - Text, - Speedscope, - } - - let profile_output = matches.value_of_os("profile_output"); - - let profile_format = match matches.value_of("profile_format") { - Some("html") => ProfileFormat::Html, - Some("text") => ProfileFormat::Text, - None if profile_output == Some("-".as_ref()) => ProfileFormat::Text, - Some("speedscope") | None => ProfileFormat::Speedscope, - Some(other) => { - error!("Unknown profile format {}", other); - process::exit(1); - } - }; - - let profile_output = profile_output.unwrap_or_else(|| match profile_format { - ProfileFormat::Html => "flame-graph.html".as_ref(), - ProfileFormat::Text => "flame.txt".as_ref(), - ProfileFormat::Speedscope => "flamescope.json".as_ref(), - }); - - let profile_output: Box = if profile_output == "-" { - Box::new(io::stdout()) - } else { - Box::new(fs::File::create(profile_output)?) - }; - - let profile_output = io::BufWriter::new(profile_output); - - match profile_format { - ProfileFormat::Html => flame::dump_html(profile_output)?, - ProfileFormat::Text => flame::dump_text_to_writer(profile_output)?, - ProfileFormat::Speedscope => flamescope::dump(profile_output)?, - } - - Ok(()) -} - -fn run_rustpython(vm: &VirtualMachine, matches: &ArgMatches) -> PyResult<()> { - let scope = vm.new_scope_with_builtins(); - let main_module = vm.new_module("__main__", scope.globals.clone()); - - vm.get_attribute(vm.sys_module.clone(), "modules")? - .set_item("__main__", main_module, vm)?; - - let site_result = vm.import("site", &[], 0); - - if site_result.is_err() { - warn!( - "Failed to import site, consider adding the Lib directory to your RUSTPYTHONPATH \ - environment variable", - ); - } - - // Figure out if a -c option was given: - if let Some(command) = matches.value_of("c") { - run_command(&vm, scope, command.to_owned())?; - } else if let Some(module) = matches.value_of("m") { - run_module(&vm, module)?; - } else if let Some(filename) = matches.value_of("script") { - run_script(&vm, scope.clone(), filename)?; - if matches.is_present("inspect") { - shell::run_shell(&vm, scope)?; - } - } else { - println!( - "Welcome to the magnificent Rust Python {} interpreter \u{1f631} \u{1f596}", - crate_version!() - ); - shell::run_shell(&vm, scope)?; - } - - Ok(()) -} - -fn _run_string(vm: &VirtualMachine, scope: Scope, source: &str, source_path: String) -> PyResult { - let code_obj = vm - .compile(source, compile::Mode::Exec, source_path.clone()) - .map_err(|err| vm.new_syntax_error(&err))?; - // trace!("Code object: {:?}", code_obj.borrow()); - scope - .globals - .set_item("__file__", vm.ctx.new_str(source_path), vm)?; - vm.run_code_obj(code_obj, scope) -} - -fn run_command(vm: &VirtualMachine, scope: Scope, source: String) -> PyResult<()> { - debug!("Running command {}", source); - _run_string(vm, scope, &source, "".to_owned())?; - Ok(()) -} - -fn run_module(vm: &VirtualMachine, module: &str) -> PyResult<()> { - debug!("Running module {}", module); - let runpy = vm.import("runpy", &[], 0)?; - let run_module_as_main = vm.get_attribute(runpy, "_run_module_as_main")?; - vm.invoke(&run_module_as_main, (module,))?; - Ok(()) -} - -fn run_script(vm: &VirtualMachine, scope: Scope, script_file: &str) -> PyResult<()> { - debug!("Running file {}", script_file); - // Parse an ast from it: - let file_path = PathBuf::from(script_file); - let file_path = if file_path.is_file() { - file_path - } else if file_path.is_dir() { - let main_file_path = file_path.join("__main__.py"); - if main_file_path.is_file() { - main_file_path - } else { - error!( - "can't find '__main__' module in '{}'", - file_path.to_str().unwrap() - ); - process::exit(1); - } - } else { - error!( - "can't open file '{}': No such file or directory", - file_path.to_str().unwrap() - ); - process::exit(1); - }; - - let dir = file_path.parent().unwrap().to_str().unwrap().to_owned(); - let sys_path = vm.get_attribute(vm.sys_module.clone(), "path").unwrap(); - vm.call_method(&sys_path, "insert", (0, dir))?; - - match util::read_file(&file_path) { - Ok(source) => { - _run_string(vm, scope, &source, file_path.to_str().unwrap().to_owned())?; - } - Err(err) => { - error!( - "Failed reading file '{}': {:?}", - file_path.to_str().unwrap(), - err.kind() - ); - process::exit(1); - } - } - Ok(()) -} - -#[test] -fn test_run_script() { - Interpreter::default().enter(|vm| { - // test file run - let r = run_script( - vm, - vm.new_scope_with_builtins(), - "extra_tests/snippets/dir_main/__main__.py", - ); - assert!(r.is_ok()); - - // test module run - let r = run_script( - vm, - vm.new_scope_with_builtins(), - "extra_tests/snippets/dir_main", - ); - assert!(r.is_ok()); - }) +pub fn main() { + rustpython::run(|_vm| {}) }