diff --git a/docs/docs/bindings/rust/index.md b/docs/docs/bindings/rust/index.md index f22747a7c..eddac80d9 100644 --- a/docs/docs/bindings/rust/index.md +++ b/docs/docs/bindings/rust/index.md @@ -75,10 +75,18 @@ use herb::lex; fn main() { let source = "

<%= user.name %>

"; - let result = lex(source); - for token in result.tokens() { - println!("{}", token.inspect()); + match lex(source) { + Ok(result) => { + println!("{}", result); + + for token in result.tokens() { + // do something with each token + } + } + Err(e) => { + eprintln!("Lex error: {}", e); + } } } ``` diff --git a/docs/docs/bindings/rust/reference.md b/docs/docs/bindings/rust/reference.md index 71a79b463..ab9b20ae3 100644 --- a/docs/docs/bindings/rust/reference.md +++ b/docs/docs/bindings/rust/reference.md @@ -20,19 +20,27 @@ The `herb` crate exposes functions for lexing, parsing, and extracting Ruby and ## Lexing -The `herb::lex` function tokenizes an HTML document with embedded Ruby and returns a `LexResult` containing all tokens. +The `herb::lex` function tokenizes an HTML document with embedded Ruby and returns a `Result` containing all tokens. -### `herb::lex(source: &str) -> LexResult` +### `herb::lex(source: &str) -> Result` :::code-group ```rust use herb::lex; let source = "

Hello <%= user.name %>

"; -let result = lex(source); -for token in result.tokens() { - println!("{}", token.inspect()); +match lex(source) { + Ok(result) => { + println!("{}", result); + + for token in result.tokens() { + // do something with each token + } + } + Err(e) => { + eprintln!("Lex error: {}", e); + } } // Output: // # diff --git a/rust/README.md b/rust/README.md index 8ee9a6e73..35b4e8870 100644 --- a/rust/README.md +++ b/rust/README.md @@ -32,14 +32,19 @@ make all # Generate templates and build ### As a Library ```rust -use herb::{lex, Token}; +use herb::{lex, parse}; fn main() { - let source = r#"
<%= name %>
"#; - let result = lex(source); + let template = "

<%= title %>

"; - for token in result.tokens() { - println!("{}", token.inspect()); + match lex(template) { + Ok(result) => { println!("{}", result); } + Err(error) => { eprintln!("Lex error: {}", error); } + } + + match parse(template) { + Ok(result) => { println!("{}", result); } + Err(error) => { eprintln!("Parse error: {}", error); } } } ``` diff --git a/rust/src/herb.rs b/rust/src/herb.rs index ecc1e9075..b5e692eb5 100644 --- a/rust/src/herb.rs +++ b/rust/src/herb.rs @@ -3,13 +3,13 @@ use crate::convert::token_from_c; use crate::{LexResult, ParseResult}; use std::ffi::CString; -pub fn lex(source: &str) -> LexResult { +pub fn lex(source: &str) -> Result { unsafe { - let c_source = CString::new(source).expect("Failed to create CString"); + let c_source = CString::new(source).map_err(|e| e.to_string())?; let c_tokens = crate::ffi::herb_lex(c_source.as_ptr()); if c_tokens.is_null() { - return LexResult::new(Vec::new()); + return Err("Failed to lex source".to_string()); } let array_size = crate::ffi::hb_array_size(c_tokens); @@ -26,7 +26,7 @@ pub fn lex(source: &str) -> LexResult { let mut c_tokens_ptr = c_tokens; crate::ffi::herb_free_tokens(&mut c_tokens_ptr as *mut *mut hb_array_T); - LexResult::new(tokens) + Ok(LexResult::new(tokens)) } } diff --git a/rust/src/lex_result.rs b/rust/src/lex_result.rs index 3c776744e..1e9977448 100644 --- a/rust/src/lex_result.rs +++ b/rust/src/lex_result.rs @@ -1,4 +1,5 @@ use crate::Token; +use std::fmt; pub struct LexResult { pub tokens: Vec, @@ -12,4 +13,19 @@ impl LexResult { pub fn tokens(&self) -> &[Token] { &self.tokens } + + pub fn inspect(&self) -> String { + self + .tokens + .iter() + .map(|token| token.inspect()) + .collect::>() + .join("\n") + } +} + +impl fmt::Display for LexResult { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.inspect()) + } } diff --git a/rust/src/lib.rs b/rust/src/lib.rs index 87329dbdc..27460fd98 100644 --- a/rust/src/lib.rs +++ b/rust/src/lib.rs @@ -23,35 +23,3 @@ pub use range::Range; pub use token::Token; pub const VERSION: &str = "0.7.5"; - -#[cfg(test)] -mod tests { - use super::*; - use crate::nodes::{DocumentNode, HTMLTextNode}; - - #[test] - fn test_tree_inspect() { - let loc = Location::new(Position::new(1, 0), Position::new(1, 5)); - - let text_node = HTMLTextNode { - node_type: "HTMLTextNode".to_string(), - location: loc, - errors: vec![], - content: "Hello".to_string(), - }; - - let doc_node = DocumentNode { - node_type: "DocumentNode".to_string(), - location: Location::new(Position::new(1, 0), Position::new(2, 0)), - errors: vec![], - children: vec![AnyNode::HTMLTextNode(text_node)], - }; - - let output = doc_node.tree_inspect(); - - assert!(output.contains("@ DocumentNode")); - assert!(output.contains("children: (1 item)")); - assert!(output.contains("@ HTMLTextNode")); - assert!(output.contains("Hello")); - } -} diff --git a/rust/src/main.rs b/rust/src/main.rs index abc3eb30c..7bd33fcef 100644 --- a/rust/src/main.rs +++ b/rust/src/main.rs @@ -65,9 +65,14 @@ fn lex_command(file_path: &str) { } }; - let result = herb::lex(&source); - for token in result.tokens() { - println!("{}", token.inspect()); + match herb::lex(&source) { + Ok(result) => { + println!("{}", result); + } + Err(e) => { + eprintln!("Lex error: {}", e); + std::process::exit(1); + } } } @@ -82,7 +87,7 @@ fn parse_command(file_path: &str) { match herb::parse(&source) { Ok(result) => { - println!("{}", result.tree_inspect()); + println!("{}", result.inspect()); } Err(e) => { eprintln!("Parse error: {}", e); diff --git a/rust/src/parse_result.rs b/rust/src/parse_result.rs index 756215195..f41e0079c 100644 --- a/rust/src/parse_result.rs +++ b/rust/src/parse_result.rs @@ -1,5 +1,6 @@ use crate::errors::{AnyError, ErrorNode}; use crate::nodes::{DocumentNode, Node}; +use std::fmt; pub struct ParseResult { pub value: DocumentNode, @@ -16,7 +17,7 @@ impl ParseResult { } } - pub fn tree_inspect(&self) -> String { + pub fn inspect(&self) -> String { self.value.tree_inspect() } @@ -39,3 +40,9 @@ impl ParseResult { self.recursive_errors().is_empty() } } + +impl fmt::Display for ParseResult { + fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { + write!(f, "{}", self.inspect()) + } +} diff --git a/rust/tests/common/mod.rs b/rust/tests/common/mod.rs new file mode 100644 index 000000000..2ded60ee4 --- /dev/null +++ b/rust/tests/common/mod.rs @@ -0,0 +1,5 @@ +// Common test utilities + +pub fn no_color() { + colored::control::set_override(false); +} diff --git a/rust/tests/error_handling_test.rs b/rust/tests/error_handling_test.rs index b94f2a6c3..7617f230d 100644 --- a/rust/tests/error_handling_test.rs +++ b/rust/tests/error_handling_test.rs @@ -1,11 +1,15 @@ +mod common; + use herb::parse; #[test] fn test_unclosed_element_error() { + common::no_color(); + let source = "
"; let result = parse(source).unwrap(); - let tree_inspect = result.tree_inspect(); + let tree_inspect = result.inspect(); assert!(tree_inspect.contains("UnclosedElementError")); assert!(tree_inspect.contains("Tag `
` opened at (1:1) was never closed")); assert!(tree_inspect.contains("MissingClosingTagError")); @@ -14,20 +18,24 @@ fn test_unclosed_element_error() { #[test] fn test_tag_names_mismatch_error() { + common::no_color(); + let source = "
"; let result = parse(source).unwrap(); - let tree_inspect = result.tree_inspect(); + let tree_inspect = result.inspect(); assert!(tree_inspect.contains("TagNamesMismatchError")); assert!(tree_inspect.contains("Opening tag `
` at (1:1) closed with ``")); } #[test] fn test_no_errors_with_valid_html() { + common::no_color(); + let source = "
Hello
"; let result = parse(source).unwrap(); - let tree_inspect = result.tree_inspect(); + let tree_inspect = result.inspect(); assert!(!tree_inspect.contains("error")); assert!(!tree_inspect.contains("ERROR")); } diff --git a/rust/tests/node_field_test.rs b/rust/tests/node_field_test.rs index 77b4b2d0c..912edad6a 100644 --- a/rust/tests/node_field_test.rs +++ b/rust/tests/node_field_test.rs @@ -1,11 +1,15 @@ +mod common; + use herb::parse; #[test] fn test_open_tag_field_is_displayed() { + common::no_color(); + let source = "
Hello
"; let result = parse(source).unwrap(); - let tree_inspect = result.tree_inspect(); + let tree_inspect = result.inspect(); assert!(tree_inspect.contains("open_tag:")); assert!(tree_inspect.contains("@ HTMLOpenTagNode")); assert!(tree_inspect.contains("tag_opening: \"<\"")); @@ -18,10 +22,12 @@ fn test_open_tag_field_is_displayed() { #[test] fn test_nested_node_fields() { + common::no_color(); + let source = "
Hello
"; let result = parse(source).unwrap(); - let tree_inspect = result.tree_inspect(); + let tree_inspect = result.inspect(); assert!(tree_inspect.contains("@ HTMLAttributeNode")); assert!(tree_inspect.contains("name:")); assert!(tree_inspect.contains("@ HTMLAttributeNameNode")); diff --git a/rust/tests/parse_result_test.rs b/rust/tests/parse_result_test.rs index 930536774..7f35d8c85 100644 --- a/rust/tests/parse_result_test.rs +++ b/rust/tests/parse_result_test.rs @@ -1,7 +1,11 @@ +mod common; + use herb::parse; #[test] fn test_parse_result_success_with_valid_html() { + common::no_color(); + let source = "
Hello
"; let result = parse(source).unwrap(); @@ -23,6 +27,8 @@ fn test_parse_result_success_with_valid_html() { #[test] fn test_parse_result_failed_with_unclosed_element() { + common::no_color(); + let source = "
"; let result = parse(source).unwrap(); @@ -54,6 +60,8 @@ fn test_parse_result_failed_with_unclosed_element() { #[test] fn test_parse_result_failed_with_tag_mismatch() { + common::no_color(); + let source = "
"; let result = parse(source).unwrap(); @@ -78,6 +86,8 @@ fn test_parse_result_failed_with_tag_mismatch() { #[test] fn test_parse_result_errors_are_recursive() { + common::no_color(); + let source = "
"; let result = parse(source).unwrap(); diff --git a/rust/tests/snapshot_test.rs b/rust/tests/snapshot_test.rs index 07f389fd2..5bcf311d2 100644 --- a/rust/tests/snapshot_test.rs +++ b/rust/tests/snapshot_test.rs @@ -1,9 +1,12 @@ +mod common; + use herb::{extract_html, extract_ruby, herb_version, lex, parse, prism_version, version}; const TEST_INPUT: &str = include_str!("./fixtures/test.html.erb"); #[test] fn test_version_output() { + common::no_color(); let output = version(); insta::assert_snapshot!(output); @@ -11,6 +14,7 @@ fn test_version_output() { #[test] fn test_herb_version_output() { + common::no_color(); let output = herb_version(); insta::assert_snapshot!(output); @@ -18,6 +22,7 @@ fn test_herb_version_output() { #[test] fn test_prism_version_output() { + common::no_color(); let output = prism_version(); insta::assert_snapshot!(output); @@ -25,7 +30,9 @@ fn test_prism_version_output() { #[test] fn test_lex_output() { - let result = lex(TEST_INPUT); + common::no_color(); + + let result = lex(TEST_INPUT).expect("Failed to lex"); let output = result .tokens() .iter() @@ -38,14 +45,18 @@ fn test_lex_output() { #[test] fn test_parse_output() { + common::no_color(); + let result = parse(TEST_INPUT).expect("Failed to parse"); - let output = result.tree_inspect(); + let output = result.inspect(); insta::assert_snapshot!(output); } #[test] fn test_extract_ruby_output() { + common::no_color(); + let result = extract_ruby(TEST_INPUT).expect("Failed to extract Ruby"); insta::assert_snapshot!(result); @@ -53,6 +64,8 @@ fn test_extract_ruby_output() { #[test] fn test_extract_html_output() { + common::no_color(); + let result = extract_html(TEST_INPUT).expect("Failed to extract HTML"); insta::assert_snapshot!(result); diff --git a/rust/tests/tree_inspect_test.rs b/rust/tests/tree_inspect_test.rs index 42826b6bd..fe446a5ea 100644 --- a/rust/tests/tree_inspect_test.rs +++ b/rust/tests/tree_inspect_test.rs @@ -1,8 +1,12 @@ +mod common; + use herb::nodes::{AnyNode, DocumentNode, HTMLTextNode, Node}; use herb::{Location, Position}; #[test] fn test_document_with_text_node() { + common::no_color(); + let loc = Location::new(Position::new(1, 0), Position::new(1, 5)); let text_node = HTMLTextNode {