diff --git a/Cargo.lock b/Cargo.lock index 1c428c2d7..e1f720698 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -51,15 +51,6 @@ dependencies = [ "libc", ] -[[package]] -name = "ansi_term" -version = "0.12.1" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2" -dependencies = [ - "winapi", -] - [[package]] name = "anyhow" version = "1.0.96" @@ -255,13 +246,9 @@ version = "2.34.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c" dependencies = [ - "ansi_term", - "atty", "bitflags 1.3.2", - "strsim", "textwrap 0.11.0", "unicode-width 0.1.14", - "vec_map", ] [[package]] @@ -1074,6 +1061,12 @@ dependencies = [ "static_assertions", ] +[[package]] +name = "lexopt" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "baff4b617f7df3d896f97fe922b64817f6cd9a756bb81d40f8883f2f66dcb401" + [[package]] name = "libc" version = "0.2.169" @@ -1952,12 +1945,12 @@ name = "rustpython" version = "0.4.0" dependencies = [ "cfg-if", - "clap", "criterion", "dirs-next", "env_logger", "flame", "flamescope", + "lexopt", "libc", "log", "pyo3", @@ -2557,12 +2550,6 @@ version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f" -[[package]] -name = "strsim" -version = "0.8.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a" - [[package]] name = "strum" version = "0.27.1" @@ -3023,12 +3010,6 @@ version = "0.2.15" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "accd4ea62f7bb7a82fe23066fb0957d48ef677f6eeb8215f372f52e48bb32426" -[[package]] -name = "vec_map" -version = "0.8.2" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191" - [[package]] name = "version_check" version = "0.9.5" diff --git a/Cargo.toml b/Cargo.toml index 14e66b39e..2d66f9775 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -34,7 +34,7 @@ cfg-if = { workspace = true } log = { workspace = true } flame = { workspace = true, optional = true } -clap = "2.34" +lexopt = "0.3" dirs = { package = "dirs-next", version = "2.0.0" } env_logger = { version = "0.9.0", default-features = false, features = ["atty", "termcolor"] } flamescope = { version = "0.1.2", optional = true } diff --git a/Lib/test/test_cmd_line.py b/Lib/test/test_cmd_line.py index f7247705d..da53f085a 100644 --- a/Lib/test/test_cmd_line.py +++ b/Lib/test/test_cmd_line.py @@ -38,8 +38,6 @@ class CmdLineTest(unittest.TestCase): self.assertNotIn(b'Traceback', err) return out - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_help(self): self.verify_valid_flag('-h') self.verify_valid_flag('-?') @@ -82,8 +80,6 @@ class CmdLineTest(unittest.TestCase): def test_site_flag(self): self.verify_valid_flag('-S') - # NOTE: RUSTPYTHON version never starts with Python - @unittest.expectedFailure def test_version(self): version = ('Python %d.%d' % sys.version_info[:2]).encode("ascii") for switch in '-V', '--version', '-VV': @@ -550,8 +546,6 @@ class CmdLineTest(unittest.TestCase): def test_no_std_streams(self): self._test_no_stdio(['stdin', 'stdout', 'stderr']) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_hash_randomization(self): # Verify that -R enables hash randomization: self.verify_valid_flag('-R') @@ -966,8 +960,6 @@ class IgnoreEnvironmentTest(unittest.TestCase): self.run_ignoring_vars("'{}' not in sys.path".format(path), PYTHONPATH=path) - # TODO: RUSTPYTHON - @unittest.expectedFailure def test_ignore_PYTHONHASHSEED(self): self.run_ignoring_vars("sys.flags.hash_randomization == 1", PYTHONHASHSEED="0") diff --git a/examples/dis.rs b/examples/dis.rs index 0b1e7c0d3..ee424f7b3 100644 --- a/examples/dis.rs +++ b/examples/dis.rs @@ -6,67 +6,59 @@ /// example usage: /// $ cargo run --release --example dis demo*.py -#[macro_use] -extern crate clap; -extern crate env_logger; #[macro_use] extern crate log; -use clap::{App, Arg}; +use lexopt::ValueExt; use rustpython_compiler as compiler; use std::error::Error; use std::fs; -use std::path::Path; +use std::path::{Path, PathBuf}; -fn main() { +fn main() -> Result<(), lexopt::Error> { env_logger::init(); - let app = App::new("dis") - .version(crate_version!()) - .author(crate_authors!()) - .about("Compiles and disassembles python script files for viewing their bytecode.") - .arg( - Arg::with_name("scripts") - .help("Scripts to scan") - .multiple(true) - .required(true), - ) - .arg( - Arg::with_name("mode") - .help("The mode to compile the scripts in") - .long("mode") - .short("m") - .default_value("exec") - .possible_values(&["exec", "single", "eval"]) - .takes_value(true), - ) - .arg( - Arg::with_name("no_expand") - .help( - "Don't expand CodeObject LoadConst instructions to show \ - the instructions inside", - ) - .long("no-expand") - .short("x"), - ) - .arg( - Arg::with_name("optimize") - .help("The amount of optimization to apply to the compiled bytecode") - .short("O") - .multiple(true), - ); - let matches = app.get_matches(); - let mode = matches.value_of_lossy("mode").unwrap().parse().unwrap(); - let expand_code_objects = !matches.is_present("no_expand"); - let optimize = matches.occurrences_of("optimize") as u8; - let scripts = matches.values_of_os("scripts").unwrap(); + let mut scripts = vec![]; + let mut mode = compiler::Mode::Exec; + let mut expand_code_objects = true; + let mut optimize = 0; + + let mut parser = lexopt::Parser::from_env(); + while let Some(arg) = parser.next()? { + use lexopt::Arg::*; + match arg { + Long("help") | Short('h') => { + let bin_name = parser.bin_name().unwrap_or("dis"); + println!( + "usage: {bin_name} [-m,--mode=exec|single|eval] [-x,--no-expand] [-O]" + ); + println!( + "Compiles and disassembles python script files for viewing their bytecode." + ); + return Ok(()); + } + Value(x) => scripts.push(PathBuf::from(x)), + Long("mode") | Short('m') => { + mode = parser + .value()? + .parse_with(|s| s.parse::().map_err(|e| e.to_string()))? + } + Long("no-expand") | Short('x') => expand_code_objects = false, + Short('O') => optimize += 1, + _ => return Err(arg.unexpected()), + } + } + + if scripts.is_empty() { + return Err("expected at least one argument".into()); + } let opts = compiler::CompileOpts { optimize, ..Default::default() }; - for script in scripts.map(Path::new) { + for script in &scripts { if script.exists() && script.is_file() { let res = display_script(script, mode, opts.clone(), expand_code_objects); if let Err(e) = res { @@ -76,6 +68,8 @@ fn main() { eprintln!("{script:?} is not a file."); } } + + Ok(()) } fn display_script( diff --git a/examples/parse_folder.rs b/examples/parse_folder.rs index f54be635c..97a1eaf03 100644 --- a/examples/parse_folder.rs +++ b/examples/parse_folder.rs @@ -4,38 +4,28 @@ /// /// example usage: /// $ RUST_LOG=info cargo run --release parse_folder /usr/lib/python3.7 - -#[macro_use] -extern crate clap; extern crate env_logger; #[macro_use] extern crate log; -use clap::{App, Arg}; use rustpython_parser::{Parse, ast}; use std::{ - path::Path, + path::{Path, PathBuf}, time::{Duration, Instant}, }; fn main() { env_logger::init(); - let app = App::new("parse_folders") - .version(crate_version!()) - .author(crate_authors!()) - .about("Walks over all .py files in a folder, and parses them.") - .arg( - Arg::with_name("folder") - .help("Folder to scan") - .required(true), - ); - let matches = app.get_matches(); - let folder = Path::new(matches.value_of("folder").unwrap()); + let folder: PathBuf = std::env::args_os() + .nth(1) + .expect("please pass a path argument") + .into(); + if folder.exists() && folder.is_dir() { println!("Parsing folder of python code: {folder:?}"); let t1 = Instant::now(); - let parsed_files = parse_folder(folder).unwrap(); + let parsed_files = parse_folder(&folder).unwrap(); let t2 = Instant::now(); let results = ScanResult { t1, diff --git a/src/lib.rs b/src/lib.rs index b0a176acf..67a2a16ee 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -37,9 +37,6 @@ //! 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; @@ -57,7 +54,7 @@ use std::process::ExitCode; pub use interpreter::InterpreterConfig; pub use rustpython_vm as vm; -pub use settings::{InstallPipMode, RunMode, opts_with_clap}; +pub use settings::{InstallPipMode, RunMode, parse_opts}; pub use shell::run_shell; /// The main cli of the `rustpython` interpreter. This function will return `std::process::ExitCode` @@ -73,7 +70,13 @@ pub fn run(init: impl FnOnce(&mut VirtualMachine) + 'static) -> ExitCode { }; } - let (settings, run_mode) = opts_with_clap(); + let (settings, run_mode) = match parse_opts() { + Ok(x) => x, + Err(e) => { + println!("{e}"); + return ExitCode::FAILURE; + } + }; // don't translate newlines (\r\n <=> \n) #[cfg(windows)] diff --git a/src/settings.rs b/src/settings.rs index 35114374c..bc279ba28 100644 --- a/src/settings.rs +++ b/src/settings.rs @@ -1,6 +1,8 @@ -use clap::{App, AppSettings, Arg, ArgMatches}; -use rustpython_vm::Settings; -use std::env; +use lexopt::Arg::*; +use lexopt::ValueExt; +use rustpython_vm::{Settings, vm::CheckHashPycsMode}; +use std::str::FromStr; +use std::{cmp, env}; pub enum RunMode { Script(String), @@ -15,192 +17,200 @@ pub enum InstallPipMode { GetPip, } -pub fn opts_with_clap() -> (Settings, RunMode) { - let app = App::new("RustPython"); - let matches = parse_arguments(app); - settings_from(&matches) +impl FromStr for InstallPipMode { + type Err = &'static str; + + fn from_str(s: &str) -> Result { + match s { + "ensurepip" => Ok(InstallPipMode::Ensurepip), + "get-pip" => Ok(InstallPipMode::GetPip), + _ => Err("--install-pip takes ensurepip or get-pip as first argument"), + } + } } -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("install_pip") - .long("install-pip") - .takes_value(true) - .allow_hyphen_values(true) - .multiple(true) - .value_name("get-pip args") - .min_values(0) - .help( - "install the pip package manager for rustpython; \ - requires rustpython be build with the ssl feature enabled.", - ), - ) - .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") - .multiple(true) - .help("Debug the parser."), - ) - .arg( - Arg::with_name("quiet") - .short("q") - .multiple(true) - .help("Be quiet at startup."), - ) - .arg( - Arg::with_name("inspect") - .short("i") - .multiple(true) - .help("Inspect interactively after running the script."), - ) - .arg( - Arg::with_name("no-user-site") - .short("s") - .multiple(true) - .help("don't add user site directory to sys.path."), - ) - .arg( - Arg::with_name("no-site") - .short("S") - .multiple(true) - .help("don't imply 'import site' on initialization"), - ) - .arg( - Arg::with_name("dont-write-bytecode") - .short("B") - .multiple(true) - .help("don't write .pyc files on import"), - ) - .arg( - Arg::with_name("safe-path") - .short("P") - .multiple(true) - .help("don’t prepend a potentially unsafe path to sys.path"), - ) - .arg( - Arg::with_name("ignore-environment") - .short("E") - .multiple(true) - .help("Ignore environment variables PYTHON* such as PYTHONPATH"), - ) - .arg( - Arg::with_name("isolate") - .short("I") - .multiple(true) - .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) - .number_of_values(1) - .help("set implementation-specific option"), - ) - .arg( - Arg::with_name("warning-control") - .short("W") - .takes_value(true) - .multiple(true) - .number_of_values(1) - .help("warning control; arg is action:message:category:module:lineno"), - ) - .arg( - Arg::with_name("check-hash-based-pycs") - .long("check-hash-based-pycs") - .takes_value(true) - .number_of_values(1) - .possible_values(&["always", "default", "never"]) - .help("control how Python invalidates hash-based .pyc files"), - ) - .arg( - Arg::with_name("bytes-warning") - .short("b") - .multiple(true) - .help( - "issue warnings about using bytes where strings \ - are usually expected (-bb: issue errors)", - ), - ) - .arg(Arg::with_name("unbuffered").short("u").multiple(true).help( - "force the stdout and stderr streams to be unbuffered; \ - this option has no effect on stdin; also PYTHONUNBUFFERED=x", - )); +#[derive(Default)] +struct CliArgs { + bytes_warning: u8, + dont_write_bytecode: bool, + debug: u8, + ignore_environment: bool, + inspect: bool, + isolate: bool, + optimize: u8, + safe_path: bool, + quiet: bool, + random_hash_seed: bool, + no_user_site: bool, + no_site: bool, + unbuffered: bool, + verbose: u8, + warning_control: Vec, + implementation_option: Vec, + check_hash_based_pycs: CheckHashPycsMode, + #[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() + profile_output: Option, + #[cfg(feature = "flame-it")] + profile_format: Option, +} + +const USAGE_STRING: &str = "\ +usage: {PROG} [option] ... [-c cmd | -m mod | file | -] [arg] ... +Options (and corresponding environment variables): +-b : issue warnings about converting bytes/bytearray to str and comparing + bytes/bytearray with str or bytes with int. (-bb: issue errors) +-B : don't write .pyc files on import; also PYTHONDONTWRITEBYTECODE=x +-c cmd : program passed in as string (terminates option list) +-d : turn on parser debugging output (for experts only, only works on + debug builds); also PYTHONDEBUG=x +-E : ignore PYTHON* environment variables (such as PYTHONPATH) +-h : print this help message and exit (also -? or --help) +-i : inspect interactively after running script; forces a prompt even + if stdin does not appear to be a terminal; also PYTHONINSPECT=x +-I : isolate Python from the user's environment (implies -E and -s) +-m mod : run library module as a script (terminates option list) +-O : remove assert and __debug__-dependent statements; add .opt-1 before + .pyc extension; also PYTHONOPTIMIZE=x +-OO : do -O changes and also discard docstrings; add .opt-2 before + .pyc extension +-P : don't prepend a potentially unsafe path to sys.path; also + PYTHONSAFEPATH +-q : don't print version and copyright messages on interactive startup +-s : don't add user site directory to sys.path; also PYTHONNOUSERSITE=x +-S : don't imply 'import site' on initialization +-u : force the stdout and stderr streams to be unbuffered; + this option has no effect on stdin; also PYTHONUNBUFFERED=x +-v : verbose (trace import statements); also PYTHONVERBOSE=x + can be supplied multiple times to increase verbosity +-V : print the Python version number and exit (also --version) + when given twice, print more information about the build +-W arg : warning control; arg is action:message:category:module:lineno + also PYTHONWARNINGS=arg +-x : skip first line of source, allowing use of non-Unix forms of #!cmd +-X opt : set implementation-specific option +--check-hash-based-pycs always|default|never: + control how Python invalidates hash-based .pyc files +--help-env: print help about Python environment variables and exit +--help-xoptions: print help about implementation-specific -X options and exit +--help-all: print complete help information and exit + +RustPython extensions: + + +Arguments: +file : program read from script file +- : program read from stdin (default; interactive mode if a tty) +arg ...: arguments passed to program in sys.argv[1:] +"; + +fn parse_args() -> Result<(CliArgs, RunMode, Vec), lexopt::Error> { + let mut args = CliArgs::default(); + let mut parser = lexopt::Parser::from_env(); + fn argv(argv0: String, mut parser: lexopt::Parser) -> Result, lexopt::Error> { + std::iter::once(Ok(argv0)) + .chain(parser.raw_args()?.map(|arg| arg.string())) + .collect() + } + while let Some(arg) = parser.next()? { + match arg { + Short('b') => args.bytes_warning += 1, + Short('B') => args.dont_write_bytecode = true, + Short('c') => { + let cmd = parser.value()?.string()?; + return Ok((args, RunMode::Command(cmd), argv("-c".to_owned(), parser)?)); + } + Short('d') => args.debug += 1, + Short('E') => args.ignore_environment = true, + Short('h' | '?') | Long("help") => help(parser), + Short('i') => args.inspect = true, + Short('I') => args.isolate = true, + Short('m') => { + let module = parser.value()?.string()?; + let argv = argv("PLACEHOLDER".to_owned(), parser)?; + return Ok((args, RunMode::Module(module), argv)); + } + Short('O') => args.optimize += 1, + Short('P') => args.safe_path = true, + Short('q') => args.quiet = true, + Short('R') => args.random_hash_seed = true, + Short('S') => args.no_site = true, + Short('s') => args.no_user_site = true, + Short('u') => args.unbuffered = true, + Short('v') => args.verbose += 1, + Short('V') | Long("version") => version(), + Short('W') => args.warning_control.push(parser.value()?.string()?), + // TODO: Short('x') => + Short('X') => args.implementation_option.push(parser.value()?.string()?), + + Long("check-hash-based-pycs") => { + args.check_hash_based_pycs = parser.value()?.parse()? + } + + // TODO: make these more specific + Long("help-env") => help(parser), + Long("help-xoptions") => help(parser), + Long("help-all") => help(parser), + + #[cfg(feature = "flame-it")] + Long("profile-output") => args.profile_output = Some(parser.value()?), + #[cfg(feature = "flame-it")] + Long("profile-format") => args.profile_format = Some(parser.value()?.string()?), + + Long("install-pip") => { + let (mode, argv) = if let Some(val) = parser.optional_value() { + (val.parse()?, vec![val.string()?]) + } else if let Ok(argv0) = parser.value() { + let mode = argv0.parse()?; + (mode, argv(argv0.string()?, parser)?) + } else { + ( + InstallPipMode::Ensurepip, + ["ensurepip", "--upgrade", "--default-pip"] + .map(str::to_owned) + .into(), + ) + }; + return Ok((args, RunMode::InstallPip(mode), argv)); + } + Value(script_name) => { + let script_name = script_name.string()?; + let mode = if script_name == "-" { + RunMode::Repl + } else { + RunMode::Script(script_name.clone()) + }; + return Ok((args, mode, argv(script_name, parser)?)); + } + _ => return Err(arg.unexpected()), + } + } + Ok((args, RunMode::Repl, vec![])) +} + +fn help(parser: lexopt::Parser) -> ! { + let usage = USAGE_STRING.replace("{PROG}", parser.bin_name().unwrap_or("rustpython")); + print!("{usage}"); + std::process::exit(0); +} + +fn version() -> ! { + println!("Python {}", rustpython_vm::version::get_version()); + std::process::exit(0); } /// Create settings by examining command line arguments and environment /// variables. -fn settings_from(matches: &ArgMatches) -> (Settings, RunMode) { +pub fn parse_opts() -> Result<(Settings, RunMode), lexopt::Error> { + let (args, mode, argv) = parse_args()?; + let mut settings = Settings::default(); - settings.isolated = matches.is_present("isolate"); - let ignore_environment = settings.isolated || matches.is_present("ignore-environment"); - settings.ignore_environment = ignore_environment; - settings.interactive = !matches.is_present("c") - && !matches.is_present("m") - && (!matches.is_present("script") || matches.is_present("inspect")); - settings.bytes_warning = matches.occurrences_of("bytes-warning"); - settings.import_site = !matches.is_present("no-site"); + settings.isolated = args.isolate; + settings.ignore_environment = settings.isolated || args.ignore_environment; + settings.bytes_warning = args.bytes_warning.into(); + settings.import_site = !args.no_site; + + let ignore_environment = settings.ignore_environment; if !ignore_environment { settings.path_list.extend(get_paths("RUSTPYTHONPATH")); @@ -209,34 +219,32 @@ fn settings_from(matches: &ArgMatches) -> (Settings, RunMode) { // Now process command line flags: - let count_flag = |arg, env| { - let mut val = matches.occurrences_of(arg) as u8; - if !ignore_environment { - if let Some(value) = get_env_var_value(env) { - val = std::cmp::max(val, value); - } - } - val + let get_env = |env| (!ignore_environment).then(|| env::var_os(env)).flatten(); + + let env_count = |env| { + get_env(env).filter(|v| !v.is_empty()).map_or(0, |val| { + val.to_str().and_then(|v| v.parse::().ok()).unwrap_or(1) + }) }; - settings.optimize = count_flag("optimize", "PYTHONOPTIMIZE"); - settings.verbose = count_flag("verbose", "PYTHONVERBOSE"); - settings.debug = count_flag("debug", "PYTHONDEBUG"); + settings.optimize = cmp::max(args.optimize, env_count("PYTHONOPTIMIZE")); + settings.verbose = cmp::max(args.verbose, env_count("PYTHONVERBOSE")); + settings.debug = cmp::max(args.debug, env_count("PYTHONDEBUG")); - let bool_env_var = |env| !ignore_environment && env::var_os(env).is_some_and(|v| !v.is_empty()); - let bool_flag = |arg, env| matches.is_present(arg) || bool_env_var(env); + let env_bool = |env| get_env(env).is_some_and(|v| !v.is_empty()); settings.user_site_directory = - !(settings.isolated || bool_flag("no-user-site", "PYTHONNOUSERSITE")); - settings.quiet = matches.is_present("quiet"); - settings.write_bytecode = !bool_flag("dont-write-bytecode", "PYTHONDONTWRITEBYTECODE"); - settings.safe_path = settings.isolated || bool_flag("safe-path", "PYTHONSAFEPATH"); - settings.inspect = bool_flag("inspect", "PYTHONINSPECT"); - settings.buffered_stdio = !bool_flag("unbuffered", "PYTHONUNBUFFERED"); + !(settings.isolated || args.no_user_site || env_bool("PYTHONNOUSERSITE")); + settings.quiet = args.quiet; + settings.write_bytecode = !(args.dont_write_bytecode || env_bool("PYTHONDONTWRITEBYTECODE")); + settings.safe_path = settings.isolated || args.safe_path || env_bool("PYTHONSAFEPATH"); + settings.inspect = args.inspect || env_bool("PYTHONINSPECT"); + settings.interactive = args.inspect; + settings.buffered_stdio = !args.unbuffered; - if !ignore_environment && env::var_os("PYTHONINTMAXSTRDIGITS").is_some() { - settings.int_max_str_digits = match env::var("PYTHONINTMAXSTRDIGITS").unwrap().parse() { - Ok(digits @ (0 | 640..)) => digits, + if let Some(val) = get_env("PYTHONINTMAXSTRDIGITS") { + settings.int_max_str_digits = match val.to_str().and_then(|s| s.parse().ok()) { + Some(digits @ (0 | 640..)) => digits, _ => { error!( "Fatal Python error: config_init_int_max_str_digits: PYTHONINTMAXSTRDIGITS: invalid limit; must be >= 640 or 0 for unlimited.\nPython runtime state: preinitialized" @@ -246,42 +254,39 @@ fn settings_from(matches: &ArgMatches) -> (Settings, RunMode) { }; } - settings.check_hash_pycs_mode = matches - .value_of("check-hash-based-pycs") - .map(|val| val.parse().unwrap()) - .unwrap_or_default(); + settings.check_hash_pycs_mode = args.check_hash_based_pycs; - let xopts = matches - .values_of("implementation-option") - .unwrap_or_default() - .map(|s| { - let (name, value) = s.split_once('=').unzip(); - let name = name.unwrap_or(s); - match name { - "dev" => settings.dev_mode = true, - "warn_default_encoding" => settings.warn_default_encoding = true, - "no_sig_int" => settings.install_signal_handlers = false, - "int_max_str_digits" => { - settings.int_max_str_digits = match value.unwrap().parse() { - Ok(digits) if digits == 0 || digits >= 640 => digits, - _ => { - error!( - "Fatal Python error: config_init_int_max_str_digits: \ - -X int_max_str_digits: \ - invalid limit; must be >= 640 or 0 for unlimited.\n\ - Python runtime state: preinitialized" - ); - std::process::exit(1); - } - }; - } - _ => {} + let xopts = args.implementation_option.into_iter().map(|s| { + let (name, value) = match s.split_once('=') { + Some((name, value)) => (name.to_owned(), Some(value)), + None => (s, None), + }; + match &*name { + "dev" => settings.dev_mode = true, + "warn_default_encoding" => settings.warn_default_encoding = true, + "no_sig_int" => settings.install_signal_handlers = false, + "int_max_str_digits" => { + settings.int_max_str_digits = match value.unwrap().parse() { + Ok(digits) if digits == 0 || digits >= 640 => digits, + _ => { + error!( + "Fatal Python error: config_init_int_max_str_digits: \ + -X int_max_str_digits: \ + invalid limit; must be >= 640 or 0 for unlimited.\n\ + Python runtime state: preinitialized" + ); + std::process::exit(1); + } + }; } - (name.to_owned(), value.map(str::to_owned)) - }); + _ => {} + } + (name, value.map(str::to_owned)) + }); settings.xoptions.extend(xopts); - settings.warn_default_encoding |= bool_env_var("PYTHONWARNDEFAULTENCODING"); + settings.warn_default_encoding = + settings.warn_default_encoding || env_bool("PYTHONWARNDEFAULTENCODING"); if settings.dev_mode { settings.warnoptions.push("default".to_owned()) @@ -294,66 +299,34 @@ fn settings_from(matches: &ArgMatches) -> (Settings, RunMode) { }; settings.warnoptions.push(warn.to_owned()); } - if let Some(warnings) = matches.values_of("warning-control") { - settings.warnoptions.extend(warnings.map(ToOwned::to_owned)); - } + settings.warnoptions.extend(args.warning_control); - let (mode, argv) = if let Some(mut cmd) = matches.values_of("c") { - let command = cmd.next().expect("clap ensure this exists"); - let argv = std::iter::once("-c".to_owned()) - .chain(cmd.map(ToOwned::to_owned)) - .collect(); - (RunMode::Command(command.to_owned()), argv) - } else if let Some(mut cmd) = matches.values_of("m") { - let module = cmd.next().expect("clap ensure this exists"); - let argv = std::iter::once("PLACEHOLDER".to_owned()) - .chain(cmd.map(ToOwned::to_owned)) - .collect(); - (RunMode::Module(module.to_owned()), argv) - } else if let Some(get_pip_args) = matches.values_of("install_pip") { - settings.isolated = true; - let mut args: Vec<_> = get_pip_args.map(ToOwned::to_owned).collect(); - if args.is_empty() { - args.extend(["ensurepip", "--upgrade", "--default-pip"].map(str::to_owned)); + settings.hash_seed = match (!args.random_hash_seed) + .then(|| get_env("PYTHONHASHSEED")) + .flatten() + { + Some(s) if s == "random" || s == "" => None, + Some(s) => { + let seed = s.parse_with(|s| { + s.parse::().map_err(|_| { + "Fatal Python init error: PYTHONHASHSEED must be \ + \"random\" or an integer in range [0; 4294967295]" + }) + })?; + Some(seed) } - let mode = match &*args[0] { - "ensurepip" => InstallPipMode::Ensurepip, - "get-pip" => InstallPipMode::GetPip, - _ => panic!("--install-pip takes ensurepip or get-pip as first argument"), - }; - (RunMode::InstallPip(mode), args) - } else if let Some(argv) = matches.values_of("script") { - let argv: Vec<_> = argv.map(ToOwned::to_owned).collect(); - let script = argv[0].clone(); - (RunMode::Script(script), argv) - } else { - (RunMode::Repl, vec!["".to_owned()]) + None => None, }; - 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]"); - // TODO: Need to change to ExitCode or Termination - std::process::exit(1) - }); - settings.argv = argv; - (settings, mode) -} + #[cfg(feature = "flame-it")] + { + settings.profile_output = args.profile_output; + settings.profile_format = args.profile_format; + } -/// Get environment variable and turn it into integer. -fn get_env_var_value(name: &str) -> Option { - env::var_os(name).filter(|v| !v.is_empty()).map(|value| { - value - .to_str() - .and_then(|v| v.parse::().ok()) - .unwrap_or(1) - }) + Ok((settings, mode)) } /// Helper function to retrieve a sequence of paths from an environment variable. diff --git a/vm/src/version.rs b/vm/src/version.rs index 8c42866a6..f2ac2354f 100644 --- a/vm/src/version.rs +++ b/vm/src/version.rs @@ -17,9 +17,10 @@ pub const VERSION_HEX: usize = pub fn get_version() -> String { format!( - "{:.80} ({:.80}) \n[{:.80}]", // \n is PyPy convention + "{:.80} ({:.80}) \n[RustPython {} with {:.80}]", // \n is PyPy convention get_version_number(), get_build_info(), + env!("CARGO_PKG_VERSION"), get_compiler() ) }