feat: rename cuddle_cli -> cuddle
Some checks failed
continuous-integration/drone/push Build is failing
Some checks failed
continuous-integration/drone/push Build is failing
Signed-off-by: kjuulh <contact@kjuulh.io>
This commit is contained in:
218
cuddle/src/cli/subcommands/init.rs
Normal file
218
cuddle/src/cli/subcommands/init.rs
Normal file
@@ -0,0 +1,218 @@
|
||||
use std::collections::BTreeMap;
|
||||
use std::fs::{create_dir_all, read, read_dir};
|
||||
use std::io::Write;
|
||||
use std::path::PathBuf;
|
||||
|
||||
use clap::{ArgMatches, Command};
|
||||
use walkdir::WalkDir;
|
||||
|
||||
use crate::cli::CuddleCli;
|
||||
|
||||
pub fn build_command(root_cmd: Command, _cli: CuddleCli) -> Command {
|
||||
let mut repo_url = clap::Arg::new("repo").long("repo").short('r');
|
||||
|
||||
if let Ok(cuddle_template_url) = std::env::var("CUDDLE_TEMPLATE_URL") {
|
||||
repo_url = repo_url.default_value(cuddle_template_url);
|
||||
} else {
|
||||
repo_url = repo_url.required(true);
|
||||
}
|
||||
|
||||
let execute_cmd = Command::new("init")
|
||||
.about("init bootstraps a repository from a template")
|
||||
.arg(repo_url)
|
||||
.arg(clap::Arg::new("name").long("name"))
|
||||
.arg(clap::Arg::new("path").long("path"))
|
||||
.arg(clap::Arg::new("value").short('v').long("value"));
|
||||
|
||||
root_cmd.subcommand(execute_cmd)
|
||||
}
|
||||
|
||||
pub fn execute_init(exe_submatch: &ArgMatches, _cli: CuddleCli) -> anyhow::Result<()> {
|
||||
let repo = exe_submatch.get_one::<String>("repo").unwrap();
|
||||
let name = exe_submatch.get_one::<String>("name");
|
||||
let path = exe_submatch.get_one::<String>("path");
|
||||
let values = exe_submatch
|
||||
.get_many::<String>("value")
|
||||
.unwrap_or_default()
|
||||
.collect::<Vec<_>>();
|
||||
|
||||
tracing::info!("Downloading: {}", repo);
|
||||
|
||||
create_dir_all(std::env::temp_dir())?;
|
||||
|
||||
let tmpdir = tempfile::tempdir()?;
|
||||
|
||||
let tmpdir_path = tmpdir.path().canonicalize()?;
|
||||
|
||||
let output = std::process::Command::new("git")
|
||||
.args(&["clone", repo, "."])
|
||||
.current_dir(tmpdir_path)
|
||||
.output()?;
|
||||
|
||||
std::io::stdout().write_all(&output.stdout)?;
|
||||
std::io::stderr().write_all(&output.stderr)?;
|
||||
|
||||
let templates_path = tmpdir.path().join("cuddle-templates.json");
|
||||
let template_path = tmpdir.path().join("cuddle-template.json");
|
||||
let templates = if templates_path.exists() {
|
||||
let templates = read(templates_path)?;
|
||||
let templates: CuddleTemplates = serde_json::from_slice(&templates)?;
|
||||
|
||||
let mut single_templates = Vec::new();
|
||||
|
||||
for template_name in templates.templates.iter() {
|
||||
let template = read(
|
||||
tmpdir
|
||||
.path()
|
||||
.join(template_name)
|
||||
.join("cuddle-template.json"),
|
||||
)?;
|
||||
let template = serde_json::from_slice::<CuddleTemplate>(&template)?;
|
||||
|
||||
single_templates.push((template_name, template))
|
||||
}
|
||||
|
||||
single_templates
|
||||
.into_iter()
|
||||
.map(|(name, template)| (name.clone(), tmpdir.path().join(name), template))
|
||||
.collect::<Vec<_>>()
|
||||
} else if template_path.exists() {
|
||||
let template = read(template_path)?;
|
||||
let template = serde_json::from_slice::<CuddleTemplate>(&template)?;
|
||||
vec![(template.clone().name, tmpdir.path().to_path_buf(), template)]
|
||||
} else {
|
||||
anyhow::bail!("No cuddle-template.json or cuddle-templates.json found");
|
||||
};
|
||||
|
||||
let template = match name {
|
||||
Some(name) => {
|
||||
let template = read(tmpdir.path().join(name).join("cuddle-template.json"))?;
|
||||
let template = serde_json::from_slice::<CuddleTemplate>(&template)?;
|
||||
Ok((name.clone(), tmpdir.path().join(name), template))
|
||||
}
|
||||
None => {
|
||||
if templates.len() > 1 {
|
||||
let name = inquire::Select::new(
|
||||
"template",
|
||||
templates.iter().map(|t| t.0.clone()).collect(),
|
||||
)
|
||||
.with_help_message("name of which template to use")
|
||||
.prompt()?;
|
||||
|
||||
let found_template = templates
|
||||
.iter()
|
||||
.find(|item| item.0 == name)
|
||||
.ok_or(anyhow::anyhow!("could not find an item with that name"))?;
|
||||
|
||||
Ok(found_template.clone())
|
||||
} else if templates.len() == 1 {
|
||||
Ok(templates[0].clone())
|
||||
} else {
|
||||
Err(anyhow::anyhow!("No templates found, with any valid names"))
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
let (_name, template_dir, mut template) = template?;
|
||||
|
||||
let path = match path {
|
||||
Some(path) => path.clone(),
|
||||
None => inquire::Text::new("path")
|
||||
.with_help_message("to where it should be placed")
|
||||
.with_default(".")
|
||||
.prompt()?,
|
||||
};
|
||||
|
||||
create_dir_all(&path)?;
|
||||
let dir = std::fs::read_dir(&path)?;
|
||||
if dir.count() != 0 {
|
||||
for entry in read_dir(&path)? {
|
||||
let entry = entry?;
|
||||
if entry.file_name() == ".git" {
|
||||
continue;
|
||||
} else {
|
||||
anyhow::bail!("Directory {} is not empty", &path);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
{
|
||||
if let Some(ref mut prompt) = template.prompt {
|
||||
'prompt: for (name, prompt) in prompt {
|
||||
for value in &values {
|
||||
if let Some((value_name, value_content)) = value.split_once("=") {
|
||||
if value_name == name {
|
||||
prompt.value = value_content.to_string();
|
||||
continue 'prompt;
|
||||
}
|
||||
}
|
||||
}
|
||||
let value = inquire::Text::new(&name)
|
||||
.with_help_message(&prompt.description)
|
||||
.prompt()?;
|
||||
prompt.value = value;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for entry in WalkDir::new(&template_dir).follow_links(false) {
|
||||
let entry = entry?;
|
||||
let entry_path = entry.path();
|
||||
|
||||
let new_path = PathBuf::from(&path).join(entry_path.strip_prefix(&template_dir)?);
|
||||
let new_path = replace_with_variables(&new_path.to_string_lossy().to_string(), &template)?;
|
||||
let new_path = PathBuf::from(new_path);
|
||||
|
||||
if entry_path.is_dir() {
|
||||
create_dir_all(&new_path)?;
|
||||
}
|
||||
|
||||
if entry_path.is_file() {
|
||||
let name = entry.file_name();
|
||||
if let Some(parent) = entry_path.parent() {
|
||||
create_dir_all(parent)?;
|
||||
}
|
||||
|
||||
if name == "cuddle-template.json" || name == "cuddle-templates.json" {
|
||||
continue;
|
||||
}
|
||||
|
||||
tracing::info!("writing to: {}", new_path.display());
|
||||
let new_content =
|
||||
replace_with_variables(&std::fs::read_to_string(entry_path)?, &template)?;
|
||||
|
||||
std::fs::write(new_path, new_content.as_bytes())?;
|
||||
}
|
||||
}
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn replace_with_variables(content: &str, template: &CuddleTemplate) -> anyhow::Result<String> {
|
||||
let mut content = content.to_string();
|
||||
if let Some(prompt) = &template.prompt {
|
||||
for (name, value) in prompt {
|
||||
content = content.replace(&format!("%%{}%%", name), &value.value);
|
||||
}
|
||||
}
|
||||
|
||||
Ok(content)
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
struct CuddleTemplates {
|
||||
pub templates: Vec<String>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
struct CuddleTemplate {
|
||||
pub name: String,
|
||||
pub prompt: Option<BTreeMap<String, CuddleTemplatePrompt>>,
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, serde::Serialize, serde::Deserialize)]
|
||||
struct CuddleTemplatePrompt {
|
||||
pub description: String,
|
||||
#[serde(skip)]
|
||||
pub value: String,
|
||||
}
|
Reference in New Issue
Block a user