Use lexopt for argument parsing (#5602)

This commit is contained in:
Noa authored and GitHub committed 2025-03-19 12:28:58 -05:00
1 parent bd94d8d50c
commit a596568151
8 files changed
+326 -392

No files matched your search

Generated
+7 -26
View File
@@ -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"
+1 -1
View File
@@ -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 }
-8
View File
@@ -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")
+40 -46
View File
@@ -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} <scripts...> [-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::<compiler::Mode>().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(
+7 -17
View File
@@ -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,
+8 -5
View File
@@ -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)]
+261 -288
View File
@@ -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<Self, Self::Err> {
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<String>,
implementation_option: Vec<String>,
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<std::ffi::OsString>,
#[cfg(feature = "flame-it")]
profile_format: Option<String>,
}
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<String>), lexopt::Error> {
let mut args = CliArgs::default();
let mut parser = lexopt::Parser::from_env();
fn argv(argv0: String, mut parser: lexopt::Parser) -> Result<Vec<String>, 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::<u8>().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::<u32>().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::<u32>().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<u8> {
env::var_os(name).filter(|v| !v.is_empty()).map(|value| {
value
.to_str()
.and_then(|v| v.parse::<u8>().ok())
.unwrap_or(1)
})
Ok((settings, mode))
}
/// Helper function to retrieve a sequence of paths from an environment variable.
+2 -1
View File
@@ -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()
)
}