use crate::generate::TomlMap;
use anyhow::{Context, Result};
use serde::Deserialize;
use std::{
fs,
path::{Path, PathBuf},
};
pub(crate) const CONFIG_FILE_NAME: &str = "project-generate.toml";
#[derive(Deserialize, Debug, Default, PartialEq)]
pub(crate) struct Config {
pub(crate) template: Option<TemplateConfig>,
#[serde(default)]
pub(crate) placeholders: Vec<TomlMap>,
}
#[derive(Deserialize, Debug, PartialEq)]
pub(crate) struct ConfigValues {
pub(crate) values: TomlMap,
}
#[derive(Default, Deserialize, Debug, Eq, PartialEq)]
pub(crate) struct TemplateConfig {
#[serde(default)]
pub(crate) exclude: Vec<String>,
#[serde(default)]
pub(crate) raw: Vec<String>,
#[serde(default)]
pub(crate) rename: Vec<RenameConfig>,
}
#[derive(Clone, Deserialize, Debug, Eq, PartialEq)]
pub(crate) struct RenameConfig {
pub(crate) from: PathBuf,
pub(crate) to: String,
}
#[derive(Deserialize, Debug, PartialEq)]
pub(crate) struct TemplateSlotsTable(pub(crate) TomlMap);
impl Config {
pub(crate) fn from_path<P>(path: &P) -> Result<Self>
where
P: AsRef<Path>,
{
let contents = fs::read_to_string(path).with_context(|| {
format!(
"Error reading template configuration `{}`",
&path.as_ref().display()
)
})?;
let config = toml::from_str::<Config>(&contents).with_context(|| {
format!(
"Error parsing template configuration '{}'",
&path.as_ref().display()
)
})?;
Ok(config)
}
pub(crate) fn exclude(&mut self, path: String) {
if let Some(ref mut tc) = self.template {
tc.exclude.push(path);
} else {
self.template = Some(TemplateConfig {
exclude: vec![path],
..Default::default()
});
}
}
}
#[cfg(test)]
mod tests {
use super::*;
use anyhow::anyhow;
use std::{fs::File, io::Write};
use tempfile::tempdir;
use toml::Value;
fn parse_config(contents: &str) -> Result<Config> {
toml::from_str::<Config>(contents).map_err(|e| anyhow!("invalid config syntax: {}", e))
}
#[test]
fn test_deserializes_config() {
let test_dir = tempdir().unwrap();
let config_path = test_dir.path().join(CONFIG_FILE_NAME);
let mut file = File::create(&config_path).unwrap();
file.write_all(
r#"
[template]
exclude = ["ignore.txt"]
raw = [ "data.bin" ]
rename = [ { from="README.alt.md", to="README.md" } ]
[[placeholders]]
name="value"
type="string"
prompt="type something"
"#
.as_bytes(),
)
.unwrap();
let config = Config::from_path(&config_path).unwrap();
assert_eq!(
config.template,
Some(TemplateConfig {
exclude: vec!["ignore.txt".into()],
raw: vec!["data.bin".into()],
rename: vec![RenameConfig {
from: "README.alt.md".into(),
to: "README.md".into()
}]
})
);
assert_eq!(config.placeholders.len(), 1);
}
#[test]
fn config_deser_placeholders() {
let result = parse_config(
r#"
[[placeholders]]
name="a"
type = "bool"
prompt = "foo"
default = false
[[placeholders]]
name="b"
type = "string"
prompt = "bar"
"#,
);
if let Err(e) = &result {
eprintln!("result error: {e}");
}
assert!(result.is_ok(), "Config should have parsed");
let result = result.unwrap();
assert_eq!(result.placeholders.len(), 2);
let pa = result.placeholders.first().unwrap();
let pb = result.placeholders.get(1).unwrap();
assert_eq!(pa.len(), 4);
assert_eq!(pb.len(), 3);
assert_eq!(pa.get("name"), Some(&Value::String("a".into())));
assert_eq!(pa.get("type"), Some(&Value::String("bool".to_string())));
assert_eq!(pa.get("prompt"), Some(&Value::String("foo".to_string())));
assert_eq!(pa.get("default"), Some(&Value::Boolean(false)));
assert_eq!(pb.get("name"), Some(&Value::String("b".into())));
assert_eq!(pb.get("type"), Some(&Value::String("string".to_string())));
assert_eq!(pb.get("prompt"), Some(&Value::String("bar".to_string())));
}
}