Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 0 additions & 1 deletion Lib/test/test_cmd_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -294,7 +294,6 @@ def test_coding(self):
# command line, but how subprocess does decode bytes to unicode. Python
# doesn't decode the command line because Windows provides directly the
# arguments as unicode (using wmain() instead of main()).
@unittest.expectedFailure # TODO: RUSTPYTHON
@unittest.skipIf(sys.platform == 'win32',
'Windows has a native unicode API')
def test_undecodable_code(self):
Expand Down
42 changes: 40 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -399,8 +399,8 @@ fn pymain_run_main(vm: &VirtualMachine, scope: Scope, run_mode: RunMode) -> PyRe
vm.check_signals()?;
let result = match run_mode {
RunMode::Command(command) => {
debug!("Running command {command}");
vm.run_string(scope, &command, "<string>").map(|_| 0)
debug!("Running command {command:?}");
run_command(vm, scope, command)
}
RunMode::Module(module) => {
debug!("Running module {module}");
Expand All @@ -421,6 +421,44 @@ fn pymain_run_main(vm: &VirtualMachine, scope: Scope, run_mode: RunMode) -> PyRe
Ok(exitcode)
}

fn run_command(
vm: &VirtualMachine,
scope: Scope,
mut command: std::ffi::OsString,
) -> PyResult<u32> {
// CPython appends a newline before auditing and executing -c.
command.push("\n");
let command = vm.fsdecode(command);
// Audit failures do not receive the command-decoding error prefix.
vm.audit("cpython.run_command", || (command.clone(),))?;

let Some(source) = command.to_str() else {
// CPython's dedent first requests strict UTF-8, so report positions
// in the original command, before removing indentation. These are
// Unicode character indices, not offsets into its WTF-8 bytes.
let mut points = command.as_wtf8().code_points();
let start = points
.position(|point| point.to_char().is_none())
.ok_or_else(|| vm.new_runtime_error("non-UTF-8 command has no surrogate"))?;
let end = start + 1 + points.take_while(|point| point.to_char().is_none()).count();
let exc = vm.new_unicode_encode_error(
vm.ctx.new_str("utf-8"),
command,
start,
end,
vm.ctx.new_str("surrogates not allowed"),
);
writeln!(
vm::stdlib::sys::PyStderr(vm),
"Unable to decode the command from the command line:"
);
return Err(exc);
};

let source = settings::dedent(source);
vm.run_string(scope, &source, "<string>").map(|_| 0)
}

/// pymain_err_print
///
/// Report `exc` and return the exit code if it ends the process.
Expand Down
12 changes: 4 additions & 8 deletions src/settings.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use std::ffi::OsString;

pub enum RunMode {
Script(String),
Command(String),
Command(OsString),
Module(String),
InstallPip(InstallPipMode),
Repl,
Expand Down Expand Up @@ -128,12 +128,8 @@ fn parse_args() -> Result<(CliArgs, RunMode, Vec<OsString>), lexopt::Error> {
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(dedent(&cmd)),
argv("-c".to_owned(), parser)?,
));
let cmd = parser.value()?;
return Ok((args, RunMode::Command(cmd), argv("-c".to_owned(), parser)?));
}
Short('d') => args.debug += 1,
Short('E') => args.ignore_environment = true,
Expand Down Expand Up @@ -647,7 +643,7 @@ pub(crate) fn split_paths<T: AsRef<std::ffi::OsStr> + ?Sized>(
/// This is like textwrap.dedent, and is used to process -c's code
/// argument. It's different from ruff's dedent, which does not
/// distinguish between tab and space characters when dedenting.
fn dedent(input: &str) -> String {
pub(crate) fn dedent(input: &str) -> String {
let mut prefix: Option<String> = None;
let isspace = |c| c == ' ' || c == '\t';

Expand Down
Loading