Skip to content

Commit d3eac06

Browse files
committed
chore: after migration improvements
1 parent 5d0c9fc commit d3eac06

14 files changed

Lines changed: 527 additions & 283 deletions

src/filters/formatting.rs

Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/// Formatting filters for MiniJinja templates
2+
use minijinja::Value;
3+
use percent_encoding::{NON_ALPHANUMERIC, utf8_percent_encode};
4+
5+
/// Format file size in human-readable format (bytes, KB, MB, GB, etc.)
6+
///
7+
/// # Arguments
8+
///
9+
/// * `value` - The file size in bytes (as number)
10+
///
11+
/// # Example
12+
///
13+
/// ```jinja
14+
/// {{ 1024 | filesizeformat }} => "1 KB"
15+
/// {{ 1048576 | filesizeformat }} => "1 MB"
16+
/// ```
17+
pub fn filesizeformat_filter(value: &Value) -> Result<String, minijinja::Error> {
18+
let bytes = if let Some(n) = value.as_i64() {
19+
n as f64
20+
} else {
21+
return Err(minijinja::Error::new(
22+
minijinja::ErrorKind::InvalidOperation,
23+
"filesizeformat filter requires a number",
24+
));
25+
};
26+
27+
const UNITS: &[&str] = &["bytes", "KB", "MB", "GB", "TB", "PB"];
28+
const THRESHOLD: f64 = 1024.0;
29+
30+
if bytes < THRESHOLD {
31+
return Ok(format!("{} bytes", bytes as i64));
32+
}
33+
34+
let mut size = bytes;
35+
let mut unit_index = 0;
36+
37+
while size >= THRESHOLD && unit_index < UNITS.len() - 1 {
38+
size /= THRESHOLD;
39+
unit_index += 1;
40+
}
41+
42+
// Format with appropriate precision
43+
// If it's a whole number (or very close to one), show without decimals
44+
if (size - size.round()).abs() < 0.01 {
45+
Ok(format!("{:.0} {}", size, UNITS[unit_index]))
46+
} else if size < 10.0 {
47+
Ok(format!("{:.2} {}", size, UNITS[unit_index]))
48+
} else if size < 100.0 {
49+
Ok(format!("{:.1} {}", size, UNITS[unit_index]))
50+
} else {
51+
Ok(format!("{:.0} {}", size, UNITS[unit_index]))
52+
}
53+
}
54+
55+
/// URL encode a string - encode special characters for use in URLs
56+
///
57+
/// # Arguments
58+
///
59+
/// * `value` - The string to URL encode
60+
///
61+
/// # Example
62+
///
63+
/// ```jinja
64+
/// {{ "hello world & foo=bar" | urlencode }} => "hello%20world%20%26%20foo%3Dbar"
65+
/// ```
66+
pub fn urlencode_filter(value: &Value) -> Result<String, minijinja::Error> {
67+
let s = value.as_str().ok_or_else(|| {
68+
minijinja::Error::new(
69+
minijinja::ErrorKind::InvalidOperation,
70+
"urlencode filter requires a string",
71+
)
72+
})?;
73+
74+
Ok(utf8_percent_encode(s, NON_ALPHANUMERIC).to_string())
75+
}

src/filters/mod.rs

Lines changed: 72 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,72 @@
1+
//! Custom filters for MiniJinja templates
2+
//!
3+
//! This module contains custom filters organized by category.
4+
//!
5+
//! # Filter Categories
6+
//!
7+
//! - **String Filters** (`string` module): Text manipulation filters
8+
//! - `slugify` - Convert strings to URL-friendly slugs
9+
//!
10+
//! - **Formatting Filters** (`formatting` module): Data formatting filters
11+
//! - `filesizeformat` - Format bytes as human-readable file sizes
12+
//! - `urlencode` - URL-encode strings for safe URL usage
13+
//!
14+
//! # Adding Custom Filters
15+
//!
16+
//! To add a new custom filter:
17+
//!
18+
//! 1. Choose or create an appropriate category module in `src/filters/`
19+
//! 2. Implement your filter function with signature: `fn my_filter(value: &Value) -> Result<T, Error>`
20+
//! 3. Add `pub mod category;` to this file if it's a new category
21+
//! 4. Add your filter to the `register_all()` function below
22+
//!
23+
//! # Example
24+
//!
25+
//! ```rust
26+
//! // In src/filters/string.rs
27+
//! use minijinja::Value;
28+
//!
29+
//! pub fn my_filter(value: &Value) -> Result<String, minijinja::Error> {
30+
//! let s = value.as_str().ok_or_else(|| {
31+
//! minijinja::Error::new(
32+
//! minijinja::ErrorKind::InvalidOperation,
33+
//! "my_filter requires a string",
34+
//! )
35+
//! })?;
36+
//!
37+
//! // Your implementation here
38+
//! Ok(s.to_uppercase())
39+
//! }
40+
//! ```
41+
42+
pub mod formatting;
43+
pub mod string;
44+
45+
use minijinja::Environment;
46+
47+
/// Register all custom filters with the MiniJinja environment
48+
///
49+
/// This function is called when setting up a MiniJinja environment to register
50+
/// all custom filters.
51+
///
52+
/// # Arguments
53+
///
54+
/// * `env` - Mutable reference to a MiniJinja Environment
55+
///
56+
/// # Example
57+
///
58+
/// ```
59+
/// use minijinja::Environment;
60+
/// use tmpltool::filters;
61+
///
62+
/// let mut env = Environment::new();
63+
/// filters::register_all(&mut env);
64+
/// ```
65+
pub fn register_all(env: &mut Environment) {
66+
// String filters
67+
env.add_filter("slugify", string::slugify_filter);
68+
69+
// Formatting filters
70+
env.add_filter("filesizeformat", formatting::filesizeformat_filter);
71+
env.add_filter("urlencode", formatting::urlencode_filter);
72+
}

src/filters/string.rs

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
/// String manipulation filters for MiniJinja templates
2+
use minijinja::Value;
3+
4+
/// Slugify a string - convert to lowercase, replace spaces with hyphens, remove special chars
5+
///
6+
/// # Arguments
7+
///
8+
/// * `value` - The string to slugify
9+
///
10+
/// # Example
11+
///
12+
/// ```jinja
13+
/// {{ "Hello World!" | slugify }} => "hello-world"
14+
/// {{ "jane smith" | slugify }} => "jane-smith"
15+
/// ```
16+
pub fn slugify_filter(value: &Value) -> Result<String, minijinja::Error> {
17+
let s = value.as_str().ok_or_else(|| {
18+
minijinja::Error::new(
19+
minijinja::ErrorKind::InvalidOperation,
20+
"slugify filter requires a string",
21+
)
22+
})?;
23+
24+
let slug = s
25+
.to_lowercase()
26+
.chars()
27+
.map(|c| {
28+
if c.is_ascii_alphanumeric() {
29+
c
30+
} else if c.is_whitespace() || c == '-' || c == '_' {
31+
'-'
32+
} else {
33+
'\0' // Will be filtered out
34+
}
35+
})
36+
.filter(|&c| c != '\0')
37+
.collect::<String>()
38+
// Remove duplicate hyphens
39+
.split('-')
40+
.filter(|s| !s.is_empty())
41+
.collect::<Vec<_>>()
42+
.join("-");
43+
44+
Ok(slug)
45+
}

src/functions/builtins.rs

Lines changed: 0 additions & 95 deletions
This file was deleted.

src/functions/datetime.rs

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/// Date and time functions for templates
2+
use chrono::Utc;
3+
use minijinja::{Error, Value};
4+
5+
/// Get current timestamp in ISO 8601 format
6+
///
7+
/// Replacement for Tera's built-in now() function
8+
///
9+
/// Returns timestamp in ISO 8601 format: YYYY-MM-DDTHH:MM:SS.sss+00:00
10+
///
11+
/// # Example
12+
///
13+
/// ```jinja
14+
/// {{ now() }} => "2024-12-31T12:34:56.789+00:00"
15+
/// ```
16+
pub fn now_fn() -> Result<Value, Error> {
17+
let timestamp = Utc::now().to_rfc3339();
18+
Ok(Value::from(timestamp))
19+
}
Lines changed: 40 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,13 +1,46 @@
1+
/// Environment variable access functions for templates
12
use minijinja::value::Kwargs;
23
use minijinja::{Error, ErrorKind, Value};
3-
/// Filter environment variables by pattern
4-
///
5-
/// This module provides a MiniJinja function to filter environment variables
6-
/// matching a glob pattern (e.g., "SERVER_*", "DB_*", etc.)
74
use std::collections::HashMap;
8-
use std::env;
95

10-
/// A MiniJinja function that filters environment variables by pattern
6+
/// Get environment variable with optional default
7+
///
8+
/// Replacement for Tera's built-in get_env() function
9+
///
10+
/// # Arguments
11+
///
12+
/// * `name` - Environment variable name
13+
/// * `default` - Optional default value if variable is not set
14+
///
15+
/// # Example
16+
///
17+
/// ```jinja
18+
/// {{ get_env(name="HOME") }}
19+
/// {{ get_env(name="MISSING", default="/tmp") }}
20+
/// ```
21+
pub fn env_fn(kwargs: Kwargs) -> Result<Value, Error> {
22+
let name: String = kwargs.get("name")?;
23+
let default: Option<String> = kwargs.get("default").ok();
24+
25+
match std::env::var(&name) {
26+
Ok(value) => Ok(Value::from(value)),
27+
Err(_) => {
28+
if let Some(def) = default {
29+
Ok(Value::from(def))
30+
} else {
31+
Err(Error::new(
32+
ErrorKind::UndefinedError,
33+
format!(
34+
"Environment variable '{}' is not set and no default provided",
35+
name
36+
),
37+
))
38+
}
39+
}
40+
}
41+
}
42+
43+
/// Filter environment variables by pattern
1144
///
1245
/// Returns a list of objects with `key` and `value` fields for all
1346
/// environment variables matching the given glob pattern.
@@ -46,7 +79,7 @@ pub fn filter_env_fn(kwargs: Kwargs) -> Result<Value, Error> {
4679
})?;
4780

4881
// Filter environment variables
49-
let mut results: Vec<HashMap<String, String>> = env::vars()
82+
let mut results: Vec<HashMap<String, String>> = std::env::vars()
5083
.filter(|(key, _)| re.is_match(key))
5184
.map(|(key, value)| {
5285
let mut map = HashMap::new();

0 commit comments

Comments
 (0)