pub mod helpers {
use color_eyre::eyre::Result;
use eyre::Error;
use std::fs;
use std::path::Path;
use toml::Value;
pub fn get_project_name_from_rustyroad_toml() -> Result<String, Error> {
let file = fs::read_to_string("rustyroad.toml")
.unwrap_or_else(|_| panic!(
"Could not find rustyroad.toml in '{}'.\n\n\
Run this command from your RustyRoad project root (the folder containing rustyroad.toml).\n\
If you haven't created a project, run: rustyroad new <project_name>",
std::env::current_dir().unwrap_or_else(|_| ".".into()).display()
));
let toml: Value = toml::from_str(&file).unwrap();
let project_table = toml["rustyroad_project"].as_table().unwrap();
Ok(project_table["name"].as_str().unwrap().to_string())
}
pub fn capitalize_first(s: &str) -> String {
let mut chars = s.chars();
match chars.next() {
None => String::new(),
Some(first) => first.to_uppercase().collect::<String>() + chars.as_str(),
}
}
use lazy_static::lazy_static;
use regex::Regex;
use std::collections::HashSet;
lazy_static! {
static ref IMPORT_REGEX: Regex = Regex::new(r"use [a-zA-Z_]+::\{[a-zA-Z_:, ]+\};").unwrap();
static ref MODULE_REGEX: Regex = Regex::new(r"use [a-zA-Z_]+::").unwrap();
}
pub fn add_or_update_import(contents: &str, module: &str, import_item: &str) -> String {
let import_regex = Regex::new(&format!(r"use {}::\{{[a-zA-Z_:, ]*\}};", module)).unwrap();
let mut crate_imports = Vec::new();
let mut other_imports = Vec::new();
let mut non_import_lines = Vec::new();
let mut found = false;
for line in contents.lines() {
if import_regex.is_match(line) {
found = true;
let mut items: HashSet<&str> = line
.trim_start_matches(&format!("use {}::{{", module))
.trim_end_matches("};")
.split(", ")
.collect();
items.insert(import_item);
let new_import_line = format!(
"use {}::{{{}}};",
module,
items.into_iter().collect::<Vec<&str>>().join(", ")
);
if new_import_line.starts_with("use crate::") {
crate_imports.push(new_import_line);
} else {
other_imports.push(new_import_line);
}
} else {
non_import_lines.push(line.to_string());
}
}
if !found && module.starts_with("crate::") {
crate_imports.push(format!("use {}::{{{}}};", module, import_item));
} else if !found {
other_imports.push(format!("use {}::{{{}}};", module, import_item));
}
let mut sorted_imports = crate_imports;
sorted_imports.extend(other_imports);
let mut updated_contents = sorted_imports.join("\n");
if !updated_contents.is_empty() {
updated_contents.push('\n');
}
updated_contents + &non_import_lines.join("\n")
}
pub fn determine_controller_path(model_name: &str) -> String {
let path = format!("./src/controllers/{}.rs", model_name);
if Path::new(&path).exists() {
path
} else {
format!("./src/controllers/{}/{}.rs", model_name, model_name)
}
}
pub fn prompt_to_create_controller(path: &str) -> Result<(), Box<dyn std::error::Error>> {
println!(
"The controller at path {} does not exist. Would you like to create it? (y/n)",
path
);
let mut input = String::new();
std::io::stdin().read_line(&mut input)?;
if input.trim().eq_ignore_ascii_case("y") {
let parent_dir = Path::new(path)
.parent()
.ok_or("Failed to find parent directory")?;
fs::create_dir_all(parent_dir)?;
println!("Creating controller at path {}", path);
fs::File::create(path)?;
} else {
return Err(Box::new(std::io::Error::other(
"Controller creation aborted by user",
)));
}
Ok(())
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_add_or_update_import() {
let mut base_content =
String::from("use actix_web::{HttpResponse, Responder};\nuse some_other_module;\n");
base_content = add_or_update_import(&base_content, "actix_web", "get");
base_content = add_or_update_import(&base_content, "actix_web", "web");
base_content = add_or_update_import(&base_content, "crate", "models");
base_content = add_or_update_import(&base_content, "crate", "helpers");
base_content = add_or_update_import(&base_content, "tera", "Context");
base_content = add_or_update_import(&base_content, "tera", "Tera");
base_content = add_or_update_import(&base_content, "actix_identity", "Identity");
base_content = add_or_update_import(&base_content, "user", "UserLogin");
base_content = add_or_update_import(&base_content, "actix_web", "get");
base_content = add_or_update_import(&base_content, "tera", "Context");
base_content = add_or_update_import(&base_content, "actix_web", "web::Json");
base_content = add_or_update_import(&base_content, "actix_web", "web::Path");
let lines: Vec<&str> = base_content.lines().collect();
let crate_import_index = lines
.iter()
.position(|&line| line.starts_with("use crate::"))
.expect("Failed to find 'use crate::'");
let _actix_web_import_index = lines
.iter()
.position(|&line| line.starts_with("use actix_web::"))
.expect("Failed to find 'use actix_web::'");
let tera_import_index = base_content
.find("use tera::")
.expect("Failed to find 'use tera::'");
let actix_identity_import_index = base_content
.find("use actix_identity::")
.expect("Failed to find 'use actix_identity::'");
let user_import_index = base_content
.find("use user::")
.expect("Failed to find 'use user::'");
assert!(crate_import_index < tera_import_index);
assert!(crate_import_index < actix_identity_import_index);
assert!(crate_import_index < user_import_index);
assert_eq!(base_content.matches("use actix_web::{get,").count(), 1);
assert_eq!(base_content.matches("use tera::Context;").count(), 1);
}
}
}