(args: &[String], cwd: &Path)
| 835 | fn catch_unwind_silent<T>(f: impl FnOnce() -> T) -> Result<T, Box<dyn std::any::Any + Send>> { |
| 836 | if env::var_os("PERRO_FORMAT_DEBUG_PANIC").is_some() { |
| 837 | return panic::catch_unwind(AssertUnwindSafe(f)); |
| 838 | } |
| 839 | let _guard = PANIC_HOOK_LOCK.lock().expect("panic hook lock poisoned"); |
| 840 | let hook = panic::take_hook(); |
| 841 | panic::set_hook(Box::new(|_| {})); |
| 842 | let result = panic::catch_unwind(AssertUnwindSafe(f)); |
| 843 | panic::set_hook(hook); |
| 844 | result |
| 845 | } |
| 846 | |
| 847 | fn scene_parse_error_detail(src: &str, err: Box<dyn std::any::Any + Send>) -> String { |
| 848 | let msg = panic_payload_to_string(&err); |
| 849 | if let Some(hint) = scene_syntax_hint(src) { |
| 850 | return format!("{msg}; {hint}"); |
| 851 | } |
| 852 | msg |
| 853 | } |
| 854 | |
| 855 | fn panic_payload_to_string(err: &(dyn std::any::Any + Send)) -> String { |
| 856 | if let Some(msg) = err.downcast_ref::<String>() { |
| 857 | return msg.clone(); |
| 858 | } |
| 859 | if let Some(msg) = err.downcast_ref::<&'static str>() { |
| 860 | return (*msg).to_string(); |
| 861 | } |
| 862 | "unknown parser error".to_string() |
| 863 | } |
| 864 | |
| 865 | fn scene_syntax_hint(src: &str) -> Option<String> { |
| 866 | for (idx, line) in src.lines().enumerate() { |
| 867 | let trimmed = line.trim(); |
| 868 | if trimmed.starts_with('/') && trimmed.ends_with(']') { |
| 869 | return Some(format!( |
| 870 | "line {} looks like missing `[` before closing tag: `{trimmed}`", |
| 871 | idx + 1 |
| 872 | )); |
| 873 | } |
| 874 | } |
| 875 | None |
no test coverage detected