feat: Migrate SAL to Cargo workspace
- Migrate individual modules to independent crates - Refactor dependencies for improved modularity - Update build system and testing infrastructure - Update documentation to reflect new structure
This commit is contained in:
		| @@ -18,7 +18,7 @@ | ||||
|  * # Examples | ||||
|  * | ||||
|  * ``` | ||||
|  * use sal::text::dedent; | ||||
|  * use sal_text::dedent; | ||||
|  * | ||||
|  * let indented = "    line 1\n    line 2\n        line 3"; | ||||
|  * let dedented = dedent(indented); | ||||
| @@ -103,7 +103,7 @@ pub fn dedent(text: &str) -> String { | ||||
|  * # Examples | ||||
|  * | ||||
|  * ``` | ||||
|  * use sal::text::prefix; | ||||
|  * use sal_text::prefix; | ||||
|  * | ||||
|  * let text = "line 1\nline 2\nline 3"; | ||||
|  * let prefixed = prefix(text, "    "); | ||||
|   | ||||
| @@ -1,17 +1,33 @@ | ||||
|  | ||||
|  | ||||
| pub fn name_fix(text: &str) -> String { | ||||
|     let mut result = String::with_capacity(text.len()); | ||||
|      | ||||
|  | ||||
|     let mut last_was_underscore = false; | ||||
|     for c in text.chars() { | ||||
|         // Keep only ASCII characters | ||||
|         if c.is_ascii() { | ||||
|             // Replace specific characters with underscore | ||||
|             if c.is_whitespace() || c == ',' || c == '-' || c == '"' || c == '\'' || | ||||
|                c == '#' || c == '!' || c == '(' || c == ')' || c == '[' || c == ']' || | ||||
|                c == '=' || c == '+' || c == '<' || c == '>' || c == '@' || c == '$' || | ||||
|                c == '%' || c == '^' || c == '&' || c == '*' { | ||||
|             if c.is_whitespace() | ||||
|                 || c == ',' | ||||
|                 || c == '-' | ||||
|                 || c == '"' | ||||
|                 || c == '\'' | ||||
|                 || c == '#' | ||||
|                 || c == '!' | ||||
|                 || c == '(' | ||||
|                 || c == ')' | ||||
|                 || c == '[' | ||||
|                 || c == ']' | ||||
|                 || c == '=' | ||||
|                 || c == '+' | ||||
|                 || c == '<' | ||||
|                 || c == '>' | ||||
|                 || c == '@' | ||||
|                 || c == '$' | ||||
|                 || c == '%' | ||||
|                 || c == '^' | ||||
|                 || c == '&' | ||||
|                 || c == '*' | ||||
|             { | ||||
|                 // Only add underscore if the last character wasn't an underscore | ||||
|                 if !last_was_underscore { | ||||
|                     result.push('_'); | ||||
| @@ -25,7 +41,7 @@ pub fn name_fix(text: &str) -> String { | ||||
|         } | ||||
|         // Non-ASCII characters are simply skipped | ||||
|     } | ||||
|      | ||||
|  | ||||
|     // Convert to lowercase | ||||
|     return result.to_lowercase(); | ||||
| } | ||||
| @@ -35,17 +51,17 @@ pub fn path_fix(text: &str) -> String { | ||||
|     if text.ends_with('/') { | ||||
|         return text.to_string(); | ||||
|     } | ||||
|      | ||||
|  | ||||
|     // Find the last '/' to extract the filename part | ||||
|     match text.rfind('/') { | ||||
|         Some(pos) => { | ||||
|             // Extract the path and filename parts | ||||
|             let path = &text[..=pos]; | ||||
|             let filename = &text[pos+1..]; | ||||
|              | ||||
|             let filename = &text[pos + 1..]; | ||||
|  | ||||
|             // Apply name_fix to the filename part only | ||||
|             return format!("{}{}", path, name_fix(filename)); | ||||
|         }, | ||||
|         } | ||||
|         None => { | ||||
|             // No '/' found, so the entire text is a filename | ||||
|             return name_fix(text); | ||||
| @@ -67,12 +83,12 @@ mod tests { | ||||
|         assert_eq!(name_fix("Quotes\"'"), "quotes_"); | ||||
|         assert_eq!(name_fix("Brackets[]<>"), "brackets_"); | ||||
|         assert_eq!(name_fix("Operators=+-"), "operators_"); | ||||
|          | ||||
|  | ||||
|         // Test non-ASCII characters removal | ||||
|         assert_eq!(name_fix("Café"), "caf"); | ||||
|         assert_eq!(name_fix("Résumé"), "rsum"); | ||||
|         assert_eq!(name_fix("Über"), "ber"); | ||||
|          | ||||
|  | ||||
|         // Test lowercase conversion | ||||
|         assert_eq!(name_fix("UPPERCASE"), "uppercase"); | ||||
|         assert_eq!(name_fix("MixedCase"), "mixedcase"); | ||||
| @@ -82,18 +98,26 @@ mod tests { | ||||
|     fn test_path_fix() { | ||||
|         // Test path ending with / | ||||
|         assert_eq!(path_fix("/path/to/directory/"), "/path/to/directory/"); | ||||
|          | ||||
|  | ||||
|         // Test single filename | ||||
|         assert_eq!(path_fix("filename.txt"), "filename.txt"); | ||||
|         assert_eq!(path_fix("UPPER-file.md"), "upper_file.md"); | ||||
|          | ||||
|  | ||||
|         // Test path with filename | ||||
|         assert_eq!(path_fix("/path/to/File Name.txt"), "/path/to/file_name.txt"); | ||||
|         assert_eq!(path_fix("./relative/path/to/DOCUMENT-123.pdf"), "./relative/path/to/document_123.pdf"); | ||||
|         assert_eq!(path_fix("/absolute/path/to/Résumé.doc"), "/absolute/path/to/rsum.doc"); | ||||
|          | ||||
|         assert_eq!( | ||||
|             path_fix("./relative/path/to/DOCUMENT-123.pdf"), | ||||
|             "./relative/path/to/document_123.pdf" | ||||
|         ); | ||||
|         assert_eq!( | ||||
|             path_fix("/absolute/path/to/Résumé.doc"), | ||||
|             "/absolute/path/to/rsum.doc" | ||||
|         ); | ||||
|  | ||||
|         // Test path with special characters in filename | ||||
|         assert_eq!(path_fix("/path/with/[special]<chars>.txt"), "/path/with/_special_chars_.txt"); | ||||
|         assert_eq!( | ||||
|             path_fix("/path/with/[special]<chars>.txt"), | ||||
|             "/path/with/_special_chars_.txt" | ||||
|         ); | ||||
|     } | ||||
| } | ||||
|  | ||||
|   | ||||
| @@ -26,7 +26,7 @@ impl TemplateBuilder { | ||||
|     /// # Example | ||||
|     /// | ||||
|     /// ``` | ||||
|     /// use sal::text::TemplateBuilder; | ||||
|     /// use sal_text::TemplateBuilder; | ||||
|     /// | ||||
|     /// let builder = TemplateBuilder::open("templates/example.html"); | ||||
|     /// ``` | ||||
| @@ -62,7 +62,7 @@ impl TemplateBuilder { | ||||
|     /// # Example | ||||
|     /// | ||||
|     /// ```no_run | ||||
|     /// use sal::text::TemplateBuilder; | ||||
|     /// use sal_text::TemplateBuilder; | ||||
|     /// | ||||
|     /// fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     ///     let builder = TemplateBuilder::open("templates/example.html")? | ||||
| @@ -93,7 +93,7 @@ impl TemplateBuilder { | ||||
|     /// # Example | ||||
|     /// | ||||
|     /// ```no_run | ||||
|     /// use sal::text::TemplateBuilder; | ||||
|     /// use sal_text::TemplateBuilder; | ||||
|     /// use std::collections::HashMap; | ||||
|     /// | ||||
|     /// fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
| @@ -155,7 +155,7 @@ impl TemplateBuilder { | ||||
|     /// # Example | ||||
|     /// | ||||
|     /// ```no_run | ||||
|     /// use sal::text::TemplateBuilder; | ||||
|     /// use sal_text::TemplateBuilder; | ||||
|     /// | ||||
|     /// fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     ///     let result = TemplateBuilder::open("templates/example.html")? | ||||
| @@ -195,7 +195,7 @@ impl TemplateBuilder { | ||||
|     /// # Example | ||||
|     /// | ||||
|     /// ```no_run | ||||
|     /// use sal::text::TemplateBuilder; | ||||
|     /// use sal_text::TemplateBuilder; | ||||
|     /// | ||||
|     /// fn main() -> Result<(), Box<dyn std::error::Error>> { | ||||
|     ///     TemplateBuilder::open("templates/example.html")? | ||||
|   | ||||
		Reference in New Issue
	
	Block a user