mirror of
https://github.com/RustPython/RustPython.git
synced 2026-10-28 10:44:36 +08:00
Use lexopt for argument parsing (#5602)
This commit is contained in:
8 files changed
+326
-392
No files matched your search
Generated
+7
-26
@@ -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
@@ -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 }
|
||||
|
||||
Vendored
-8
@@ -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
@@ -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(
|
||||
|
||||
@@ -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
@@ -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
@@ -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
@@ -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()
|
||||
)
|
||||
}
|
||||
|
||||
Reference in new issue
Block a user