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