use std::collections::HashMap;
use std::error::Error;
use std::fs;
use std::fs::File;
use std::io::Write;
use std::path::PathBuf;
use std::process::Command;
use include_dir::{Dir, include_dir};
use text_placeholder::Template;
use crate::MyConfig;
static TEMPLATE_DIR: Dir<'_> = include_dir!("src/template");
fn resolve_files(dir: &Dir, conf: &MyConfig) -> Result<(), Box<dyn Error>> {
let project_name = conf.name.as_ref().unwrap();
let project_title = conf.title.as_ref().unwrap_or(project_name);
for entry in dir.entries() {
if let Some(x) = entry.as_dir() {
resolve_files(x, conf)?;
} else {
let file_path = entry.path();
let mut output_path_buf = PathBuf::new();
output_path_buf.push(project_name);
output_path_buf.push(file_path);
let output_path = output_path_buf.to_str().unwrap();
let parent_dir = std::path::Path::new(output_path).parent().unwrap();
fs::create_dir_all(parent_dir)?;
let mut file = File::create(output_path)?;
if let Some(content) = entry.as_file().unwrap().contents_utf8() {
let tt = Template::new_with_placeholder(content, "<%", "%>");
let mut map = HashMap::new();
map.insert("name", project_name.as_str());
map.insert("title", project_title.as_str());
let new_content = tt.fill_with_hashmap(&map);
file.write_all(new_content.as_bytes())?;
} else {
file.write_all(entry.as_file().unwrap().contents())?;
}
}
}
Ok(())
}
fn init_git(conf: &MyConfig) -> Result<(), Box<dyn Error>> {
if conf.skip_git {
return Ok(());
}
let project_name = conf.name.as_ref().unwrap();
Command::new("git")
.arg("init")
.current_dir(&project_name)
.output()?;
Command::new("git")
.arg("add")
.arg(".")
.current_dir(&project_name)
.output()?;
Command::new("git")
.arg("commit")
.arg("-m")
.arg("initial commit")
.current_dir(&project_name)
.output()?;
Ok(())
}
pub fn runner_init(conf: MyConfig) -> Result<(), String> {
let project_name = conf.name.as_ref().unwrap();
if let Ok(_) = fs::metadata(project_name) {
return Err(format!("name已经存在: {project_name}"));
}
if let Err(e) = resolve_files(&TEMPLATE_DIR, &conf) {
return Err(format!("模板文件解析失败: {e}"));
}
if let Err(e) = init_git(&conf) {
return Err(format!("git初始化失败: {e}"));
};
Ok(())
}
#[cfg(test)]
mod tests {
use uuid::Uuid;
use crate::{MyCommand, MyConfig};
use super::*;
#[cfg(test)]
#[macro_export]
macro_rules! assert_contains {
($filename:expr, $test_name:expr) => {
if let Ok(c) = fs::read_to_string($filename) {
assert!(c.contains($test_name));
} else {
assert!(false);
}
};
}
fn setup(test_name: &str) {
let result = fs::metadata(&test_name);
if let Ok(m) = result {
if m.is_dir() {
fs::remove_dir_all(&test_name).unwrap();
} else {
fs::remove_file(&test_name).unwrap();
}
}
}
fn teardown(test_name: &str) {
fs::remove_dir_all(test_name).unwrap();
}
#[test]
fn test_runner_init_normal() {
let test_name = Uuid::new_v4().to_string();
const TEST_DESC: &str = "我的文档";
setup(&test_name);
let config = MyConfig {
command: MyCommand::INIT,
name: Some(test_name.to_string()),
title: Some(TEST_DESC.to_string()),
skip_git: false,
};
runner_init(config).unwrap();
assert_contains!(format!("{test_name}/antora.yml"),&test_name);
assert_contains!(format!("{test_name}/antora-playbook.yml"),&test_name);
assert_contains!(format!("{test_name}/antora.yml"),TEST_DESC);
assert_contains!(format!("{test_name}/antora-playbook.yml"),TEST_DESC);
teardown(&test_name);
}
#[test]
fn test_runner_init_without_desc() {
let test_name = Uuid::new_v4().to_string();
setup(&test_name);
let config = MyConfig {
command: MyCommand::INIT,
name: Some(test_name.to_string()),
title: None,
skip_git: false,
};
runner_init(config).unwrap();
assert_contains!(format!("{test_name}/antora.yml"),&test_name);
assert_contains!(format!("{test_name}/antora-playbook.yml"),&test_name);
assert_contains!(format!("{test_name}/antora.yml"),&test_name);
assert_contains!(format!("{test_name}/antora-playbook.yml"),&test_name);
teardown(&test_name);
}
#[test]
fn test_git_init() {
let test_name = Uuid::new_v4().to_string();
setup(&test_name);
fs::create_dir_all(&test_name).unwrap();
let config = MyConfig {
command: MyCommand::INIT,
name: Some(test_name.to_string()),
title: None,
skip_git: false,
};
init_git(&config).unwrap();
let git_path = format!("{test_name}/.git");
assert!(fs::metadata(PathBuf::from(&git_path)).is_ok());
assert!(fs::metadata(PathBuf::from(format!("{git_path:}/config"))).is_ok());
assert!(fs::metadata(PathBuf::from(format!("{git_path:}/hooks"))).is_ok());
teardown(&test_name);
}
#[test]
fn test_skip_git() {
let test_name = Uuid::new_v4().to_string();
setup(&test_name);
fs::create_dir_all(&test_name).unwrap();
let config = MyConfig {
command: MyCommand::INIT,
name: Some(test_name.to_string()),
title: None,
skip_git: true,
};
init_git(&config).unwrap();
let git_path = format!("{test_name}/.git");
assert!(fs::metadata(PathBuf::from(&git_path)).is_err());
assert!(fs::metadata(PathBuf::from(format!("{git_path:}/config"))).is_err());
assert!(fs::metadata(PathBuf::from(format!("{git_path:}/hooks"))).is_err());
teardown(&test_name);
}
}