feat: Migrate SAL to Cargo workspace
Some checks failed
Rhai Tests / Run Rhai Tests (push) Has been cancelled
Rhai Tests / Run Rhai Tests (pull_request) Has been cancelled

- 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:
Mahmoud-Emad
2025-06-24 12:39:18 +03:00
parent 8012a66250
commit e125bb6511
54 changed files with 1196 additions and 1582 deletions

View File

@@ -9,14 +9,14 @@ license = "Apache-2.0"
[dependencies]
# Regex support for text replacement
regex = "1.8.1"
regex = { workspace = true }
# Template engine for text rendering
tera = "1.19.0"
# Serialization support for templates
serde = { version = "1.0", features = ["derive"] }
serde = { workspace = true }
# Rhai scripting support
rhai = { version = "1.12.0", features = ["sync"] }
rhai = { workspace = true }
[dev-dependencies]
# For temporary files in tests
tempfile = "3.5"
tempfile = { workspace = true }

View File

@@ -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, " ");

View File

@@ -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"
);
}
}

View File

@@ -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")?

View File

@@ -106,7 +106,7 @@ fn test_dedent_and_prefix_combination() {
let indented = " def function():\n print('hello')\n return True";
let dedented = dedent(indented);
let prefixed = prefix(&dedented, ">>> ");
let expected = ">>> def function():\n>>> print('hello')\n>>> return True";
assert_eq!(prefixed, expected);
}
@@ -120,7 +120,7 @@ fn test_dedent_real_code_example() {
return result
else:
return None"#;
let dedented = dedent(code);
let expected = "\nif condition:\n for item in items:\n process(item)\n return result\nelse:\n return None";
assert_eq!(dedented, expected);