Skip to content

Commit

Permalink
feat: implement a few tera functions for mise toml config (#2561)
Browse files Browse the repository at this point in the history
* Add platform-info to get platform information

* Add num_cpus to get the number of CPU information

* Implement customized tera functions for toml config

Functions:
- arch
- num_cpus
- os
- os_family
- invocation_directory
- quote
- kebabcase
- lowercamelcase
- shoutykebabcase
- shoutysnakecase
- snakecase
- uppercamelcase

* fixup! Implement customized tera functions for toml config

* fixup! Implement customized tera functions for toml config

---------

Co-authored-by: Erick Guan <erickguan@users.noreply.github.com>
  • Loading branch information
erickguan and erickguan committed Sep 11, 2024
1 parent 0830c06 commit 542a78d
Show file tree
Hide file tree
Showing 3 changed files with 304 additions and 1 deletion.
11 changes: 11 additions & 0 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,12 @@ indicatif = { version = "0.17.8", features = ["default", "improved_unicode"] }
indoc = "2.0.5"
itertools = "0.13"
log = "0.4.21"
num_cpus = "1.16.0"
num_cpus = "1.16.0" # gets cross-platform the number of CPU
once_cell = "1.19.0"
openssl = { version = "0.10.66", optional = true }
path-absolutize = "3.1.1"
petgraph = "0.6.4"
platform-info = "2.0.3" # cross-platform platform information
rand = "0.8.5"
rayon = "1.10.0"
regex = "1.10.4"
Expand Down
291 changes: 291 additions & 0 deletions src/tera.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,12 @@
use std::collections::HashMap;
use std::path::{Path, PathBuf};

use heck::{
ToKebabCase, ToLowerCamelCase, ToShoutyKebabCase, ToShoutySnakeCase, ToSnakeCase,
ToUpperCamelCase,
};
use once_cell::sync::Lazy;
use platform_info::{PlatformInfo, PlatformInfoAPI, UNameAPI};
use tera::{Context, Tera, Value};

use crate::cmd::cmd;
Expand Down Expand Up @@ -36,6 +41,47 @@ pub fn get_tera(dir: Option<&Path>) -> Tera {
}
},
);
tera.register_function(
"arch",
move |_args: &HashMap<String, Value>| -> tera::Result<Value> {
let info = PlatformInfo::new().expect("unable to determine platform info");
let result = String::from(info.machine().to_string_lossy()); // ignore potential UTF-8 convension error
Ok(Value::String(result))
},
);
tera.register_function(
"num_cpus",
move |_args: &HashMap<String, Value>| -> tera::Result<Value> {
let num = num_cpus::get();
Ok(Value::String(num.to_string()))
},
);
tera.register_function(
"os",
move |_args: &HashMap<String, Value>| -> tera::Result<Value> {
let info = PlatformInfo::new().expect("unable to determine platform info");
let result = String::from(info.osname().to_string_lossy()); // ignore potential UTF-8 convension error
Ok(Value::String(result))
},
);
tera.register_function(
"os_family",
move |_args: &HashMap<String, Value>| -> tera::Result<Value> {
let info = PlatformInfo::new().expect("unable to determine platform info");
let result = String::from(info.sysname().to_string_lossy()); // ignore potential UTF-8 convension error
Ok(Value::String(result))
},
);
tera.register_function(
"invocation_directory",
move |_args: &HashMap<String, Value>| -> tera::Result<Value> {
let path = env::current_dir().unwrap_or_default();

let result = String::from(path.to_string_lossy());

Ok(Value::String(result))
},
);
tera.register_filter(
"hash",
move |input: &Value, _args: &HashMap<String, Value>| match input {
Expand Down Expand Up @@ -78,12 +124,257 @@ pub fn get_tera(dir: Option<&Path>) -> Tera {
_ => Err("join_path input must be an array of strings".into()),
},
);
tera.register_filter(
"quote",
move |input: &Value, _args: &HashMap<String, Value>| match input {
Value::String(s) => {
let result = format!("'{}'", s.replace("'", "\\'"));

Ok(Value::String(result))
}
_ => Err("quote input must be a string".into()),
},
);
tera.register_filter(
"kebabcase",
move |input: &Value, _args: &HashMap<String, Value>| match input {
Value::String(s) => Ok(Value::String(s.to_kebab_case())),
_ => Err("kebabcase input must be a string".into()),
},
);
tera.register_filter(
"lowercamelcase",
move |input: &Value, _args: &HashMap<String, Value>| match input {
Value::String(s) => Ok(Value::String(s.to_lower_camel_case())),
_ => Err("lowercamelcase input must be a string".into()),
},
);
tera.register_filter(
"shoutykebabcase",
move |input: &Value, _args: &HashMap<String, Value>| match input {
Value::String(s) => Ok(Value::String(s.to_shouty_kebab_case())),
_ => Err("shoutykebabcase input must be a string".into()),
},
);
tera.register_filter(
"shoutysnakecase",
move |input: &Value, _args: &HashMap<String, Value>| match input {
Value::String(s) => Ok(Value::String(s.to_shouty_snake_case())),
_ => Err("shoutysnakecase input must be a string".into()),
},
);
tera.register_filter(
"snakecase",
move |input: &Value, _args: &HashMap<String, Value>| match input {
Value::String(s) => Ok(Value::String(s.to_snake_case())),
_ => Err("snakecase input must be a string".into()),
},
);
tera.register_filter(
"uppercamelcase",
move |input: &Value, _args: &HashMap<String, Value>| match input {
Value::String(s) => Ok(Value::String(s.to_upper_camel_case())),
_ => Err("uppercamelcase input must be a string".into()),
},
);
tera.register_tester(
"file_exists",
move |input: Option<&Value>, _args: &[Value]| match input {
Some(Value::String(s)) => Ok(Path::new(s).exists()),
_ => Err("file_exists input must be a string".into()),
},
);

tera
}

#[cfg(test)]
mod tests {
use super::*;

#[test]
#[cfg(target_arch = "x86_64")]
fn test_render_with_custom_function_arch_x86_64() {
let mut tera = get_tera(Option::default());

let result = tera
.render_str("{{ arch() }}", &Context::default())
.unwrap();

assert_eq!("x86_64", result);
}

#[test]
#[cfg(target_arch = "aarch64")]
fn test_render_with_custom_function_arch_arm64() {
let mut tera = get_tera(Option::default());

let result = tera
.render_str("{{ arch() }}", &Context::default())
.unwrap();

assert_eq!("aarch64", result);
}

#[test]
fn test_render_with_custom_function_num_cpus() {
let mut tera = get_tera(Option::default());

let result = tera
.render_str("{{ num_cpus() }}", &Context::default())
.unwrap();

let num = result.parse::<u32>().unwrap();
assert!(num > 0);
}

#[test]
#[cfg(target_os = "linux")]
fn test_render_with_custom_function_os_linux() {
let mut tera = get_tera(Option::default());

let result = tera.render_str("{{ os() }}", &Context::default()).unwrap();

assert_eq!("GNU/Linux", result);
}

#[test]
#[cfg(target_os = "windows")]
fn test_render_with_custom_function_os_windows() {
let mut tera = get_tera(Option::default());

let result = tera.render_str("{{ os() }}", &Context::default()).unwrap();

assert_eq!("Windows", result);
}

#[test]
#[cfg(target_family = "unix")]
fn test_render_with_custom_function_os_family_unix() {
let mut tera = get_tera(Option::default());

let result = tera
.render_str("{{ os_family() }}", &Context::default())
.unwrap();

assert_eq!("Linux", result);
}

#[test]
#[cfg(target_family = "windows")]
fn test_render_with_custom_function_os_windows() {
let mut tera = get_tera(Option::default());

let result = tera
.render_str("{{ os_family() }}", &Context::default())
.unwrap();

assert_eq!("Windows", result);
}

#[test]
#[cfg(target_family = "unix")]
fn test_render_with_custom_function_invocation_directory() {
let a = env::set_current_dir("/tmp").is_ok();
let mut tera = get_tera(Option::default());
assert!(a);
println!("{:?}", env::current_dir().unwrap());

let result = tera
.render_str("{{ invocation_directory() }}", &Context::default())
.unwrap();

assert_eq!("/tmp", result);
}

#[test]
#[cfg(target_family = "windows")]
fn test_render_with_custom_function_invocation_directory() {
let a = env::set_current_dir("C:\\").is_ok();
let mut tera = get_tera(Option::default());
assert!(a);

let result = tera
.render_str("{{ invocation_directory() }}", &Context::default())
.unwrap();

assert_eq!("C:\\", result);
}

#[test]
fn test_render_with_custom_filter_quote() {
let mut tera = get_tera(Option::default());

let result = tera
.render_str("{{ \"quoted'str\" | quote }}", &Context::default())
.unwrap();

assert_eq!("'quoted\\'str'", result);
}

#[test]
fn test_render_with_custom_filter_kebabcase() {
let mut tera = get_tera(Option::default());

let result = tera
.render_str("{{ \"thisFilter\" | kebabcase }}", &Context::default())
.unwrap();

assert_eq!("this-filter", result);
}

#[test]
fn test_render_with_custom_filter_lowercamelcase() {
let mut tera = get_tera(Option::default());

let result = tera
.render_str("{{ \"Camel-case\" | lowercamelcase }}", &Context::default())
.unwrap();

assert_eq!("camelCase", result);
}

#[test]
fn test_render_with_custom_filter_shoutykebabcase() {
let mut tera = get_tera(Option::default());

let result = tera
.render_str("{{ \"kebabCase\" | shoutykebabcase }}", &Context::default())
.unwrap();

assert_eq!("KEBAB-CASE", result);
}

#[test]
fn test_render_with_custom_filter_shoutysnakecase() {
let mut tera = get_tera(Option::default());

let result = tera
.render_str("{{ \"snakeCase\" | shoutysnakecase }}", &Context::default())
.unwrap();

assert_eq!("SNAKE_CASE", result);
}

#[test]
fn test_render_with_custom_filter_snakecase() {
let mut tera = get_tera(Option::default());

let result = tera
.render_str("{{ \"snakeCase\" | snakecase }}", &Context::default())
.unwrap();

assert_eq!("snake_case", result);
}

#[test]
fn test_render_with_custom_filter_uppercamelcase() {
let mut tera = get_tera(Option::default());

let result = tera
.render_str("{{ \"CamelCase\" | uppercamelcase }}", &Context::default())
.unwrap();

assert_eq!("CamelCase", result);
}
}

0 comments on commit 542a78d

Please sign in to comment.