Files
RustPython/src/settings.rs
Junho Lee d4be55c2ea Add command line parameter -P (#4611)
* Add command line parameter -P

* Modify the value of safe_path to be set

---------

Co-authored-by: Jeong YunWon <jeong@youknowone.org>
2023-08-30 19:32:27 +09:00

397 lines
14 KiB
Rust
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
use clap::{App, AppSettings, Arg, ArgMatches};
use rustpython_vm::Settings;
use std::{env, str::FromStr};
pub enum RunMode {
ScriptInteractive(Option<String>, bool),
Command(String),
Module(String),
InstallPip(String),
}
pub fn opts_with_clap() -> (Settings, RunMode) {
let app = App::new("RustPython");
let matches = parse_arguments(app);
settings_from(&matches)
}
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").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("safe-path")
.short("P")
.help("don’t prepend a potentially unsafe path to sys.path"),
)
.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)
.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)
.default_value("default")
.help("always|default|never\ncontrol 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")
.help(
"force the stdout and stderr streams to be unbuffered; \
this option has no effect on stdin; also PYTHONUNBUFFERED=x",
),
);
#[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 settings_from(matches: &ArgMatches) -> (Settings, RunMode) {
let mut settings = Settings::default();
settings.isolated = matches.is_present("isolate");
settings.ignore_environment = matches.is_present("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.no_site = matches.is_present("no-site");
let ignore_environment = settings.ignore_environment || settings.isolated;
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;
}
}
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;
}
if !ignore_environment && env::var_os("PYTHONINTMAXSTRDIGITS").is_some() {
settings.int_max_str_digits = match env::var("PYTHONINTMAXSTRDIGITS").unwrap().parse() {
Ok(digits) if digits == 0 || digits >= 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");
std::process::exit(1);
}
};
}
if matches.is_present("safe-path")
|| (!ignore_environment && env::var_os("PYTHONSAFEPATH").is_some())
{
settings.safe_path = true;
}
settings.check_hash_based_pycs = matches
.value_of("check-hash-based-pycs")
.unwrap_or("default")
.to_owned();
let mut dev_mode = false;
let mut warn_default_encoding = 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();
let value = parts.next().map(ToOwned::to_owned);
if name == "dev" {
dev_mode = true
}
if name == "warn_default_encoding" {
warn_default_encoding = true
}
if name == "no_sig_int" {
settings.no_sig_int = true;
}
if name == "int_max_str_digits" {
settings.int_max_str_digits = match value.as_ref().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.\nPython runtime state: preinitialized");
std::process::exit(1);
},
};
}
(name, value)
}));
}
settings.dev_mode = dev_mode;
if warn_default_encoding
|| (!ignore_environment && env::var_os("PYTHONWARNDEFAULTENCODING").is_some())
{
settings.warn_default_encoding = true;
}
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 (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.push("ensurepip".to_owned());
args.push("--upgrade".to_owned());
args.push("--default-pip".to_owned());
}
let installer = args[0].clone();
let mode = match installer.as_str() {
"ensurepip" | "get-pip" => RunMode::InstallPip(installer),
_ => panic!("--install-pip takes ensurepip or get-pip as first argument"),
};
(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::ScriptInteractive(Some(script), matches.is_present("inspect")),
argv,
)
} else {
(RunMode::ScriptInteractive(None, true), vec!["".to_owned()])
};
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)
}
/// Get environment variable and turn it into integer.
fn get_env_var_value(name: &str) -> Result<u8, std::env::VarError> {
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<Item = String> + '_ {
env::var_os(env_variable_name)
.into_iter()
.flat_map(move |paths| {
split_paths(&paths)
.map(|path| {
path.into_os_string()
.into_string()
.unwrap_or_else(|_| panic!("{env_variable_name} isn't valid unicode"))
})
.collect::<Vec<_>>()
})
}
#[cfg(not(target_os = "wasi"))]
pub(crate) use env::split_paths;
#[cfg(target_os = "wasi")]
pub(crate) fn split_paths<T: AsRef<std::ffi::OsStr> + ?Sized>(
s: &T,
) -> impl Iterator<Item = std::path::PathBuf> + '_ {
use std::os::wasi::ffi::OsStrExt;
let s = s.as_ref().as_bytes();
s.split(|b| *b == b':')
.map(|x| std::ffi::OsStr::from_bytes(x).to_owned().into())
}