Merge pull request #5442 from coolreader18/cli-fixes

Miscellaneous cli-related parity fixes
This commit is contained in:
Noa
2024-12-03 23:57:57 -06:00
committed by GitHub
20 changed files with 229 additions and 239 deletions

2
Cargo.lock generated
View File

@@ -1876,7 +1876,6 @@ dependencies = [
name = "rustpython"
version = "0.4.0"
dependencies = [
"atty",
"cfg-if",
"clap",
"criterion",
@@ -2194,7 +2193,6 @@ version = "0.4.0"
dependencies = [
"ahash",
"ascii",
"atty",
"bitflags 2.6.0",
"bstr",
"caseless",

View File

@@ -31,7 +31,6 @@ rustpython-stdlib = { workspace = true, optional = true, features = ["compiler"]
rustpython-vm = { workspace = true, features = ["compiler"] }
rustpython-parser = { workspace = true }
atty = { workspace = true }
cfg-if = { workspace = true }
log = { workspace = true }
flame = { workspace = true, optional = true }
@@ -141,7 +140,6 @@ rustpython-format= { version = "0.4.0" }
ahash = "0.8.11"
ascii = "1.0"
atty = "0.2.14"
bitflags = "2.4.1"
bstr = "1"
cfg-if = "1.0"

View File

@@ -845,9 +845,7 @@ class LeapdaysTestCase(unittest.TestCase):
def conv(s):
# XXX RUSTPYTHON TODO: TextIOWrapper newline translation
return s.encode()
# return s.replace('\n', os.linesep).encode()
return s.replace('\n', os.linesep).encode()
class CommandLineTestCase(unittest.TestCase):
def run_ok(self, *args):

View File

@@ -177,8 +177,6 @@ class CmdLineTest(unittest.TestCase):
# All good if module is located and run successfully
assert_python_ok('-m', 'timeit', '-n', '1')
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_run_module_bug1764407(self):
# -m and -i need to play well together
# Runs the timeit module and checks the __main__
@@ -335,8 +333,6 @@ class CmdLineTest(unittest.TestCase):
self.assertEqual(stdout, expected)
self.assertEqual(p.returncode, 0)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_non_interactive_output_buffering(self):
code = textwrap.dedent("""
import sys
@@ -352,8 +348,6 @@ class CmdLineTest(unittest.TestCase):
'False False False\n'
'False False True\n')
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_unbuffered_output(self):
# Test expected operation of the '-u' switch
for stream in ('stdout', 'stderr'):
@@ -447,8 +441,6 @@ class CmdLineTest(unittest.TestCase):
stdout, stderr = proc.communicate()
self.assertEqual(stdout.rstrip(), expected)
# TODO: RUSTPYTHON
@unittest.skipIf(sys.platform.startswith('win'), "TODO: RUSTPYTHON windows has \n troubles")
def test_stdin_readline(self):
# Issue #11272: check that sys.stdin.readline() replaces '\r\n' by '\n'
# on Windows (sys.stdin is opened in binary mode)
@@ -456,16 +448,12 @@ class CmdLineTest(unittest.TestCase):
"import sys; print(repr(sys.stdin.readline()))",
b"'abc\\n'")
# TODO: RUSTPYTHON
@unittest.skipIf(sys.platform.startswith('win'), "TODO: RUSTPYTHON windows has \n troubles")
def test_builtin_input(self):
# Issue #11272: check that input() strips newlines ('\n' or '\r\n')
self.check_input(
"print(repr(input()))",
b"'abc'")
# TODO: RUSTPYTHON
@unittest.skipIf(sys.platform.startswith('win'), "TODO: RUSTPYTHON windows has \n troubles")
def test_output_newline(self):
# Issue 13119 Newline for print() should be \r\n on Windows.
code = """if 1:
@@ -632,8 +620,6 @@ class CmdLineTest(unittest.TestCase):
self.assertEqual(err.splitlines().count(b'Unknown option: -a'), 1)
self.assertEqual(b'', out)
# TODO: RUSTPYTHON
@unittest.expectedFailure
@unittest.skipIf(interpreter_requires_environment(),
'Cannot run -I tests when PYTHON env vars are required.')
def test_isolatedmode(self):
@@ -662,8 +648,6 @@ class CmdLineTest(unittest.TestCase):
cwd=tmpdir)
self.assertEqual(out.strip(), b"ok")
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_sys_flags_set(self):
# Issue 31845: a startup refactoring broke reading flags from env vars
for value, expected in (("", 0), ("1", 1), ("text", 1), ("2", 2)):

View File

@@ -725,7 +725,6 @@ class TestOpen(BaseTest):
with self.assertRaises(ValueError):
gzip.open(self.filename, "rb", newline="\n")
@unittest.expectedFailureIfWindows("TODO: RUSTPYTHON")
def test_encoding(self):
# Test non-default encoding.
uncompressed = data1.decode("ascii") * 50

View File

@@ -616,8 +616,6 @@ class CGIHTTPServerTestCase(BaseTestCase):
pass
linesep = os.linesep.encode('ascii')
# TODO: RUSTPYTHON
linesep = b'\n'
def setUp(self):
BaseTestCase.setUp(self)

View File

@@ -92,8 +92,6 @@ class TestInteractiveInterpreter(unittest.TestCase):
output = kill_python(p)
self.assertEqual(p.returncode, 0)
# TODO: RUSTPYTHON
@unittest.expectedFailure
def test_close_stdin(self):
user_input = dedent('''
import os

View File

@@ -16,7 +16,7 @@ pub type InitHook = Box<dyn FnOnce(&mut VirtualMachine)>;
/// use rustpython_vm::Settings;
/// // Override your settings here.
/// let mut settings = Settings::default();
/// settings.debug = true;
/// settings.debug = 1;
/// // You may want to add paths to `rustpython_vm::Settings::path_list` to allow import python libraries.
/// settings.path_list.push("".to_owned()); // add current working directory
/// let interpreter = rustpython::InterpreterConfig::new()

View File

@@ -50,13 +50,14 @@ mod interpreter;
mod settings;
mod shell;
use atty::Stream;
use rustpython_vm::{scope::Scope, PyResult, VirtualMachine};
use std::{env, process::ExitCode};
use std::env;
use std::io::IsTerminal;
use std::process::ExitCode;
pub use interpreter::InterpreterConfig;
pub use rustpython_vm as vm;
pub use settings::{opts_with_clap, RunMode};
pub use settings::{opts_with_clap, InstallPipMode, RunMode};
/// The main cli of the `rustpython` interpreter. This function will return `std::process::ExitCode`
/// based on the return code of the python code ran through the cli.
@@ -73,9 +74,6 @@ pub fn run(init: impl FnOnce(&mut VirtualMachine) + 'static) -> ExitCode {
let (settings, run_mode) = opts_with_clap();
// Be quiet if "quiet" arg is set OR stdin is not connected to a terminal
let quiet_var = settings.quiet || !atty::is(Stream::Stdin);
// don't translate newlines (\r\n <=> \n)
#[cfg(windows)]
{
@@ -97,7 +95,7 @@ pub fn run(init: impl FnOnce(&mut VirtualMachine) + 'static) -> ExitCode {
config = config.init_hook(Box::new(init));
let interp = config.interpreter();
let exitcode = interp.run(move |vm| run_rustpython(vm, run_mode, quiet_var));
let exitcode = interp.run(move |vm| run_rustpython(vm, run_mode));
ExitCode::from(exitcode)
}
@@ -117,7 +115,6 @@ fn setup_main_module(vm: &VirtualMachine) -> PyResult<Scope> {
Ok(scope)
}
#[cfg(feature = "ssl")]
fn get_pip(scope: Scope, vm: &VirtualMachine) -> PyResult<()> {
let get_getpip = rustpython_vm::py_compile!(
source = r#"\
@@ -128,7 +125,7 @@ __import__("io").TextIOWrapper(
mode = "eval"
);
eprintln!("downloading get-pip.py...");
let getpip_code = vm.run_code_obj(vm.ctx.new_code(get_getpip), scope.clone())?;
let getpip_code = vm.run_code_obj(vm.ctx.new_code(get_getpip), vm.new_scope_with_builtins())?;
let getpip_code: rustpython_vm::builtins::PyStrRef = getpip_code
.downcast()
.expect("TextIOWrapper.read() should return str");
@@ -137,29 +134,21 @@ __import__("io").TextIOWrapper(
Ok(())
}
#[cfg(feature = "ssl")]
fn ensurepip(_: Scope, vm: &VirtualMachine) -> PyResult<()> {
vm.run_module("ensurepip")
}
fn install_pip(_installer: &str, _scope: Scope, vm: &VirtualMachine) -> PyResult<()> {
#[cfg(feature = "ssl")]
{
match _installer {
"ensurepip" => ensurepip(_scope, vm),
"get-pip" => get_pip(_scope, vm),
_ => unreachable!(),
}
fn install_pip(installer: InstallPipMode, scope: Scope, vm: &VirtualMachine) -> PyResult<()> {
if cfg!(not(feature = "ssl")) {
return Err(vm.new_exception_msg(
vm.ctx.exceptions.system_error.to_owned(),
"install-pip requires rustpython be build with '--features=ssl'".to_owned(),
));
}
#[cfg(not(feature = "ssl"))]
Err(vm.new_exception_msg(
vm.ctx.exceptions.system_error.to_owned(),
"install-pip requires rustpython be build with '--features=ssl'".to_owned(),
))
match installer {
InstallPipMode::Ensurepip => vm.run_module("ensurepip"),
InstallPipMode::GetPip => get_pip(scope, vm),
}
}
fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode, quiet: bool) -> PyResult<()> {
fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode) -> PyResult<()> {
#[cfg(feature = "flame-it")]
let main_guard = flame::start_guard("RustPython main");
@@ -183,33 +172,46 @@ fn run_rustpython(vm: &VirtualMachine, run_mode: RunMode, quiet: bool) -> PyResu
);
}
match run_mode {
let is_repl = matches!(run_mode, RunMode::Repl);
if !vm.state.settings.quiet
&& (vm.state.settings.verbose > 0 || (is_repl && std::io::stdin().is_terminal()))
{
eprintln!(
"Welcome to the magnificent Rust Python {} interpreter \u{1f631} \u{1f596}",
env!("CARGO_PKG_VERSION")
);
eprintln!(
"RustPython {}.{}.{}",
vm::version::MAJOR,
vm::version::MINOR,
vm::version::MICRO,
);
eprintln!("Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.");
}
let res = match run_mode {
RunMode::Command(command) => {
debug!("Running command {}", command);
vm.run_code_string(scope, &command, "<stdin>".to_owned())?;
vm.run_code_string(scope.clone(), &command, "<stdin>".to_owned())
.map(drop)
}
RunMode::Module(module) => {
debug!("Running module {}", module);
vm.run_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 if !quiet {
println!(
"Welcome to the magnificent Rust Python {} interpreter \u{1f631} \u{1f596}",
crate_version!()
);
}
if interactive {
shell::run_shell(vm, scope)?;
}
RunMode::InstallPip(installer) => install_pip(installer, scope.clone(), vm),
RunMode::Script(script) => {
debug!("Running script {}", &script);
vm.run_script(scope.clone(), &script)
}
RunMode::Repl => Ok(()),
};
if is_repl || vm.state.settings.inspect {
shell::run_shell(vm, scope)?;
} else {
res?;
}
#[cfg(feature = "flame-it")]
{
main_guard.end();

View File

@@ -1,12 +1,18 @@
use clap::{App, AppSettings, Arg, ArgMatches};
use rustpython_vm::Settings;
use std::{env, str::FromStr};
use std::env;
pub enum RunMode {
ScriptInteractive(Option<String>, bool),
Script(String),
Command(String),
Module(String),
InstallPip(String),
InstallPip(InstallPipMode),
Repl,
}
pub enum InstallPipMode {
Ensurepip,
GetPip,
}
pub fn opts_with_clap() -> (Settings, RunMode) {
@@ -58,8 +64,9 @@ fn parse_arguments<'a>(app: App<'a, '_>) -> ArgMatches<'a> {
.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."
.help(
"install the pip package manager for rustpython; \
requires rustpython be build with the ssl feature enabled.",
),
)
.arg(
@@ -74,45 +81,58 @@ fn parse_arguments<'a>(app: App<'a, '_>) -> ArgMatches<'a> {
.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("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("dont 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(
@@ -136,22 +156,22 @@ fn parse_arguments<'a>(app: App<'a, '_>) -> ArgMatches<'a> {
.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"),
.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")
.help(
"force the stdout and stderr streams to be unbuffered; \
this option has no effect on stdin; also PYTHONUNBUFFERED=x",
"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",
));
#[cfg(feature = "flame-it")]
let app = app
.arg(
@@ -174,67 +194,49 @@ fn parse_arguments<'a>(app: App<'a, '_>) -> ArgMatches<'a> {
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");
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");
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;
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
};
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;
}
}
settings.optimize = count_flag("optimize", "PYTHONOPTIMIZE");
settings.verbose = count_flag("verbose", "PYTHONVERBOSE");
settings.debug = count_flag("debug", "PYTHONDEBUG");
if matches.is_present("no-user-site")
|| matches.is_present("isolate")
|| (!ignore_environment && env::var_os("PYTHONNOUSERSITE").is_some())
{
settings.user_site_directory = false;
}
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);
if matches.is_present("quiet") {
settings.quiet = true;
}
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");
if matches.is_present("dont-write-bytecode")
|| (!ignore_environment && env::var_os("PYTHONDONTWRITEBYTECODE").is_some())
{
settings.write_bytecode = false;
}
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,
Ok(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");
std::process::exit(1);
@@ -242,54 +244,44 @@ fn settings_from(matches: &ArgMatches) -> (Settings, RunMode) {
};
}
if matches.is_present("safe-path")
|| (!ignore_environment && env::var_os("PYTHONSAFEPATH").is_some())
{
settings.safe_path = true;
}
matches
settings.check_hash_pycs_mode = matches
.value_of("check-hash-based-pycs")
.unwrap_or("default")
.clone_into(&mut settings.check_hash_pycs_mode);
.map(|val| val.parse().unwrap())
.unwrap_or_default();
let mut dev_mode = false;
let mut warn_default_encoding = false;
if let Some(xopts) = matches.values_of("implementation-option") {
settings.xoptions.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
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);
}
};
}
_ => {}
}
if name == "warn_default_encoding" {
warn_default_encoding = true
}
if name == "no_sig_int" {
settings.install_signal_handlers = false;
}
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,
_ => {
(name.to_owned(), value.map(str::to_owned))
});
settings.xoptions.extend(xopts);
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;
}
settings.warn_default_encoding |= bool_env_var("PYTHONWARNDEFAULTENCODING");
if dev_mode {
if settings.dev_mode {
settings.warnoptions.push("default".to_owned())
}
if settings.bytes_warning > 0 {
@@ -320,25 +312,20 @@ fn settings_from(matches: &ArgMatches) -> (Settings, RunMode) {
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());
args.extend(["ensurepip", "--upgrade", "--default-pip"].map(str::to_owned));
}
let installer = args[0].clone();
let mode = match installer.as_str() {
"ensurepip" | "get-pip" => RunMode::InstallPip(installer),
let mode = match &*args[0] {
"ensurepip" => InstallPipMode::Ensurepip,
"get-pip" => InstallPipMode::GetPip,
_ => panic!("--install-pip takes ensurepip or get-pip as first argument"),
};
(mode, args)
(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::ScriptInteractive(Some(script), matches.is_present("inspect")),
argv,
)
(RunMode::Script(script), argv)
} else {
(RunMode::ScriptInteractive(None, true), vec!["".to_owned()])
(RunMode::Repl, vec!["".to_owned()])
};
let hash_seed = match env::var("PYTHONHASHSEED") {
@@ -358,8 +345,13 @@ fn settings_from(matches: &ArgMatches) -> (Settings, RunMode) {
}
/// 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| u8::from_str(&value).unwrap_or(1))
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)
})
}
/// Helper function to retrieve a sequence of paths from an environment variable.

View File

@@ -6,7 +6,7 @@ use rustpython_vm::{
compiler::{self, CompileError, CompileErrorType},
readline::{Readline, ReadlineResult},
scope::Scope,
version, AsObject, PyResult, VirtualMachine,
AsObject, PyResult, VirtualMachine,
};
enum ShellExecResult {
@@ -93,15 +93,6 @@ pub fn run_shell(vm: &VirtualMachine, scope: Scope) -> PyResult<()> {
let mut continuing = false;
println!(
"RustPython {}.{}.{}",
version::MAJOR,
version::MINOR,
version::MICRO,
);
println!("Type \"help\", \"copyright\", \"credits\" or \"license\" for more information.");
loop {
let prompt_name = if continuing { "ps2" } else { "ps1" };
let prompt = vm

View File

@@ -42,7 +42,6 @@ rustpython-sre_engine = { workspace = true }
ascii = { workspace = true }
ahash = { workspace = true }
atty = { workspace = true }
bitflags = { workspace = true }
bstr = { workspace = true }
cfg-if = { workspace = true }
@@ -68,6 +67,8 @@ paste = { workspace = true }
rand = { workspace = true }
serde = { workspace = true, optional = true }
static_assertions = { workspace = true }
strum = { workspace = true }
strum_macros = { workspace = true }
thiserror = { workspace = true }
thread_local = { workspace = true }
memchr = { workspace = true }
@@ -94,8 +95,6 @@ unic-ucd-ident = "0.9.0"
rustix = { workspace = true }
exitcode = "1.1.2"
uname = "0.1.1"
strum = { workspace = true }
strum_macros = { workspace = true }
[target.'cfg(not(target_arch = "wasm32"))'.dependencies]
rustyline = { workspace = true }

View File

@@ -54,6 +54,7 @@ into_func_args_from_tuple!((v1, T1), (v2, T2));
into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3));
into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4));
into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5));
into_func_args_from_tuple!((v1, T1), (v2, T2), (v3, T3), (v4, T4), (v5, T5), (v6, T6));
/// The `FuncArgs` struct is one of the most used structs then creating
/// a rust function that can be called from python. It holds both positional

View File

@@ -7,6 +7,8 @@ pub use builtins::{ascii, print, reversed};
#[pymodule]
mod builtins {
use std::io::IsTerminal;
use crate::{
builtins::{
enumerate::PyReverseSequenceIterator,
@@ -432,7 +434,7 @@ mod builtins {
};
// everything is normalish, we can just rely on rustyline to use stdin/stdout
if fd_matches(&stdin, 0) && fd_matches(&stdout, 1) && atty::is(atty::Stream::Stdin) {
if fd_matches(&stdin, 0) && fd_matches(&stdout, 1) && std::io::stdin().is_terminal() {
let prompt = prompt.as_ref().map_or("", |s| s.as_str());
let mut readline = Readline::new(());
match readline.readline(prompt) {

View File

@@ -88,7 +88,7 @@ mod _imp {
#[pyattr]
fn check_hash_based_pycs(vm: &VirtualMachine) -> PyStrRef {
vm.ctx
.new_str(vm.state.settings.check_hash_pycs_mode.clone())
.new_str(vm.state.settings.check_hash_pycs_mode.to_string())
}
#[pyfunction]

View File

@@ -15,7 +15,7 @@ use crate::{
convert::{IntoPyException, ToPyException},
PyObjectRef, PyRef, PyResult, TryFromObject, VirtualMachine,
};
pub use _io::io_open as open;
pub use _io::{io_open as open, OpenArgs};
impl ToPyException for std::io::Error {
fn to_pyexception(&self, vm: &VirtualMachine) -> PyBaseExceptionRef {
@@ -2750,15 +2750,13 @@ mod _io {
let data = obj.as_str();
let replace_nl = match textio.newline {
Newlines::Lf => Some("\n"),
Newlines::Cr => Some("\r"),
Newlines::Crlf => Some("\r\n"),
Newlines::Universal if cfg!(windows) => Some("\r\n"),
_ => None,
};
let has_lf = if replace_nl.is_some() || textio.line_buffering {
data.contains('\n')
} else {
false
};
let has_lf = (replace_nl.is_some() || textio.line_buffering) && data.contains('\n');
let flush = textio.line_buffering && (has_lf || data.contains('\r'));
let chunk = if let Some(replace_nl) = replace_nl {
if has_lf {
@@ -3822,17 +3820,17 @@ mod _io {
#[derive(FromArgs)]
pub struct OpenArgs {
#[pyarg(any, default = "-1")]
buffering: isize,
pub buffering: isize,
#[pyarg(any, default)]
encoding: Option<PyStrRef>,
pub encoding: Option<PyStrRef>,
#[pyarg(any, default)]
errors: Option<PyStrRef>,
pub errors: Option<PyStrRef>,
#[pyarg(any, default)]
newline: Option<PyStrRef>,
pub newline: Option<PyStrRef>,
#[pyarg(any, default = "true")]
closefd: bool,
pub closefd: bool,
#[pyarg(any, default)]
opener: Option<PyObjectRef>,
pub opener: Option<PyObjectRef>,
}
impl Default for OpenArgs {
fn default() -> Self {

View File

@@ -823,7 +823,7 @@ mod sys {
impl Flags {
fn from_settings(settings: &Settings) -> Self {
Self {
debug: settings.debug as u8,
debug: settings.debug,
inspect: settings.inspect as u8,
interactive: settings.interactive as u8,
optimize: settings.optimize,

View File

@@ -35,12 +35,14 @@ impl VirtualMachine {
return Ok(());
}
let dir = std::path::Path::new(path)
.parent()
.unwrap()
.to_str()
.unwrap();
self.insert_sys_path(self.new_pyobj(dir))?;
if !self.state.settings.safe_path {
let dir = std::path::Path::new(path)
.parent()
.unwrap()
.to_str()
.unwrap();
self.insert_sys_path(self.new_pyobj(dir))?;
}
match std::fs::read_to_string(path) {
Ok(source) => {

View File

@@ -51,7 +51,7 @@ use std::{
pub use context::Context;
pub use interpreter::Interpreter;
pub(crate) use method::PyMethod;
pub use setting::Settings;
pub use setting::{CheckHashPycsMode, Settings};
pub const MAX_MEMORY_SIZE: usize = isize::MAX as usize;
@@ -301,17 +301,39 @@ impl VirtualMachine {
#[cfg(any(not(target_arch = "wasm32"), target_os = "wasi"))]
{
// this isn't fully compatible with CPython; it imports "io" and sets
// builtins.open to io.OpenWrapper, but this is easier, since it doesn't
// require the Python stdlib to be present
let io = import::import_builtin(self, "_io")?;
let set_stdio = |name, fd, mode: &str| {
let stdio = crate::stdlib::io::open(
let set_stdio = |name, fd, write| {
let buffered_stdio = self.state.settings.buffered_stdio;
let unbuffered = write && !buffered_stdio;
let buf = crate::stdlib::io::open(
self.ctx.new_int(fd).into(),
Some(mode),
Default::default(),
Some(if write { "wb" } else { "rb" }),
crate::stdlib::io::OpenArgs {
buffering: if unbuffered { 0 } else { -1 },
..Default::default()
},
self,
)?;
let raw = if unbuffered {
buf.clone()
} else {
buf.get_attr("raw", self)?
};
raw.set_attr("name", self.ctx.new_str(format!("<{name}>")), self)?;
let isatty = self.call_method(&raw, "isatty", ())?.is_true(self)?;
let write_through = !buffered_stdio;
let line_buffering = buffered_stdio && (isatty || fd == 2);
let newline = if cfg!(windows) { None } else { Some("\n") };
let stdio = self.call_method(
&io,
"TextIOWrapper",
(buf, (), (), newline, line_buffering, write_through),
)?;
let mode = if write { "w" } else { "r" };
stdio.set_attr("mode", self.ctx.new_str(mode), self)?;
let dunder_name = self.ctx.intern_str(format!("__{name}__"));
self.sys_module.set_attr(
dunder_name, // e.g. __stdin__
@@ -321,9 +343,9 @@ impl VirtualMachine {
self.sys_module.set_attr(name, stdio, self)?;
Ok(())
};
set_stdio("stdin", 0, "r")?;
set_stdio("stdout", 1, "w")?;
set_stdio("stderr", 2, "w")?;
set_stdio("stdin", 0, false)?;
set_stdio("stdout", 1, true)?;
set_stdio("stderr", 2, true)?;
let io_open = io.get_attr("open", self)?;
self.builtins.set_attr("open", io_open, self)?;

View File

@@ -73,14 +73,13 @@ pub struct Settings {
// int configure_c_stdio;
/// -u, PYTHONUNBUFFERED=x
// TODO: use this; can TextIOWrapper even work with a non-buffered?
pub buffered_stdio: bool,
// wchar_t *stdio_encoding;
pub utf8_mode: u8,
// wchar_t *stdio_errors;
/// --check-hash-based-pycs
pub check_hash_pycs_mode: String,
pub check_hash_pycs_mode: CheckHashPycsMode,
// int use_frozen_modules;
/// -P
@@ -98,7 +97,7 @@ pub struct Settings {
// wchar_t *home;
// wchar_t *platlibdir;
/// -d command line switch
pub debug: bool,
pub debug: u8,
/// -O optimization switch counter
pub optimize: u8,
@@ -115,6 +114,15 @@ pub struct Settings {
pub profile_format: Option<String>,
}
#[derive(Debug, Default, Copy, Clone, strum_macros::Display, strum_macros::EnumString)]
#[strum(serialize_all = "lowercase")]
pub enum CheckHashPycsMode {
#[default]
Default,
Always,
Never,
}
impl Settings {
pub fn with_path(mut self, path: String) -> Self {
self.path_list.push(path);
@@ -126,7 +134,7 @@ impl Settings {
impl Default for Settings {
fn default() -> Self {
Settings {
debug: false,
debug: 0,
inspect: false,
interactive: false,
optimize: 0,
@@ -148,7 +156,7 @@ impl Default for Settings {
argv: vec![],
hash_seed: None,
buffered_stdio: true,
check_hash_pycs_mode: "default".to_owned(),
check_hash_pycs_mode: CheckHashPycsMode::Default,
allow_external_library: cfg!(feature = "importlib"),
utf8_mode: 1,
int_max_str_digits: 4300,