// yt - A fully featured command line YouTube client // // Copyright (C) 2025 Benedikt Peetz // SPDX-License-Identifier: GPL-3.0-or-later // // This file is part of Yt. // // You should have received a copy of the License along with this program. // If not, see . use std::fmt::Display; use log::{Level, debug, log_enabled}; use rustpython::vm::{ AsObject, PyPayload, PyRef, VirtualMachine, builtins::{PyBaseException, PyBaseExceptionRef, PyStr}, py_io::Write, suggestion::offer_suggestions, }; #[derive(thiserror::Error, Debug)] pub struct PythonError(pub String); impl Display for PythonError { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "Python threw an exception: {}", self.0) } } impl PythonError { pub(super) fn from_exception(vm: &VirtualMachine, exc: &PyRef) -> Self { let buffer = process_exception(vm, exc); Self(buffer) } } pub(super) fn process_exception(vm: &VirtualMachine, err: &PyBaseExceptionRef) -> String { let mut buffer = String::new(); write_exception(vm, &mut buffer, err) .expect("We are writing into an *in-memory* string, it will always work"); if log_enabled!(Level::Debug) { let mut output = String::new(); vm.write_exception(&mut output, err) .expect("We are writing into an *in-memory* string, it will always work"); debug!("Python threw an exception: {output}"); } buffer } // Inlined and changed from `vm.write_exception_inner` fn write_exception( vm: &VirtualMachine, output: &mut W, exc: &PyBaseExceptionRef, ) -> Result<(), W::Error> { let varargs = exc.args(); let args_repr = { match varargs.len() { 0 => vec![], 1 => { let args0_repr = if true { varargs[0] .str(vm) .unwrap_or_else(|_| PyStr::from("").into_ref(&vm.ctx)) } else { varargs[0].repr(vm).unwrap_or_else(|_| { PyStr::from("").into_ref(&vm.ctx) }) }; vec![args0_repr] } _ => varargs .iter() .map(|vararg| { vararg.repr(vm).unwrap_or_else(|_| { PyStr::from("").into_ref(&vm.ctx) }) }) .collect(), } }; let exc_class = exc.class(); if exc_class.fast_issubclass(vm.ctx.exceptions.syntax_error) { unreachable!( "A syntax error should never be raised, \ as yt_dlp should not have them and neither our embedded code" ); } let exc_name = exc_class.name(); match args_repr.len() { 0 => write!(output, "{exc_name}"), 1 => write!(output, "{}: {}", exc_name, args_repr[0]), _ => write!( output, "{}: ({})", exc_name, args_repr .iter() .map(|val| val.as_str()) .collect::>() .join(", "), ), }?; match offer_suggestions(exc, vm) { Some(suggestions) => { write!(output, ". Did you mean: '{suggestions}'?") } None => Ok(()), } }