diff --git a/cli/benches/benchmark.rs b/cli/benches/benchmark.rs index 627b7a95..ef67bd21 100644 --- a/cli/benches/benchmark.rs +++ b/cli/benches/benchmark.rs @@ -4,6 +4,7 @@ use std::path::{Path, PathBuf}; use std::time::Instant; use std::{env, fs, usize}; use tree_sitter::{Language, Parser}; +use tree_sitter_cli::error::Error; use tree_sitter_cli::loader::Loader; include!("../src/tests/helpers/dirs.rs"); @@ -153,7 +154,7 @@ fn parse(parser: &mut Parser, example_path: &Path, max_path_length: usize) -> us ); let source_code = fs::read(example_path) - .map_err(|e| format!("Failed to read {:?} - {}", example_path, e)) + .map_err(Error::wrap(|| format!("Failed to read {:?}", example_path))) .unwrap(); let time = Instant::now(); let _tree = parser @@ -171,6 +172,8 @@ fn get_language(path: &Path) -> Language { let src_dir = GRAMMARS_DIR.join(path).join("src"); TEST_LOADER .load_language_at_path(&src_dir, &src_dir) - .map_err(|e| format!("Failed to load language at path {:?} - {:?}", src_dir, e)) + .map_err(Error::wrap(|| { + format!("Failed to load language at path {:?}", src_dir) + })) .unwrap() } diff --git a/cli/src/error.rs b/cli/src/error.rs index b0e52797..968486f4 100644 --- a/cli/src/error.rs +++ b/cli/src/error.rs @@ -1,46 +1,82 @@ +use std::fmt::Write; use std::io; use tree_sitter_highlight::PropertySheetError; #[derive(Debug)] -pub struct Error(pub String); +pub struct Error(pub Vec); pub type Result = std::result::Result; impl Error { pub fn grammar(message: &str) -> Self { - Error(format!("Grammar error: {}", message)) + Error(vec![format!("Grammar error: {}", message)]) } pub fn regex(message: &str) -> Self { - Error(format!("Regex error: {}", message)) + Error(vec![format!("Regex error: {}", message)]) } pub fn undefined_symbol(name: &str) -> Self { - Error(format!("Undefined symbol `{}`", name)) + Error(vec![format!("Undefined symbol `{}`", name)]) + } + + pub fn new(message: String) -> Self { + Error(vec![message]) + } + + pub fn err(message: String) -> Result { + Err(Error::new(message)) + } + + pub fn wrap, M: ToString, F: FnOnce() -> M>( + message_fn: F, + ) -> impl FnOnce(E) -> Self { + |e| { + let mut result = e.into(); + result.0.push(message_fn().to_string()); + result + } + } + + pub fn message(&self) -> String { + let mut result = self.0.last().unwrap().clone(); + if self.0.len() > 1 { + result.push_str("\nDetails:\n"); + for msg in self.0[0..self.0.len() - 1].iter().rev() { + writeln!(&mut result, " {}", msg).unwrap(); + } + } + result } } impl From for Error { fn from(error: serde_json::Error) -> Self { - Error(error.to_string()) + Error::new(error.to_string()) } } impl From for Error { fn from(error: io::Error) -> Self { - Error(error.to_string()) + Error::new(error.to_string()) } } impl From for Error { fn from(error: rsass::Error) -> Self { - Error(error.to_string()) + Error::new(error.to_string()) + } +} + +impl From for Error { + fn from(error: regex_syntax::ast::Error) -> Self { + Error::new(error.to_string()) } } impl From for Error { fn from(error: String) -> Self { - Error(error) + Error::new(error) } } diff --git a/cli/src/generate/build_tables/build_parse_table.rs b/cli/src/generate/build_tables/build_parse_table.rs index b9e48bf3..e2c05146 100644 --- a/cli/src/generate/build_tables/build_parse_table.rs +++ b/cli/src/generate/build_tables/build_parse_table.rs @@ -602,7 +602,7 @@ impl<'a> ParseTableBuilder<'a> { } write!(&mut msg, "\n").unwrap(); - Err(Error(msg)) + Err(Error::new(msg)) } fn get_auxiliary_node_info( diff --git a/cli/src/generate/mod.rs b/cli/src/generate/mod.rs index 48e747c9..b779a004 100644 --- a/cli/src/generate/mod.rs +++ b/cli/src/generate/mod.rs @@ -64,20 +64,16 @@ pub fn generate_parser_in_directory( node_types_json, } = generate_parser_for_grammar_with_opts(&grammar_json, minimize, state_ids_to_log)?; - fs::write(&repo_src_path.join("parser.c"), c_code) - .map_err(|e| format!("Failed to write parser.c: {}", e))?; - fs::write(&repo_src_path.join("node-types.json"), node_types_json) - .map_err(|e| format!("Failed to write parser.c: {}", e))?; - fs::write( + write_file(&repo_src_path.join("parser.c"), c_code)?; + write_file(&repo_src_path.join("node-types.json"), node_types_json)?; + write_file( &repo_header_path.join("parser.h"), tree_sitter::PARSER_HEADER, - ) - .map_err(|e| format!("Failed to write parser.h: {}", e))?; - fs::write( + )?; + write_file( &repo_path.join("index.js"), npm_files::index_js(&language_name), - ) - .map_err(|e| format!("Failed to write index.js: {}", e))?; + )?; ensure_file(&repo_src_path.join("binding.cc"), || { npm_files::binding_cc(&language_name) })?; @@ -139,7 +135,7 @@ fn load_grammar_file(grammar_path: &Path) -> Result { match grammar_path.extension().and_then(|e| e.to_str()) { Some("js") => Ok(load_js_grammar_file(grammar_path)?), Some("json") => Ok(fs::read_to_string(grammar_path)?), - _ => Err(Error(format!( + _ => Err(Error::new(format!( "Unknown grammar file extension: {:?}", grammar_path ))), @@ -169,7 +165,7 @@ fn load_js_grammar_file(grammar_path: &Path) -> Result { match output.status.code() { None => panic!("Node process was killed"), Some(0) => {} - Some(code) => return Err(Error(format!("Node process exited with status {}", code))), + Some(code) => return Error::err(format!("Node process exited with status {}", code)), } let mut result = String::from_utf8(output.stdout).expect("Got invalid UTF8 from node"); @@ -177,11 +173,16 @@ fn load_js_grammar_file(grammar_path: &Path) -> Result { Ok(result) } +fn write_file(path: &Path, body: impl AsRef<[u8]>) -> Result<()> { + fs::write(path, body).map_err(Error::wrap(|| { + format!("Failed to write {:?}", path.file_name().unwrap()) + })) +} + fn ensure_file>(path: &PathBuf, f: impl Fn() -> T) -> Result<()> { if path.exists() { Ok(()) } else { - fs::write(path, f().as_ref()) - .map_err(|e| Error(format!("Failed to write file {:?}: {}", path, e))) + write_file(path, f().as_ref()) } } diff --git a/cli/src/generate/prepare_grammar/expand_tokens.rs b/cli/src/generate/prepare_grammar/expand_tokens.rs index 2f69fbdd..2b88762b 100644 --- a/cli/src/generate/prepare_grammar/expand_tokens.rs +++ b/cli/src/generate/prepare_grammar/expand_tokens.rs @@ -98,7 +98,9 @@ pub(crate) fn expand_tokens(mut grammar: ExtractedLexicalGrammar) -> Result { let s = preprocess_regex(s); - let ast = parse::Parser::new() - .parse(&s) - .map_err(|e| Error(e.to_string()))?; + let ast = parse::Parser::new().parse(&s)?; self.expand_regex(&ast, next_state_id) } Rule::String(s) => { diff --git a/cli/src/generate/prepare_grammar/extract_tokens.rs b/cli/src/generate/prepare_grammar/extract_tokens.rs index d1264b6e..01b4584c 100644 --- a/cli/src/generate/prepare_grammar/extract_tokens.rs +++ b/cli/src/generate/prepare_grammar/extract_tokens.rs @@ -95,10 +95,10 @@ pub(super) fn extract_tokens( if let Rule::Symbol(symbol) = rule { let new_symbol = symbol_replacer.replace_symbol(symbol); if new_symbol.is_non_terminal() { - return Err(Error(format!( + return Error::err(format!( "Non-token symbol '{}' cannot be used as an extra token", &variables[new_symbol.index].name - ))); + )); } else { extra_tokens.push(new_symbol); } @@ -116,10 +116,10 @@ pub(super) fn extract_tokens( let rule = symbol_replacer.replace_symbols_in_rule(&external_token.rule); if let Rule::Symbol(symbol) = rule { if symbol.is_non_terminal() { - return Err(Error(format!( + return Error::err(format!( "Rule '{}' cannot be used as both an external token and a non-terminal rule", &variables[symbol.index].name, - ))); + )); } if symbol.is_external() { @@ -136,9 +136,9 @@ pub(super) fn extract_tokens( }) } } else { - return Err(Error(format!( + return Error::err(format!( "Non-symbol rules cannot be used as external tokens" - ))); + )); } } @@ -146,10 +146,10 @@ pub(super) fn extract_tokens( if let Some(token) = grammar.word_token { let token = symbol_replacer.replace_symbol(token); if token.is_non_terminal() { - return Err(Error(format!( + return Error::err(format!( "Non-terminal symbol '{}' cannot be used as the word token", &variables[token.index].name - ))); + )); } word_token = Some(token); } @@ -482,9 +482,9 @@ mod test { grammar.extra_tokens = vec![Rule::non_terminal(1)]; match extract_tokens(grammar) { - Err(Error(s)) => { + Err(e) => { assert_eq!( - s, + e.message(), "Non-token symbol 'rule_1' cannot be used as an extra token" ); } @@ -510,8 +510,8 @@ mod test { grammar.external_tokens = vec![Variable::named("rule_1", Rule::non_terminal(1))]; match extract_tokens(grammar) { - Err(Error(s)) => { - assert_eq!(s, "Rule 'rule_1' cannot be used as both an external token and a non-terminal rule"); + Err(e) => { + assert_eq!(e.message(), "Rule 'rule_1' cannot be used as both an external token and a non-terminal rule"); } _ => { panic!("Expected an error but got no error"); diff --git a/cli/src/generate/prepare_grammar/flatten_grammar.rs b/cli/src/generate/prepare_grammar/flatten_grammar.rs index af93a82d..e325776c 100644 --- a/cli/src/generate/prepare_grammar/flatten_grammar.rs +++ b/cli/src/generate/prepare_grammar/flatten_grammar.rs @@ -187,14 +187,14 @@ pub(super) fn flatten_grammar(grammar: ExtractedSyntaxGrammar) -> Result Result let interner = Interner { grammar }; if variable_type_for_name(&grammar.variables[0].name) == VariableType::Hidden { - return Err(Error("A grammar's start rule must be visible.".to_string())); + return Error::err("A grammar's start rule must be visible.".to_string()); } let mut variables = Vec::with_capacity(grammar.variables.len()); @@ -227,7 +227,7 @@ mod tests { let result = intern_symbols(&build_grammar(vec![Variable::named("x", Rule::named("y"))])); match result { - Err(Error(message)) => assert_eq!(message, "Undefined symbol `y`"), + Err(e) => assert_eq!(e.message(), "Undefined symbol `y`"), _ => panic!("Expected an error but got none"), } } diff --git a/cli/src/highlight.rs b/cli/src/highlight.rs index 91e6b5b3..4bd0b19a 100644 --- a/cli/src/highlight.rs +++ b/cli/src/highlight.rs @@ -362,20 +362,22 @@ fn language_for_injection_string<'a>( string: &str, ) -> Option<(Language, &'a PropertySheet)> { match loader.language_configuration_for_injection_string(string) { - Err(message) => { + Err(e) => { eprintln!( "Failed to load language for injection string '{}': {}", - string, message.0 + string, + e.message() ); None } Ok(None) => None, Ok(Some((language, configuration))) => { match configuration.highlight_property_sheet(language) { - Err(message) => { + Err(e) => { eprintln!( "Failed to load property sheet for injection string '{}': {}", - string, message.0 + string, + e.message() ); None } diff --git a/cli/src/loader.rs b/cli/src/loader.rs index 3fed4428..34e0e164 100644 --- a/cli/src/loader.rs +++ b/cli/src/loader.rs @@ -165,8 +165,10 @@ impl Loader { struct GrammarJSON { name: String, } - let mut grammar_file = fs::File::open(grammar_path)?; - let grammar_json: GrammarJSON = serde_json::from_reader(BufReader::new(&mut grammar_file))?; + let mut grammar_file = + fs::File::open(grammar_path).map_err(Error::wrap(|| "Failed to read grammar.json"))?; + let grammar_json: GrammarJSON = serde_json::from_reader(BufReader::new(&mut grammar_file)) + .map_err(Error::wrap(|| "Failed to parse grammar.json"))?; let scanner_path = if scanner_path.exists() { Some(scanner_path) @@ -197,7 +199,11 @@ impl Loader { let mut library_path = self.parser_lib_path.join(name); library_path.set_extension(DYLIB_EXTENSION); - if needs_recompile(&library_path, &parser_path, &scanner_path)? { + let recompile = needs_recompile(&library_path, &parser_path, &scanner_path).map_err( + Error::wrap(|| "Failed to compare source and binary timestamps"), + )?; + + if recompile { let mut config = cc::Build::new(); config .cpp(true) @@ -244,9 +250,11 @@ impl Loader { command.arg("-xc").arg(parser_path); } - let output = command.output()?; + let output = command + .output() + .map_err(Error::wrap(|| "Failed to execute C compiler"))?; if !output.status.success() { - return Err(Error(format!( + return Err(Error::new(format!( "Parser compilation failed.\nStdout: {}\nStderr: {}", String::from_utf8_lossy(&output.stdout), String::from_utf8_lossy(&output.stderr) @@ -254,16 +262,16 @@ impl Loader { } } - let library = Library::new(&library_path).map_err(|e| { - Error(format!( - "Error opening dynamic library {:?}: {}", - &library_path, e - )) - })?; + let library = Library::new(&library_path).map_err(Error::wrap(|| { + format!("Error opening dynamic library {:?}", &library_path) + }))?; let language_fn_name = format!("tree_sitter_{}", replace_dashes_with_underscores(name)); let language = unsafe { - let language_fn: Symbol Language> = - library.get(language_fn_name.as_bytes())?; + let language_fn: Symbol Language> = library + .get(language_fn_name.as_bytes()) + .map_err(Error::wrap(|| { + format!("Failed to load symbol {}", language_fn_name) + }))?; language_fn() }; mem::forget(library); @@ -349,8 +357,19 @@ impl LanguageConfiguration { self.highlight_property_sheet .get_or_try_init(|| { if let Some(path) = &self.highlight_property_sheet_path { - let sheet_json = fs::read_to_string(path)?; - let sheet = load_property_sheet(language, &sheet_json)?; + let sheet_json = fs::read_to_string(path).map_err(Error::wrap(|| { + format!( + "Failed to read property sheet {:?}", + path.file_name().unwrap() + ) + }))?; + let sheet = + load_property_sheet(language, &sheet_json).map_err(Error::wrap(|| { + format!( + "Failed to parse property sheet {:?}", + path.file_name().unwrap() + ) + }))?; Ok(Some(sheet)) } else { Ok(None) diff --git a/cli/src/main.rs b/cli/src/main.rs index e98a1a00..12dbe44e 100644 --- a/cli/src/main.rs +++ b/cli/src/main.rs @@ -1,4 +1,5 @@ use clap::{App, AppSettings, Arg, SubCommand}; +use error::Error; use std::env; use std::fs; use std::path::Path; @@ -10,7 +11,7 @@ use tree_sitter_cli::{ fn main() { if let Err(e) = run() { - eprintln!("{}", e.0); + eprintln!("{}", e.message()); exit(1); } } @@ -174,15 +175,35 @@ fn run() -> error::Result<()> { for path in paths { let path = Path::new(path); let language = if let Some(scope) = matches.value_of("scope") { - if let Some(config) = loader.language_configuration_for_scope(scope)? { + if let Some(config) = + loader + .language_configuration_for_scope(scope) + .map_err(Error::wrap(|| { + format!("Failed to load language for scope '{}'", scope) + }))? + { config.0 } else { - return Err(error::Error(format!("Unknown scope '{}'", scope))); + return Error::err(format!("Unknown scope '{}'", scope)); } - } else if let Some((l, _)) = loader.language_configuration_for_file_name(path)? { - l - } else if let Some(l) = loader.language_at_path(¤t_dir)? { - l + } else if let Some((lang, _)) = loader + .language_configuration_for_file_name(path) + .map_err(Error::wrap(|| { + format!( + "Failed to load language for file name {:?}", + path.file_name().unwrap() + ) + }))? + { + lang + } else if let Some(lang) = + loader + .language_at_path(¤t_dir) + .map_err(Error::wrap(|| { + "Failed to load language in current directory" + }))? + { + lang } else { eprintln!("No language found"); return Ok(()); @@ -202,7 +223,7 @@ fn run() -> error::Result<()> { } if has_error { - return Err(error::Error(String::new())); + return Error::err(String::new()); } } else if let Some(matches) = matches.subcommand_matches("highlight") { let paths = matches.values_of("path").unwrap().into_iter(); @@ -218,7 +239,7 @@ fn run() -> error::Result<()> { if let Some(scope) = matches.value_of("scope") { language_config = loader.language_configuration_for_scope(scope)?; if language_config.is_none() { - return Err(error::Error(format!("Unknown scope '{}'", scope))); + return Error::err(format!("Unknown scope '{}'", scope)); } } else { language_config = None; @@ -245,9 +266,7 @@ fn run() -> error::Result<()> { highlight::ansi(&loader, &config.theme, &source, language, sheet, time)?; } } else { - return Err(error::Error(format!( - "No syntax highlighting property sheet specified" - ))); + return Error::err(format!("No syntax highlighting property sheet specified")); } } } else if let Some(matches) = matches.subcommand_matches("build-wasm") { diff --git a/cli/src/parse.rs b/cli/src/parse.rs index a8d411e1..0320c2b4 100644 --- a/cli/src/parse.rs +++ b/cli/src/parse.rs @@ -29,8 +29,9 @@ pub fn parse_file_at_path( let mut _log_session = None; let mut parser = Parser::new(); parser.set_language(language)?; - let mut source_code = fs::read(path) - .map_err(|e| Error(format!("Error reading source file {:?}: {}", path, e)))?; + let mut source_code = fs::read(path).map_err(Error::wrap(|| { + format!("Error reading source file {:?}", path) + }))?; // If the `--cancel` flag was passed, then cancel the parse // when the user types a newline. diff --git a/cli/src/properties.rs b/cli/src/properties.rs index 33489f4d..86d87592 100644 --- a/cli/src/properties.rs +++ b/cli/src/properties.rs @@ -461,8 +461,10 @@ pub fn generate_property_sheets_in_directory(repo_path: &Path) -> Result<()> { let property_sheet_json_path = src_dir_path .join(css_path.file_name().unwrap()) .with_extension("json"); - let property_sheet_json_file = File::create(&property_sheet_json_path) - .map_err(|e| format!("Failed to create {:?}: {}", property_sheet_json_path, e))?; + let property_sheet_json_file = + File::create(&property_sheet_json_path).map_err(Error::wrap(|| { + format!("Failed to create {:?}", property_sheet_json_path) + }))?; let mut writer = BufWriter::new(property_sheet_json_file); serde_json::to_writer_pretty(&mut writer, &sheet)?; } @@ -561,7 +563,7 @@ fn parse_sass_items( is_immediate: immediate, }); } else { - return Err(Error(format!("Node type {} must be separated by whitespace or the `>` operator", value))); + return Error::err(format!("Node type {} must be separated by whitespace or the `>` operator", value)); } } operator_was_immediate = None; @@ -576,7 +578,7 @@ fn parse_sass_items( None => return Err(interpolation_error()), Some("text") => { if operator_was_immediate.is_some() { - return Err(Error("The `text` attribute must be used in combination with a node type or field".to_string())); + return Error::err("The `text` attribute must be used in combination with a node type or field".to_string()); } if let Some(last_step) = prefix.last_mut() { last_step.text_pattern = @@ -595,21 +597,21 @@ fn parse_sass_items( }); operator_was_immediate = None; } else { - return Err(Error("The `token` attribute canot be used in combination with a node type".to_string())); + return Error::err("The `token` attribute canot be used in combination with a node type".to_string()); } } _ => { - return Err(Error(format!( + return Error::err(format!( "Unsupported attribute {}", part - ))); + )); } } } SelectorPart::PseudoElement { .. } => { - return Err(Error( + return Error::err( "Pseudo elements are not supported".to_string(), - )); + ); } SelectorPart::Pseudo { name, arg } => match name.single_raw() { None => return Err(interpolation_error()), @@ -621,19 +623,19 @@ fn parse_sass_items( if let Ok(i) = usize::from_str_radix(&arg_str, 10) { last_step.child_index = Some(i); } else { - return Err(Error(format!( + return Error::err(format!( "Invalid child index {}", arg - ))); + )); } } } } _ => { - return Err(Error(format!( + return Error::err(format!( "Unsupported pseudo-class {}", part - ))); + )); } }, SelectorPart::Descendant => { @@ -644,10 +646,10 @@ fn parse_sass_items( if operator == '>' { operator_was_immediate = Some(true); } else { - return Err(Error(format!( + return Error::err(format!( "Unsupported operator {}", operator - ))); + )); } } } @@ -657,7 +659,7 @@ fn parse_sass_items( } parse_sass_items(items, &full_selectors, result)?; } - _ => return Err(Error(format!("Unsupported syntax type {:?}", item))), + _ => return Error::err(format!("Unsupported syntax type {:?}", item)), } } @@ -687,7 +689,7 @@ fn process_at_rules( items.splice(i..(i + 1), imported_items); continue; } else { - return Err(Error("@import arguments must be strings".to_string())); + return Err(Error::new("@import arguments must be strings".to_string())); } } rsass::Item::AtRule { name, args, .. } => match name.as_str() { @@ -698,10 +700,10 @@ fn process_at_rules( items.remove(i); continue; } else { - return Err(Error("@schema arguments must be strings".to_string())); + return Error::err("@schema arguments must be strings".to_string()); } } - _ => return Err(Error(format!("Unsupported at-rule '{}'", name))), + _ => return Error::err(format!("Unsupported at-rule '{}'", name)), }, _ => {} } @@ -730,7 +732,9 @@ fn parse_sass_value(value: &Value) -> Result { result.insert("args".to_string(), PropertyValue::Array(args)); Ok(PropertyValue::Object(result)) } else { - Err(Error("String interpolation is not supported".to_string())) + Err(Error::new( + "String interpolation is not supported".to_string(), + )) } } Value::List(elements, ..) => { @@ -744,7 +748,7 @@ fn parse_sass_value(value: &Value) -> Result { Value::Numeric(n, _) => Ok(PropertyValue::Number(n.to_integer())), Value::True => Ok(PropertyValue::Boolean(true)), Value::False => Ok(PropertyValue::Boolean(false)), - _ => Err(Error(format!( + _ => Err(Error::new(format!( "Property values must be strings or function calls. Got {:?}", value ))), @@ -781,13 +785,13 @@ fn resolve_path(base: &Path, p: &str) -> Result { } } } - Err(Error(format!("Could not resolve import path `{}`", p))) + Err(Error::new(format!("Could not resolve import path `{}`", p))) } fn check_node_kind(name: &String) -> Result<()> { for c in name.chars() { if !c.is_alphanumeric() && c != '_' { - return Err(Error(format!("Invalid identifier '{}'", name))); + return Err(Error::new(format!("Invalid identifier '{}'", name))); } } Ok(()) @@ -799,12 +803,12 @@ fn get_string_value(mut s: String) -> Result { s.remove(0); Ok(s) } else { - Err(Error(format!("Unsupported string literal {}", s))) + Err(Error::new(format!("Unsupported string literal {}", s))) } } fn interpolation_error() -> Error { - Error("String interpolation is not supported".to_string()) + Error::new("String interpolation is not supported".to_string()) } #[cfg(test)] diff --git a/cli/src/test.rs b/cli/src/test.rs index 98404138..82166afb 100644 --- a/cli/src/test.rs +++ b/cli/src/test.rs @@ -92,7 +92,7 @@ pub fn run_tests_at_path( println!("\n {}. {}:", i + 1, name); print_diff(actual, expected); } - Err(Error(String::new())) + Error::err(String::new()) } else { Ok(()) } diff --git a/cli/src/tests/corpus_test.rs b/cli/src/tests/corpus_test.rs index 76d68820..19c90e19 100644 --- a/cli/src/tests/corpus_test.rs +++ b/cli/src/tests/corpus_test.rs @@ -224,10 +224,11 @@ fn test_feature_corpus_files() { let expected_message = fs::read_to_string(&error_message_path).unwrap(); if let Err(e) = generate_result { - if e.0 != expected_message { + if e.message() != expected_message { eprintln!( "Unexpected error message.\n\nExpected:\n\n{}\nActual:\n\n{}\n", - expected_message, e.0 + expected_message, + e.message() ); failure_count += 1; } diff --git a/cli/src/wasm.rs b/cli/src/wasm.rs index be24399d..af335625 100644 --- a/cli/src/wasm.rs +++ b/cli/src/wasm.rs @@ -7,18 +7,12 @@ use std::process::Command; pub fn get_grammar_name(src_dir: &Path) -> Result { let grammar_json_path = src_dir.join("grammar.json"); - let grammar_json = fs::read_to_string(&grammar_json_path).map_err(|e| { - format!( - "Failed to read grammar file {:?} - {}", - grammar_json_path, e - ) - })?; - let grammar: GrammarJSON = serde_json::from_str(&grammar_json).map_err(|e| { - format!( - "Failed to parse grammar file {:?} - {}", - grammar_json_path, e - ) - })?; + let grammar_json = fs::read_to_string(&grammar_json_path).map_err(Error::wrap(|| { + format!("Failed to read grammar file {:?}", grammar_json_path) + }))?; + let grammar: GrammarJSON = serde_json::from_str(&grammar_json).map_err(Error::wrap(|| { + format!("Failed to parse grammar file {:?}", grammar_json_path) + }))?; Ok(grammar.name) } @@ -55,7 +49,7 @@ pub fn compile_language_to_wasm(language_dir: &Path, force_docker: bool) -> Resu let user_id_output = Command::new("id") .arg("-u") .output() - .map_err(|e| format!("Failed to get get current user id {}", e))?; + .map_err(Error::wrap(|| "Failed to get get current user id {}"))?; let user_id = String::from_utf8_lossy(&user_id_output.stdout); let user_id = user_id.trim(); command.args(&["--user", user_id]); @@ -82,8 +76,9 @@ pub fn compile_language_to_wasm(language_dir: &Path, force_docker: bool) -> Resu ]); // Find source files to pass to emscripten - let src_entries = fs::read_dir(&src_dir) - .map_err(|e| format!("Failed to read source directory {:?} - {}", src_dir, e))?; + let src_entries = fs::read_dir(&src_dir).map_err(Error::wrap(|| { + format!("Failed to read source directory {:?}", src_dir) + }))?; for entry in src_entries { let entry = entry?; @@ -107,7 +102,7 @@ pub fn compile_language_to_wasm(language_dir: &Path, force_docker: bool) -> Resu let output = command .output() - .map_err(|e| format!("Failed to run emcc command - {}", e))?; + .map_err(Error::wrap(|| "Failed to run emcc command"))?; if !output.status.success() { return Err(Error::from(format!( "emcc command failed - {}", @@ -116,8 +111,9 @@ pub fn compile_language_to_wasm(language_dir: &Path, force_docker: bool) -> Resu } // Move the created `.wasm` file into the current working directory. - fs::rename(&language_dir.join(&output_filename), &output_filename) - .map_err(|e| format!("Couldn't find output file {:?} - {}", output_filename, e))?; + fs::rename(&language_dir.join(&output_filename), &output_filename).map_err(Error::wrap( + || format!("Couldn't find output file {:?}", output_filename), + ))?; Ok(()) } diff --git a/cli/src/web_ui.rs b/cli/src/web_ui.rs index 7522d53f..7ebced86 100644 --- a/cli/src/web_ui.rs +++ b/cli/src/web_ui.rs @@ -1,3 +1,4 @@ +use super::error::Error; use super::wasm; use std::fs; use std::net::TcpListener; @@ -24,20 +25,22 @@ pub fn serve(grammar_path: &Path) { let url = format!("127.0.0.1:{}", port); let server = Server::http(&url).expect("Failed to start web server"); let grammar_name = wasm::get_grammar_name(&grammar_path.join("src")) - .map_err(|e| format!("Failed to get wasm filename: {:?}", e)) + .map_err(Error::wrap(|| "Failed to get wasm filename")) .unwrap(); let wasm_filename = format!("tree-sitter-{}.wasm", grammar_name); let language_wasm = fs::read(grammar_path.join(&wasm_filename)) - .map_err(|_| { + .map_err(Error::wrap(|| { format!( - "Failed to read '{}'. Run `tree-sitter build-wasm` first.", + "Failed to read {}. Run `tree-sitter build-wasm` first.", wasm_filename ) - }) + })) .unwrap(); webbrowser::open(&format!("http://127.0.0.1:{}", port)) - .map_err(|e| format!("Failed to open '{}' in a web browser. Error: {}", url, e)) + .map_err(Error::wrap(|| { + format!("Failed to open '{}' in a web browser", url) + })) .unwrap(); let html = HTML