Combined with another recent addition (inserting hostnames into Tera contexts) this patch will allow us to generate different configurations for different hosts based on the same template file.
We intend for users to either remove the now-generated config file
from version control or to rename it to something like config-gen
and place config-gen
in their VCS's ignore file.
ZMZLQNXYGTBEA5DOI3KIIHAAWJFDJFVCX2Q5QJWOVYAW3355WYWQC
let file: PathBuf = if link.file.is_absolute() {
link.file.to_path_buf()
} else {
let mut path = install_path.canonicalize()?;
path.push(&link.file);
path
};
let link: PathBuf = if link.link.is_absolute() {
link.link.to_path_buf()
} else {
let mut path = install_path.canonicalize()?;
path.push(&link.link);
path
};
// Normalize paths
let file_path: PathBuf = path_rel_to_install(&link.file, install_path)?;
let link_path: PathBuf = path_rel_to_install(&link.link, install_path)?;
let template_path: Option<Result<PathBuf, _>> = link
.template
.as_ref()
.map(|path| path_rel_to_install(path, install_path));
let file_exists = !matches!(
link.symlink_metadata().map_err(|e| e.kind()),
// If we're given a template path, we render that template using the regular
// `Context` and write the result out to `file_path`. This is so the user
// can programmatically generate their dotfiles -- at least in a basic way.
if let Some(template_path) = template_path {
let template_path = template_path?;
match generate_template(&template_path, &file_path, ctx) {
Ok(s) => s,
Err(e) => {
tracing::warn!(
"couldn't render Tera template {template_path:?}: {}\n\t\
(skipping this link)",
display_anyhow_err_chain(e)
);
continue;
}
}
}
// If the link location has a file or link already, either skip
// this iteration or delete the offending file/link.
let link_loc_exists = !matches!(
link_path.symlink_metadata().map_err(|e| e.kind()),
}
}
fn generate_template(template_path: &Path, file_path: &Path, ctx: &Context) -> anyhow::Result<()> {
let template_contents = std::fs::read_to_string(&template_path)?;
let rendered: String = Tera::one_off(&template_contents, ctx, false)?;
// Notify the user if we've generated a new version of the file.
let old_rendered = std::fs::read_to_string(&file_path).unwrap_or_default();
if rendered == old_rendered {
tracing::debug!(
"found template for {file_path:?} \
but rendered contents are unchanged: skipping."
);
} else {
std::fs::write(&file_path, &rendered)?;
tracing::info!(
"found template for {file_path:?} \
and rendered contents were updated"
);
fn display_anyhow_err_chain(top_level_err: anyhow::Error) -> String {
let mut display_chain = String::new();
for err in top_level_err.chain() {
display_chain.push_str(&format!("\n\t{}", err));
}
display_chain
}
fn path_rel_to_install(path: &Path, install: &Path) -> anyhow::Result<PathBuf> {
if path.is_absolute() {
Ok(path.to_path_buf())
} else {
let mut canon_path = install.canonicalize()?;
canon_path.push(&path);
Ok(canon_path)
}
}