forked from Rust-related/RustPython
Merge pull request #4147 from youknowone/interpreter-config
Interpreter config
This commit is contained in:
@@ -6,10 +6,10 @@
|
||||
// so build.rs sets this env var
|
||||
pub const LIB_PATH: &str = match option_env!("win_lib_path") {
|
||||
Some(s) => s,
|
||||
None => concat!(env!("CARGO_MANIFEST_DIR"), "/../Lib"),
|
||||
None => concat!(env!("CARGO_MANIFEST_DIR"), "/Lib"),
|
||||
};
|
||||
|
||||
#[cfg(feature = "freeze-stdlib")]
|
||||
pub fn frozen_stdlib() -> impl Iterator<Item = (String, rustpython_compiler_core::FrozenModule)> {
|
||||
rustpython_derive::py_freeze!(dir = "../Lib", crate_name = "rustpython_compiler_core")
|
||||
rustpython_derive::py_freeze!(dir = "./Lib", crate_name = "rustpython_compiler_core")
|
||||
}
|
||||
|
||||
71
src/interpreter.rs
Normal file
71
src/interpreter.rs
Normal file
@@ -0,0 +1,71 @@
|
||||
use rustpython_vm::{Interpreter, Settings, VirtualMachine};
|
||||
|
||||
pub type InitHook = Box<dyn FnOnce(&mut VirtualMachine)>;
|
||||
|
||||
#[derive(Default)]
|
||||
pub struct InterpreterConfig {
|
||||
settings: Option<Settings>,
|
||||
init_hooks: Vec<InitHook>,
|
||||
}
|
||||
|
||||
impl InterpreterConfig {
|
||||
pub fn new() -> Self {
|
||||
Self::default()
|
||||
}
|
||||
pub fn interpreter(self) -> Interpreter {
|
||||
let settings = self.settings.unwrap_or_default();
|
||||
Interpreter::with_init(settings, |vm| {
|
||||
for hook in self.init_hooks {
|
||||
hook(vm);
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
pub fn settings(mut self, settings: Settings) -> Self {
|
||||
self.settings = Some(settings);
|
||||
self
|
||||
}
|
||||
pub fn init_hook(mut self, hook: InitHook) -> Self {
|
||||
self.init_hooks.push(hook);
|
||||
self
|
||||
}
|
||||
#[cfg(feature = "stdlib")]
|
||||
pub fn init_stdlib(self) -> Self {
|
||||
self.init_hook(Box::new(init_stdlib))
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "stdlib")]
|
||||
pub fn init_stdlib(vm: &mut VirtualMachine) {
|
||||
vm.add_native_modules(rustpython_stdlib::get_module_inits());
|
||||
|
||||
// if we're on freeze-stdlib, the core stdlib modules will be included anyway
|
||||
#[cfg(feature = "freeze-stdlib")]
|
||||
vm.add_frozen(rustpython_pylib::frozen_stdlib());
|
||||
|
||||
#[cfg(not(feature = "freeze-stdlib"))]
|
||||
{
|
||||
use rustpython_vm::common::rc::PyRc;
|
||||
|
||||
let state = PyRc::get_mut(&mut vm.state).unwrap();
|
||||
let settings = &mut state.settings;
|
||||
|
||||
#[allow(clippy::needless_collect)] // false positive
|
||||
let path_list: Vec<_> = settings.path_list.drain(..).collect();
|
||||
|
||||
// BUILDTIME_RUSTPYTHONPATH should be set when distributing
|
||||
if let Some(paths) = option_env!("BUILDTIME_RUSTPYTHONPATH") {
|
||||
settings.path_list.extend(
|
||||
crate::settings::split_paths(paths)
|
||||
.map(|path| path.into_os_string().into_string().unwrap()),
|
||||
)
|
||||
} else {
|
||||
#[cfg(feature = "rustpython-pylib")]
|
||||
settings
|
||||
.path_list
|
||||
.push(rustpython_pylib::LIB_PATH.to_owned())
|
||||
}
|
||||
|
||||
settings.path_list.extend(path_list.into_iter());
|
||||
}
|
||||
}
|
||||
465
src/lib.rs
465
src/lib.rs
@@ -43,27 +43,23 @@ extern crate env_logger;
|
||||
#[macro_use]
|
||||
extern crate log;
|
||||
|
||||
mod interpreter;
|
||||
mod settings;
|
||||
mod shell;
|
||||
|
||||
use clap::{App, AppSettings, Arg, ArgMatches};
|
||||
use rustpython_vm::{scope::Scope, Interpreter, PyResult, Settings, VirtualMachine};
|
||||
use std::{env, process::ExitCode, str::FromStr};
|
||||
use rustpython_vm::{scope::Scope, PyResult, VirtualMachine};
|
||||
use std::{env, process::ExitCode};
|
||||
|
||||
pub use interpreter::InterpreterConfig;
|
||||
pub use rustpython_vm as vm;
|
||||
pub use settings::{opts_with_clap, RunMode};
|
||||
|
||||
/// The main cli of the `rustpython` interpreter. This function will return with `std::process::ExitCode`
|
||||
/// based on the return code of the python code ran through the cli.
|
||||
pub fn run<F>(init: F) -> ExitCode
|
||||
where
|
||||
F: FnOnce(&mut VirtualMachine),
|
||||
{
|
||||
#[cfg(feature = "flame-it")]
|
||||
let main_guard = flame::start_guard("RustPython main");
|
||||
pub fn run(init: impl FnOnce(&mut VirtualMachine) + 'static) -> ExitCode {
|
||||
env_logger::init();
|
||||
let app = App::new("RustPython");
|
||||
let matches = parse_arguments(app);
|
||||
let matches = &matches;
|
||||
let settings = create_settings(matches);
|
||||
|
||||
let (settings, run_mode) = opts_with_clap();
|
||||
|
||||
// don't translate newlines (\r\n <=> \n)
|
||||
#[cfg(windows)]
|
||||
@@ -78,12 +74,18 @@ where
|
||||
}
|
||||
}
|
||||
|
||||
let interp = Interpreter::with_init(settings, |vm| {
|
||||
add_stdlib(vm);
|
||||
init(vm);
|
||||
});
|
||||
let mut config = InterpreterConfig::new().settings(settings);
|
||||
#[cfg(feature = "stdlib")]
|
||||
{
|
||||
config = config.init_stdlib();
|
||||
}
|
||||
config = config.init_hook(Box::new(init));
|
||||
let interp = config.interpreter();
|
||||
|
||||
let exitcode = interp.run(move |vm| run_rustpython(vm, matches));
|
||||
#[cfg(feature = "flame-it")]
|
||||
let main_guard = flame::start_guard("RustPython main");
|
||||
|
||||
let exitcode = interp.run(move |vm| run_rustpython(vm, run_mode));
|
||||
|
||||
#[cfg(feature = "flame-it")]
|
||||
{
|
||||
@@ -95,377 +97,6 @@ where
|
||||
ExitCode::from(exitcode)
|
||||
}
|
||||
|
||||
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("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()
|
||||
}
|
||||
|
||||
fn add_stdlib(vm: &mut VirtualMachine) {
|
||||
let _ = vm;
|
||||
#[cfg(feature = "stdlib")]
|
||||
vm.add_native_modules(rustpython_stdlib::get_module_inits());
|
||||
|
||||
// if we're on freeze-stdlib, the core stdlib modules will be included anyway
|
||||
#[cfg(feature = "freeze-stdlib")]
|
||||
vm.add_frozen(rustpython_pylib::frozen_stdlib());
|
||||
|
||||
#[cfg(not(feature = "freeze-stdlib"))]
|
||||
{
|
||||
use rustpython_vm::common::rc::PyRc;
|
||||
let state = PyRc::get_mut(&mut vm.state).unwrap();
|
||||
|
||||
#[allow(clippy::needless_collect)] // false positive
|
||||
let path_list: Vec<_> = state.settings.path_list.drain(..).collect();
|
||||
|
||||
// BUILDTIME_RUSTPYTHONPATH should be set when distributing
|
||||
if let Some(paths) = option_env!("BUILDTIME_RUSTPYTHONPATH") {
|
||||
state
|
||||
.settings
|
||||
.path_list
|
||||
.extend(split_paths(paths).map(|path| path.into_os_string().into_string().unwrap()))
|
||||
} else {
|
||||
#[cfg(feature = "rustpython-pylib")]
|
||||
state
|
||||
.settings
|
||||
.path_list
|
||||
.push(rustpython_pylib::LIB_PATH.to_owned())
|
||||
}
|
||||
|
||||
state.settings.path_list.extend(path_list.into_iter());
|
||||
}
|
||||
}
|
||||
|
||||
/// Create settings by examining command line arguments and environment
|
||||
/// variables.
|
||||
fn create_settings(matches: &ArgMatches) -> Settings {
|
||||
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;
|
||||
}
|
||||
|
||||
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();
|
||||
if name == "dev" {
|
||||
dev_mode = true
|
||||
}
|
||||
if name == "warn_default_encoding" {
|
||||
warn_default_encoding = true
|
||||
}
|
||||
let value = parts.next().map(ToOwned::to_owned);
|
||||
(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 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(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());
|
||||
}
|
||||
match args.first().map(String::as_str) {
|
||||
Some("ensurepip") | Some("get-pip") => (),
|
||||
_ => panic!("--install-pip takes ensurepip or get-pip as first argument"),
|
||||
}
|
||||
args
|
||||
} 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::<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
|
||||
}
|
||||
|
||||
/// 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!("{} isn't valid unicode", env_variable_name))
|
||||
})
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
}
|
||||
#[cfg(not(target_os = "wasi"))]
|
||||
use env::split_paths;
|
||||
#[cfg(target_os = "wasi")]
|
||||
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())
|
||||
}
|
||||
|
||||
#[cfg(feature = "flame-it")]
|
||||
fn write_profile(matches: &ArgMatches) -> Result<(), Box<dyn std::error::Error>> {
|
||||
use std::{fs, io};
|
||||
@@ -557,10 +188,10 @@ fn ensurepip(_: Scope, vm: &VirtualMachine) -> PyResult<()> {
|
||||
vm.run_module("ensurepip")
|
||||
}
|
||||
|
||||
fn install_pip(_scope: Scope, vm: &VirtualMachine) -> PyResult<()> {
|
||||
fn install_pip(_installer: &str, _scope: Scope, vm: &VirtualMachine) -> PyResult<()> {
|
||||
#[cfg(feature = "ssl")]
|
||||
{
|
||||
match vm.state.settings.argv[0].as_str() {
|
||||
match _installer {
|
||||
"ensurepip" => ensurepip(_scope, vm),
|
||||
"get-pip" => get_pip(_scope, vm),
|
||||
_ => unreachable!(),
|
||||
@@ -574,7 +205,7 @@ fn install_pip(_scope: Scope, vm: &VirtualMachine) -> PyResult<()> {
|
||||
))
|
||||
}
|
||||
|
||||
fn run_rustpython(vm: &VirtualMachine, matches: &ArgMatches) -> PyResult<()> {
|
||||
fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> {
|
||||
let scope = setup_main_module(vm)?;
|
||||
|
||||
let site_result = vm.import("site", None, 0);
|
||||
@@ -586,27 +217,32 @@ fn run_rustpython(vm: &VirtualMachine, matches: &ArgMatches) -> PyResult<()> {
|
||||
);
|
||||
}
|
||||
|
||||
// Figure out if a -c option was given:
|
||||
if let Some(command) = matches.value_of("c") {
|
||||
debug!("Running command {}", command);
|
||||
vm.run_code_string(scope, command, "<stdin>".to_owned())?;
|
||||
} else if let Some(module) = matches.value_of("m") {
|
||||
debug!("Running module {}", module);
|
||||
vm.run_module(module)?;
|
||||
} else if matches.is_present("install_pip") {
|
||||
install_pip(scope, vm)?;
|
||||
} else if let Some(filename) = matches.value_of("script") {
|
||||
debug!("Running file {}", filename);
|
||||
vm.run_script(scope.clone(), filename)?;
|
||||
if matches.is_present("inspect") {
|
||||
shell::run_shell(vm, scope)?;
|
||||
match run_mode {
|
||||
RunMode::Command(command) => {
|
||||
debug!("Running command {}", command);
|
||||
vm.run_code_string(scope, &command, "<stdin>".to_owned())?;
|
||||
}
|
||||
RunMode::Module(module) => {
|
||||
debug!("Running module {}", module);
|
||||
vm.run_module(&module)?;
|
||||
}
|
||||
RunMode::InstallPip(installer) => {
|
||||
install_pip(&installer, scope, vm)?;
|
||||
}
|
||||
RunMode::ScriptInteractive(script, interactive) => {
|
||||
if let Some(script) = script {
|
||||
debug!("Running script {}", &script);
|
||||
vm.run_script(scope.clone(), &script)?;
|
||||
} else {
|
||||
println!(
|
||||
"Welcome to the magnificent Rust Python {} interpreter \u{1f631} \u{1f596}",
|
||||
crate_version!()
|
||||
);
|
||||
}
|
||||
if interactive {
|
||||
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(())
|
||||
@@ -615,11 +251,10 @@ fn run_rustpython(vm: &VirtualMachine, matches: &ArgMatches) -> PyResult<()> {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rustpython_vm::Interpreter;
|
||||
|
||||
fn interpreter() -> Interpreter {
|
||||
Interpreter::with_init(Settings::default(), |vm| {
|
||||
add_stdlib(vm);
|
||||
})
|
||||
InterpreterConfig::new().init_stdlib().interpreter()
|
||||
}
|
||||
|
||||
#[test]
|
||||
|
||||
363
src/settings.rs
Normal file
363
src/settings.rs
Normal file
@@ -0,0 +1,363 @@
|
||||
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("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;
|
||||
}
|
||||
|
||||
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();
|
||||
if name == "dev" {
|
||||
dev_mode = true
|
||||
}
|
||||
if name == "warn_default_encoding" {
|
||||
warn_default_encoding = true
|
||||
}
|
||||
let value = parts.next().map(ToOwned::to_owned);
|
||||
(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!("{} isn't valid unicode", env_variable_name))
|
||||
})
|
||||
.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())
|
||||
}
|
||||
@@ -11,9 +11,8 @@ edition = "2021"
|
||||
crate-type = ["cdylib", "rlib"]
|
||||
|
||||
[features]
|
||||
default = ["stdlib"]
|
||||
stdlib = ["freeze-stdlib", "rustpython-pylib", "rustpython-stdlib"]
|
||||
freeze-stdlib = ["rustpython-vm/freeze-stdlib", "rustpython-pylib?/freeze-stdlib"]
|
||||
default = ["freeze-stdlib"]
|
||||
freeze-stdlib = ["rustpython-vm/freeze-stdlib", "rustpython-pylib/freeze-stdlib", "rustpython-stdlib"]
|
||||
no-start-func = []
|
||||
|
||||
[dependencies]
|
||||
|
||||
Reference in New Issue
Block a user