feat: basic parsing and read structure implemented

This commit is contained in:
2025-02-22 13:43:00 +01:00
parent d4cb12dbbd
commit 667a0aa4a1
8 changed files with 701 additions and 0 deletions

66
src/files/mod.rs Normal file
View File

@@ -0,0 +1,66 @@
use std::{env, fs, path::PathBuf};
const ENV_KEY: &str = "BLUEPRINTS_PATHS";
const DEFAULT_PATH: &str = "./.blueprints";
// ╭────────────────────────╮
// │- Read from filesystem -│
// ╰────────────────────────╯
pub fn get_template_options(base_paths: Vec<String>) -> Vec<String> {
let mut type_options: Vec<String> = vec![];
for base_path in base_paths {
let type_dirs = get_valid_dirs_paths(&base_path);
// TODO: prettify this in one line
for type_dir in type_dirs {
type_options.push(type_dir);
}
}
type_options
}
pub fn get_valid_dirs_paths(dir: &str) -> Vec<String> {
// FIXME: this panics when the directory does not exist
let paths = fs::read_dir(dir).unwrap();
let mut valid_dirs: Vec<String> = vec![];
for path in paths {
let _path = path.unwrap().path();
if is_template_path_valid(&_path) {
valid_dirs.push(_path.display().to_string());
}
}
return valid_dirs;
}
pub fn is_template_path_valid(path: &PathBuf) -> bool {
path.is_dir()
}
pub fn get_base_template_paths() -> Vec<String> {
let mut base_paths: Vec<String> = vec![];
match env::var(ENV_KEY) {
Ok(value) => {
let env_path_list = value.split(";");
for raw_path in env_path_list {
base_paths.push(raw_path.to_string());
}
}
Err(_) => {
base_paths.push(DEFAULT_PATH.to_string());
println!(
"${} is not set. Falling back to default './.blueprints' route.",
ENV_KEY
);
}
}
base_paths
}
// ╭───────────────────────╮
// │- Write to filesystem -│
// ╰───────────────────────╯

38
src/main.rs Normal file
View File

@@ -0,0 +1,38 @@
mod files;
mod parsers;
use files::*;
use inquire::{Select, Text};
#[cfg(test)]
#[path = "./parsers/test/parsers_tests.rs"]
mod test;
fn main() {
println!(
"╭──────────────────╮
│- Code Templates -│
╰──────────────────╯"
);
let base_paths = get_base_template_paths();
let template_type_options = get_template_options(base_paths);
let template_type_result =
Select::new("Select a template variant:", template_type_options).prompt();
if let Ok(template_type) = template_type_result {
let template_file_options = get_template_options(vec![template_type]);
let template_file_result =
Select::new("Select a template:", template_file_options).prompt();
if let Ok(template_file) = template_file_result {
let target_name_result = Text::new("Insert the desired name:").prompt();
if let Ok(target_name) = target_name_result {
// TODO: decide if the path should be inserted automatically of with a loop of selections -> Maybe better the loop
let target_path_result = Text::new("Insert the target path:").prompt();
if let Ok(target_path) = target_path_result {}
}
}
}
}

View File

@@ -0,0 +1,23 @@
pub const FILENAME_EXPRESSIONS: [&str; 9] = [
"__name__",
"__upperCase_name__",
"__lowerCase_name__",
"__camelCase_name__",
"__pascalCase_name__",
"__snakeCase_name__",
"__upperSnakeCase_name__",
"__kebabCase_name__",
"__lowerDotCase_name__",
];
pub const TEMPLATE_EXPRESSIONS: [&str; 9] = [
"{{name}}",
"{{upperCase name}}",
"{{lowerCase name}}",
"{{camelCase name}}",
"{{pascalCase name}}",
"{{snakeCase name}}",
"{{upperSnakeCase name}}",
"{{kebabCase name}}",
"{{lowerDotCase name}}",
];

85
src/parsers/mod.rs Normal file
View File

@@ -0,0 +1,85 @@
use regex::Regex;
pub mod expressions;
pub fn apply_filename_template(template: &str, filename: &str) -> String {
match template {
"__name__" => filename.to_string(),
"__upperCase_name__" => filename.to_uppercase().to_string(),
"__lowerCase_name__" => filename.to_lowercase().to_string(),
"__camelCase_name__" => parse_camel_case(filename),
"__pascalCase_name__" => parse_pascal_case(filename),
"__snakeCase_name__" => parse_snake_case(filename),
"__upperSnakeCase_name__" => parse_snake_case(filename).to_uppercase(),
"__kebabCase_name__" => parse_snake_case(filename).replace("_", "-"),
"__lowerDotCase_name__" => parse_snake_case(filename).replace("_", "."),
_ => filename.to_string(),
}
}
fn parse_camel_case(filename: &str) -> String {
let first_char_regex = Regex::new(r"^[A-Z]").unwrap();
let filename = parse_pascal_case(filename);
let filename = first_char_regex
.replace_all(&filename, |captured: &regex::Captures| {
captured[0].to_lowercase()
})
.into_owned();
filename
}
fn parse_pascal_case(filename: &str) -> String {
let char_after_space_regex = Regex::new(r" ([a-z])").unwrap();
let first_char_regex = Regex::new(r"^[a-z]").unwrap();
// Change all separators by " " to facilitate regex parsing
let filename = filename.replace("-", " ").replace("_", " ");
let filename = char_after_space_regex
.replace_all(&filename, |captured: &regex::Captures| {
format!(" {}", captured[1].to_uppercase())
})
.into_owned();
let filename = first_char_regex
.replace_all(&filename, |captured: &regex::Captures| {
captured[0].to_uppercase()
})
.into_owned();
let filename = filename.replace(" ", "");
filename
}
fn parse_snake_case(filename: &str) -> String {
let highlight_regex = Regex::new(r"[\s\_\-A-Z]([A-Za-z])").unwrap();
let splited_filename = filename.split_at(1);
let rest_filename = splited_filename.1.to_string();
let rest_filename = highlight_regex
.replace_all(&rest_filename, |captured: &regex::Captures| {
let valid_char_regex = Regex::new(r"^[A-Za-z]$").unwrap();
let discarded = captured[0].to_string().chars().nth(0);
let mut left_side = "".to_string();
if let Some(discarded_first) = discarded {
if valid_char_regex.is_match(&discarded_first.to_string()) {
left_side = discarded_first.to_string();
}
}
format!(
"_{}{}",
left_side.to_lowercase(),
captured[1].to_lowercase()
)
})
.into_owned();
let filename = format!(
"{}{}",
splited_filename.0.to_lowercase(),
rest_filename.to_lowercase()
);
filename.to_string()
}

View File

@@ -0,0 +1,26 @@
#[cfg(test)]
pub mod tests {
use crate::parsers::{apply_filename_template, expressions::FILENAME_EXPRESSIONS};
#[test]
fn test_apply_filename_template() {
const FILENAME: &str = "this_is a-TeSt";
let expected_filename_output = [
"this_is a-TeSt", // normal
"THIS_IS A-TEST", // upper
"this_is a-test", // lower
"thisIsATeSt", // camel
"ThisIsATeSt", // Pascal
"this_is_a_te_st", // snake
"THIS_IS_A_TE_ST", // snake upper
"this-is-a-te-st", // kebab
"this.is.a.te.st", // lower dot
];
for (i, expression) in FILENAME_EXPRESSIONS.into_iter().enumerate() {
let output = apply_filename_template(expression, FILENAME);
assert_eq!(output, expected_filename_output[i])
}
}
}