Add a confiugation hierarchy

main
trivernis 10 months ago
parent a4848617c6
commit 74a09087c2
Signed by: Trivernis
GPG Key ID: DFFFCC2C7A02DB45

87
Cargo.lock generated

@ -74,6 +74,15 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "atomic"
version = "0.6.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8d818003e740b63afc82337e3160717f4f63078720a810b7b903e70a5d1d2994"
dependencies = [
"bytemuck",
]
[[package]] [[package]]
name = "backtrace" name = "backtrace"
version = "0.3.69" version = "0.3.69"
@ -129,6 +138,12 @@ dependencies = [
"serde", "serde",
] ]
[[package]]
name = "bytemuck"
version = "1.14.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "374d28ec25809ee0e23827c2ab573d729e293f281dfe393500e7ad618baa61c6"
[[package]] [[package]]
name = "cc" name = "cc"
version = "1.0.83" version = "1.0.83"
@ -270,6 +285,20 @@ dependencies = [
"windows-sys 0.52.0", "windows-sys 0.52.0",
] ]
[[package]]
name = "figment"
version = "0.10.13"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7629b8c7bcd214a072c2c88b263b5bb3ceb54c34365d8c41c1665461aeae0993"
dependencies = [
"atomic",
"pear",
"serde",
"toml",
"uncased",
"version_check",
]
[[package]] [[package]]
name = "form_urlencoded" name = "form_urlencoded"
version = "1.2.1" version = "1.2.1"
@ -403,6 +432,12 @@ dependencies = [
"hashbrown", "hashbrown",
] ]
[[package]]
name = "inlinable_string"
version = "0.1.15"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c8fae54786f62fb2918dcfae3d568594e50eb9b5c25bf04371af6fe7516452fb"
[[package]] [[package]]
name = "is-terminal" name = "is-terminal"
version = "0.4.9" version = "0.4.9"
@ -603,6 +638,29 @@ version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f" checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
name = "pear"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ccca0f6c17acc81df8e242ed473ec144cbf5c98037e69aa6d144780aad103c8"
dependencies = [
"inlinable_string",
"pear_codegen",
"yansi",
]
[[package]]
name = "pear_codegen"
version = "0.2.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "2e22670e8eb757cff11d6c199ca7b987f352f0346e0be4dd23869ec72cb53c77"
dependencies = [
"proc-macro2",
"proc-macro2-diagnostics",
"quote",
"syn",
]
[[package]] [[package]]
name = "percent-encoding" name = "percent-encoding"
version = "2.3.1" version = "2.3.1"
@ -679,6 +737,19 @@ dependencies = [
"unicode-ident", "unicode-ident",
] ]
[[package]]
name = "proc-macro2-diagnostics"
version = "0.10.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "af066a9c399a26e020ada66a034357a868728e72cd426f3adcd35f80d88d88c8"
dependencies = [
"proc-macro2",
"quote",
"syn",
"version_check",
"yansi",
]
[[package]] [[package]]
name = "quote" name = "quote"
version = "1.0.35" version = "1.0.35"
@ -819,6 +890,7 @@ version = "0.1.0"
dependencies = [ dependencies = [
"clap", "clap",
"dirs", "dirs",
"figment",
"git2", "git2",
"globset", "globset",
"handlebars", "handlebars",
@ -1005,6 +1077,15 @@ version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9" checksum = "ed646292ffc8188ef8ea4d1e0e0150fb15a5c2e12ad9b8fc191ae7a8a7f3c4b9"
[[package]]
name = "uncased"
version = "0.9.9"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "9b9bc53168a4be7402ab86c3aad243a84dd7381d09be0eddc81280c1da95ca68"
dependencies = [
"version_check",
]
[[package]] [[package]]
name = "unicode-bidi" name = "unicode-bidi"
version = "0.3.14" version = "0.3.14"
@ -1244,3 +1325,9 @@ checksum = "b7520bbdec7211caa7c4e682eb1fbe07abe20cee6756b6e00f537c82c11816aa"
dependencies = [ dependencies = [
"memchr", "memchr",
] ]
[[package]]
name = "yansi"
version = "1.0.0-rc.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "1367295b8f788d371ce2dbc842c7b709c73ee1364d30351dd300ec2203b12377"

@ -8,6 +8,7 @@ edition = "2021"
[dependencies] [dependencies]
clap = { version = "4.4.14", features = ["derive", "env"] } clap = { version = "4.4.14", features = ["derive", "env"] }
dirs = "5.0.1" dirs = "5.0.1"
figment = { version = "0.10.13", features = ["toml", "env"] }
git2 = "0.18.1" git2 = "0.18.1"
globset = { version = "0.4.14", features = ["serde", "serde1"] } globset = { version = "0.4.14", features = ["serde", "serde1"] }
handlebars = "5.0.0" handlebars = "5.0.0"

@ -0,0 +1,40 @@
use std::{collections::HashMap, path::Path};
use figment::{
providers::{Env, Format, Serialized, Toml},
Figment,
};
use miette::{Context, IntoDiagnostic, Result};
use serde::{Deserialize, Serialize};
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct SiloConfig {
/// Diff tool used to display file differences
pub diff_tool: String,
/// Context values for handlebars that are available globally under the `ctx` variable
pub template_context: HashMap<String, toml::Value>,
}
impl Default for SiloConfig {
fn default() -> Self {
Self {
diff_tool: String::from("diff"),
template_context: HashMap::new(),
}
}
}
/// Read the configuration file from the user config directory
/// with overrides from the `repo.toml` file
/// and the `repo.local.toml` config file
/// and environment variables prefixed with `SILO_``
pub fn read_config(repo: &Path) -> Result<SiloConfig> {
Figment::from(Serialized::defaults(SiloConfig::default()))
.merge(Toml::file(dirs::config_dir().unwrap().join("silo.toml")))
.merge(Toml::file(repo.join("repo.toml")))
.merge(Toml::file(repo.join("repo.local.toml")))
.merge(Env::prefixed("SILO_"))
.extract()
.into_diagnostic()
.context("parsing config file")
}

@ -6,6 +6,7 @@ use miette::{Context, IntoDiagnostic, Result};
use repo::SiloRepo; use repo::SiloRepo;
mod args; mod args;
mod config;
mod repo; mod repo;
mod templating; mod templating;
@ -17,9 +18,11 @@ fn main() -> Result<()> {
args::Command::Init => init(&args)?, args::Command::Init => init(&args)?,
args::Command::Apply => apply(&args)?, args::Command::Apply => apply(&args)?,
args::Command::Context => { args::Command::Context => {
let repo = SiloRepo::open(&args.repo)?;
println!( println!(
"{}", "{}",
serde_json::to_string_pretty(templating::context()).into_diagnostic()? serde_json::to_string_pretty(&templating::context(repo.config.template_context))
.into_diagnostic()?
) )
} }
args::Command::Repo => { args::Command::Repo => {
@ -59,6 +62,10 @@ fn init(args: &Args) -> Result<()> {
let _gitrepo = git2::Repository::init(&args.repo) let _gitrepo = git2::Repository::init(&args.repo)
.into_diagnostic() .into_diagnostic()
.with_context(|| format!("initializing repository at {:?}", args.repo))?; .with_context(|| format!("initializing repository at {:?}", args.repo))?;
fs::write(args.repo.join(".gitignore"), "repo.local.toml\n")
.into_diagnostic()
.context("adding default .gitignore")?;
log::info!("Repo initialized at {:?}", args.repo); log::info!("Repo initialized at {:?}", args.repo);
Ok(()) Ok(())

@ -8,12 +8,16 @@ use std::{
rc::Rc, rc::Rc,
}; };
use crate::templating; use crate::{
config::{read_config, SiloConfig},
templating,
};
use lazy_static::lazy_static; use lazy_static::lazy_static;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct SiloRepo { pub struct SiloRepo {
pub root: DirEntry, pub root: DirEntry,
pub config: SiloConfig,
} }
impl SiloRepo { impl SiloRepo {
@ -23,6 +27,7 @@ impl SiloRepo {
} }
Ok(Self { Ok(Self {
config: read_config(path)?,
root: DirEntry::parse(Rc::new(ParseContext::default()), path.to_owned())?, root: DirEntry::parse(Rc::new(ParseContext::default()), path.to_owned())?,
}) })
} }
@ -31,7 +36,10 @@ impl SiloRepo {
let cwd = env::current_dir() let cwd = env::current_dir()
.into_diagnostic() .into_diagnostic()
.context("get current dir")?; .context("get current dir")?;
self.root.apply(&cwd) let ctx = ApplyContext {
config: self.config.clone(),
};
self.root.apply(&ctx, &cwd)
} }
} }
@ -53,6 +61,10 @@ impl Default for ParseContext {
} }
} }
pub struct ApplyContext {
config: SiloConfig,
}
lazy_static! { lazy_static! {
static ref IGNORED_PATHS: GlobSet = GlobSetBuilder::new() static ref IGNORED_PATHS: GlobSet = GlobSetBuilder::new()
.add(Glob::new("**/.git").unwrap()) .add(Glob::new("**/.git").unwrap())
@ -118,9 +130,9 @@ impl DirEntry {
} }
} }
fn apply(&self, cwd: &Path) -> Result<()> { fn apply(&self, ctx: &ApplyContext, cwd: &Path) -> Result<()> {
match self { match self {
DirEntry::File(file) => file.apply(cwd), DirEntry::File(file) => file.apply(ctx, cwd),
DirEntry::Dir(p, children) => { DirEntry::Dir(p, children) => {
let cwd = if p != cwd { let cwd = if p != cwd {
let cwd = cwd.join(p.file_name().unwrap()); let cwd = cwd.join(p.file_name().unwrap());
@ -135,14 +147,17 @@ impl DirEntry {
p.to_owned() p.to_owned()
}; };
for child in children { for child in children {
child.apply(&cwd)?; child.apply(ctx, &cwd)?;
} }
Ok(()) Ok(())
} }
DirEntry::Root(_, data, children) => { DirEntry::Root(_, data, children) => {
let engine = templating::engine(); let engine = templating::engine();
let rendered_path = engine let rendered_path = engine
.render_template(&data.path, templating::context()) .render_template(
&data.path,
&templating::context(&ctx.config.template_context),
)
.into_diagnostic() .into_diagnostic()
.with_context(|| format!("render template {}", data.path))?; .with_context(|| format!("render template {}", data.path))?;
@ -155,7 +170,7 @@ impl DirEntry {
.with_context(|| format!("Creating directory {cwd:?}"))?; .with_context(|| format!("Creating directory {cwd:?}"))?;
} }
for child in children { for child in children {
child.apply(&cwd)?; child.apply(ctx, &cwd)?;
} }
Ok(()) Ok(())
} }
@ -180,14 +195,17 @@ impl FileEntry {
} }
} }
fn apply(&self, cwd: &Path) -> Result<()> { fn apply(&self, ctx: &ApplyContext, cwd: &Path) -> Result<()> {
match self { match self {
FileEntry::Template(path) => { FileEntry::Template(path) => {
log::debug!("Processing template {path:?}"); log::debug!("Processing template {path:?}");
let contents = fs::read_to_string(path).into_diagnostic()?; let contents = fs::read_to_string(path).into_diagnostic()?;
let rendered = templating::engine() let rendered = templating::engine()
.render_template(&contents, templating::context()) .render_template(
&contents,
&templating::context(&ctx.config.template_context),
)
.into_diagnostic() .into_diagnostic()
.with_context(|| format!("rendering template {path:?}"))?; .with_context(|| format!("rendering template {path:?}"))?;
@ -237,7 +255,7 @@ impl RootDirData {
.into_diagnostic() .into_diagnostic()
.with_context(|| format!("reading metadata file {path:?}"))?; .with_context(|| format!("reading metadata file {path:?}"))?;
let rendered = templating::engine() let rendered = templating::engine()
.render_template(&contents, templating::context()) .render_template(&contents, &templating::context(()))
.into_diagnostic() .into_diagnostic()
.with_context(|| format!("processing template {path:?}"))?; .with_context(|| format!("processing template {path:?}"))?;
toml::from_str(&rendered) toml::from_str(&rendered)

@ -11,11 +11,18 @@ pub fn engine<'a>() -> Handlebars<'a> {
hb hb
} }
pub fn context<'a>() -> &'a ContextData { pub fn context<'a, T: Serialize>(ctx: T) -> WrappedContext<'a, T> {
lazy_static! { lazy_static! {
static ref CTX: ContextData = ContextData::default(); static ref CTX: ContextData = ContextData::default();
} }
&*CTX WrappedContext { data: &*CTX, ctx }
}
#[derive(Serialize)]
pub struct WrappedContext<'a, T: Serialize> {
#[serde(flatten)]
data: &'a ContextData,
ctx: T,
} }
#[derive(Clone, Debug, Serialize, Default)] #[derive(Clone, Debug, Serialize, Default)]

Loading…
Cancel
Save