use crate::ast::Value;
use crate::core::{Environment, Type};
use crate::error::Error;
use heck::{ToLowerCamelCase, ToPascalCase, ToSnakeCase};
use std::hash::{DefaultHasher, Hash, Hasher};
pub fn upper(input: &str) -> String {
input.to_uppercase()
}
pub fn lower(input: &str) -> String {
input.to_lowercase()
}
pub fn to_snake_case(input: &str) -> String {
input.to_snake_case()
}
pub fn to_camel_case(input: &str) -> String {
input.to_lower_camel_case()
}
pub fn to_pascal_case(input: &str) -> String {
input.to_pascal_case()
}
pub fn hash(input: &str, environment: &Environment) -> String {
let mut hasher = DefaultHasher::new();
environment.seed().hash(&mut hasher);
input.hash(&mut hasher);
hasher.finish().to_string()
}
pub fn normalize(input: &str) -> String {
let mut result = String::new();
let mut inserted_underscore = false;
let num_chars = input.chars().count();
for (i, char) in input.chars().enumerate() {
let is_first = result.is_empty();
let is_last = i == num_chars - 1;
let should_strip = is_first || is_last;
if char.is_alphanumeric() || char == '_' {
if result.is_empty() && char.is_numeric() && !inserted_underscore {
result.push('_');
} else if char == '_' && should_strip {
continue;
}
result.push(char);
inserted_underscore = false;
} else if !inserted_underscore && !should_strip {
result.push('_');
inserted_underscore = true;
}
}
if inserted_underscore {
result.pop();
}
if result.is_empty() {
result.push('_');
}
result
}
pub fn concat(inputs: &[&str]) -> String {
inputs.join("")
}
pub fn to_ident(value: &Value) -> Result<Value, Error> {
value.try_cast(&Type::Ident)
}
pub fn to_path(value: &Value) -> Result<Value, Error> {
value.try_cast(&Type::Path)
}
pub fn to_type(value: &Value) -> Result<Value, Error> {
value.try_cast(&Type::Type)
}
pub fn to_expr(value: &Value) -> Result<Value, Error> {
value.try_cast(&Type::Expr)
}
pub fn to_str(value: &Value) -> Result<Value, Error> {
value.try_cast(&Type::LitStr)
}
pub fn to_int(value: &Value) -> Result<Value, Error> {
value.try_cast(&Type::LitInt)
}
#[cfg(test)]
mod tests {
use super::*;
use crate::util::unique_id::next_unique_id;
use rstest::rstest;
#[rstest]
fn test_random_determinism() {
let environment = Environment::new(Default::default(), next_unique_id());
let expected = hash("1", &environment);
let actual = hash("1", &environment);
assert_eq!(actual, expected);
assert_ne!(hash("2", &environment), expected);
}
#[rstest]
#[case("hello_world", "hello_world")]
#[case("$hello_world", "hello_world")]
#[case("_hello_world", "hello_world")]
#[case("hello_world$", "hello_world")]
#[case("hello world", "hello_world")]
#[case("hello__world", "hello__world")]
#[case("hello-world", "hello_world")]
#[case("hello.world", "hello_world")]
#[case("hello...world", "hello_world")]
#[case("hello-_-world", "hello___world")]
#[case("123hello", "_123hello")]
#[case("123", "_123")]
#[case("_123", "_123")]
#[case("#$%^&*", "_")]
#[case("", "_")]
#[case("a__b___c", "a__b___c")]
#[case("a b c", "a_b_c")]
#[case("a.b.c", "a_b_c")]
#[case("a!@#b$%^c", "a_b_c")]
#[case("a_!@#_b", "a___b")]
#[case("&'static str", "static_str")]
#[case("&'static str ", "static_str")]
#[case("Result<T, E>", "Result_T_E")]
#[case("Result< T, E >", "Result_T_E")]
fn test_normalize(#[case] input: &str, #[case] expected: &str) {
let actual = normalize(input);
assert_eq!(actual, expected, "Input: {}", input);
}
#[rstest]
#[case(&[], "")]
#[case(&["hello"], "hello")]
#[case(&["hello", "world"], "helloworld")]
#[case(&["foo", "_", "bar"], "foo_bar")]
#[case(&["a", "b", "c", "d"], "abcd")]
#[case(&["", "hello", "", "world", ""], "helloworld")]
fn test_concat(#[case] inputs: &[&str], #[case] expected: &str) {
let actual = concat(inputs);
assert_eq!(actual, expected, "Inputs: {:?}", inputs);
}
}