Operate on tmp files and prompt for changes at the end

main
trivernis 8 months ago
parent efd1d54c2c
commit c4d2d3c72b
Signed by: Trivernis
GPG Key ID: 7E6D18B61C8D2F4B

243
Cargo.lock generated

@ -39,6 +39,15 @@ dependencies = [
"memchr",
]
[[package]]
name = "ansi_term"
version = "0.12.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d52a9bb7ec0cf484c551830a7ce27bd20d67eac647e1befb56b0be4ee39a55d2"
dependencies = [
"winapi",
]
[[package]]
name = "anstream"
version = "0.6.7"
@ -102,6 +111,17 @@ dependencies = [
"bytemuck",
]
[[package]]
name = "atty"
version = "0.2.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d9b39be18770d11421cdb1b9947a45dd3f37e93092cbf377614828a319d5fee8"
dependencies = [
"hermit-abi 0.1.19",
"libc",
"winapi",
]
[[package]]
name = "autocfg"
version = "1.1.0"
@ -399,6 +419,21 @@ dependencies = [
"chksum-hash-sha2-512",
]
[[package]]
name = "clap"
version = "2.34.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a0610544180c38b88101fecf2dd634b174a62eef6946f84dfc6a7127512b381c"
dependencies = [
"ansi_term",
"atty",
"bitflags 1.3.2",
"strsim 0.8.0",
"textwrap 0.11.0",
"unicode-width",
"vec_map",
]
[[package]]
name = "clap"
version = "4.4.17"
@ -418,7 +453,7 @@ dependencies = [
"anstream",
"anstyle",
"clap_lex",
"strsim",
"strsim 0.10.0",
]
[[package]]
@ -427,7 +462,7 @@ version = "4.4.7"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "cf9804afaaf59a91e75b022a30fb7229a7901f60c755489cc61c9b423b836442"
dependencies = [
"heck",
"heck 0.4.1",
"proc-macro2",
"quote",
"syn 2.0.48",
@ -457,7 +492,7 @@ version = "0.15.8"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0e1f83fc076bd6dd27517eacdf25fef6c4dfe5f1d7448bafaaf3a26f13b5e4eb"
dependencies = [
"encode_unicode",
"encode_unicode 0.3.6",
"lazy_static",
"libc",
"unicode-width",
@ -508,6 +543,27 @@ dependencies = [
"typenum",
]
[[package]]
name = "csv"
version = "1.3.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "ac574ff4d437a7b5ad237ef331c17ccca63c46479e5b5453eb8e10bb99a759fe"
dependencies = [
"csv-core",
"itoa",
"ryu",
"serde",
]
[[package]]
name = "csv-core"
version = "0.1.11"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "5efa2b3d7902f4b634a20cae3c9c4e6209dc4779feb6863329607560143efa70"
dependencies = [
"memchr",
]
[[package]]
name = "dashmap"
version = "5.5.3"
@ -562,6 +618,16 @@ dependencies = [
"dirs-sys",
]
[[package]]
name = "dirs-next"
version = "2.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "b98cf8ebf19c3d1b223e151f99a4f9f0690dca41414773390fc824184ac833e1"
dependencies = [
"cfg-if",
"dirs-sys-next",
]
[[package]]
name = "dirs-sys"
version = "0.4.1"
@ -574,12 +640,23 @@ dependencies = [
"windows-sys 0.48.0",
]
[[package]]
name = "dirs-sys-next"
version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4ebda144c4fe02d1f7ea1a7d9641b6fc6b580adcfa024ae48797ecdeb6825b4d"
dependencies = [
"libc",
"redox_users",
"winapi",
]
[[package]]
name = "dot-silo"
version = "0.4.1"
dependencies = [
"chksum",
"clap",
"clap 4.4.17",
"dialoguer",
"dirs",
"figment",
@ -591,6 +668,7 @@ dependencies = [
"log",
"miette",
"pretty_env_logger",
"prettydiff",
"serde",
"serde_json",
"sys-info",
@ -617,6 +695,12 @@ version = "0.3.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a357d28ed41a50f9c765dbfe56cbc04a64e53e5fc58ba79fbc34c10ef3df831f"
[[package]]
name = "encode_unicode"
version = "1.0.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "34aa73646ffb006b8f5147f3dc182bd4bcb190227ce861fc4a4844bf8e3cb2c0"
[[package]]
name = "encoding_rs"
version = "0.8.33"
@ -1623,12 +1707,30 @@ version = "0.14.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "290f1a1d9242c78d09ce40a5e87e7554ee637af1351968159f4952f028f75604"
[[package]]
name = "heck"
version = "0.3.3"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "6d621efb26863f0e9924c6ac577e8275e5e6b77455db64ffa6c65c904e9e132c"
dependencies = [
"unicode-segmentation",
]
[[package]]
name = "heck"
version = "0.4.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "95505c38b4572b2d910cecb0281560f54b440a19336cbbcb27bf6ce6adc6f5a8"
[[package]]
name = "hermit-abi"
version = "0.1.19"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "62b467343b94ba476dcb2500d242dadbb39557df889310ac77c5d99100aaac33"
dependencies = [
"libc",
]
[[package]]
name = "hermit-abi"
version = "0.3.3"
@ -1780,7 +1882,7 @@ version = "0.4.10"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0bad00257d07be169d870ab665980b06cdb366d792ad690bf2e76876dc503455"
dependencies = [
"hermit-abi",
"hermit-abi 0.3.3",
"rustix",
"windows-sys 0.52.0",
]
@ -1903,7 +2005,7 @@ dependencies = [
"supports-hyperlinks",
"supports-unicode",
"terminal_size",
"textwrap",
"textwrap 0.15.2",
"thiserror",
"unicode-width",
]
@ -1960,7 +2062,7 @@ version = "1.16.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "4161fcb6d602d4d2081af7c3a45852d875a03dd337a6bfdd6e06407b61342a43"
dependencies = [
"hermit-abi",
"hermit-abi 0.3.3",
"libc",
]
@ -2000,6 +2102,15 @@ version = "3.5.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c1b04fb49957986fdce4d6ee7a65027d55d4b6d2265e5848bbb507b58ccfdb6f"
[[package]]
name = "pad"
version = "0.1.6"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d2ad9b889f1b12e0b9ee24db044b5129150d5eada288edc800f789928dc8c0e3"
dependencies = [
"unicode-width",
]
[[package]]
name = "parking_lot"
version = "0.12.1"
@ -2125,6 +2236,56 @@ dependencies = [
"log",
]
[[package]]
name = "prettydiff"
version = "0.6.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ff1fec61082821f8236cf6c0c14e8172b62ce8a72a0eedc30d3b247bb68dc11"
dependencies = [
"ansi_term",
"pad",
"prettytable-rs",
"structopt",
]
[[package]]
name = "prettytable-rs"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "eea25e07510aa6ab6547308ebe3c036016d162b8da920dbb079e3ba8acf3d95a"
dependencies = [
"csv",
"encode_unicode 1.0.0",
"is-terminal",
"lazy_static",
"term",
"unicode-width",
]
[[package]]
name = "proc-macro-error"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "da25490ff9892aab3fcf7c36f08cfb902dd3e71ca0f9f9517bea02a73a5ce38c"
dependencies = [
"proc-macro-error-attr",
"proc-macro2",
"quote",
"syn 1.0.109",
"version_check",
]
[[package]]
name = "proc-macro-error-attr"
version = "1.0.4"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a1be40180e52ecc98ad80b184934baf3d0d29f979574e439af5a55274b35f869"
dependencies = [
"proc-macro2",
"quote",
"version_check",
]
[[package]]
name = "proc-macro2"
version = "1.0.76"
@ -2318,6 +2479,12 @@ dependencies = [
"untrusted",
]
[[package]]
name = "rustversion"
version = "1.0.14"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "7ffc183a10b4478d04cbbbfc96d0873219d962dd5accaff2ffbd4ceb7df837f4"
[[package]]
name = "ryu"
version = "1.0.16"
@ -2467,12 +2634,42 @@ version = "1.1.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "a2eb9349b6444b326872e140eb1cf5e7c522154d69e7a0ffb0fb81c06b37543f"
[[package]]
name = "strsim"
version = "0.8.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "8ea5119cdb4c55b55d432abb513a0429384878c15dde60cc77b1c99de1a95a6a"
[[package]]
name = "strsim"
version = "0.10.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "73473c0e59e6d5812c5dfe2a064a6444949f089e20eec9a2e5506596494e4623"
[[package]]
name = "structopt"
version = "0.3.26"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "0c6b5c64445ba8094a6ab0c3cd2ad323e07171012d9c98b0b15651daf1787a10"
dependencies = [
"clap 2.34.0",
"lazy_static",
"structopt-derive",
]
[[package]]
name = "structopt-derive"
version = "0.4.18"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "dcb5ae327f9cc13b68763b5749770cb9e048a99bd9dfdfa58d0cf05d5f64afe0"
dependencies = [
"heck 0.3.3",
"proc-macro-error",
"proc-macro2",
"quote",
"syn 1.0.109",
]
[[package]]
name = "supports-color"
version = "2.1.0"
@ -2567,6 +2764,17 @@ dependencies = [
"windows-sys 0.52.0",
]
[[package]]
name = "term"
version = "0.7.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "c59df8ac95d96ff9bede18eb7300b0fda5e5d8d90960e76f8e14ae765eedbf1f"
dependencies = [
"dirs-next",
"rustversion",
"winapi",
]
[[package]]
name = "termcolor"
version = "1.4.1"
@ -2586,6 +2794,15 @@ dependencies = [
"winapi",
]
[[package]]
name = "textwrap"
version = "0.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d326610f408c7a4eb6f51c37c330e496b08506c9457c9d34287ecc38809fb060"
dependencies = [
"unicode-width",
]
[[package]]
name = "textwrap"
version = "0.15.2"
@ -2822,6 +3039,12 @@ dependencies = [
"tinyvec",
]
[[package]]
name = "unicode-segmentation"
version = "1.11.0"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "d4c87d22b6e3f4a18d4d40ef354e97c90fcb14dd91d7dc0aa9d8a1172ebf7202"
[[package]]
name = "unicode-width"
version = "0.1.11"
@ -2851,6 +3074,12 @@ version = "0.2.1"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "711b9620af191e0cdc7468a8d14e709c3dcdb115b36f838e601583af800a370a"
[[package]]
name = "vec_map"
version = "0.8.2"
source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "f1bddf1187be692e79c5ffeab891132dfb0f236ed36a43c7ed39f1165ee20191"
[[package]]
name = "version_check"
version = "0.9.4"

@ -24,6 +24,7 @@ lazy_static = "1.4.0"
log = "0.4.20"
miette = { version = "5.10.0", features = ["serde", "fancy"] }
pretty_env_logger = "0.5.0"
prettydiff = "0.6.4"
serde = { version = "1.0.195", features = ["derive"] }
serde_json = "1.0.111"
sys-info = "0.9.1"

@ -0,0 +1,123 @@
use std::{
fs::{self, File},
io::Write,
mem,
path::{Path, PathBuf},
};
use chksum::sha2_256::chksum;
use dialoguer::Confirm;
use miette::{Context, IntoDiagnostic, Result};
use prettydiff::diff_lines;
use tempfile::NamedTempFile;
use super::FsAccess;
pub struct BufferedFsAccess {
mappings: Vec<(NamedTempFile, PathBuf)>,
}
impl BufferedFsAccess {
pub fn new() -> Self {
Self {
mappings: Vec::new(),
}
}
}
impl FsAccess for BufferedFsAccess {
fn write_all(&mut self, dst: &std::path::Path, buf: &[u8]) -> miette::Result<()> {
let mut tmp = tmpfile()?;
tmp.write_all(buf).into_diagnostic().with_context(|| {
format!(
"writing {} bytes to file contents {:?}",
buf.len(),
tmp.path()
)
})?;
self.mappings.push((tmp, dst.to_owned()));
Ok(())
}
fn copy(&mut self, src: &std::path::Path, dst: &std::path::Path) -> miette::Result<()> {
let tmp = tmpfile()?;
fs::copy(src, tmp.path())
.into_diagnostic()
.with_context(|| format!("copying {src:?} to {:?}", tmp.path()))?;
self.mappings.push((tmp, dst.to_owned()));
Ok(())
}
fn persist(&mut self) -> Result<()> {
let mappings = mem::take(&mut self.mappings);
let mut drop_list = Vec::new();
for (tmp, dst) in mappings {
if confirm_write(tmp.path(), &dst)? {
ensure_parent(dst.parent().unwrap())?;
fs::copy(tmp.path(), &dst)
.into_diagnostic()
.with_context(|| format!("copying {:?} to {dst:?}", tmp.path()))?;
log::info!("Updated {dst:?}");
} else {
log::info!("Skipping {dst:?}");
}
drop_list.push(tmp);
}
mem::drop(drop_list);
Ok(())
}
}
fn tmpfile() -> Result<NamedTempFile> {
NamedTempFile::new()
.into_diagnostic()
.context("failed to create tmp file")
}
fn confirm_write(new: &Path, old: &Path) -> Result<bool> {
if !old.exists() {
return Ok(true);
}
let f1 = File::open(new)
.into_diagnostic()
.with_context(|| format!("opening file {new:?}"))?;
let f2 = File::open(old)
.into_diagnostic()
.with_context(|| format!("opening file {old:?}"))?;
if chksum(&f1).into_diagnostic()?.as_bytes() == chksum(&f2).into_diagnostic()?.as_bytes() {
return Ok(true);
}
let cont_new = fs::read_to_string(new)
.into_diagnostic()
.context("reading a")?;
let cont_old = fs::read_to_string(old)
.into_diagnostic()
.context("reading b")?;
println!(
"\n=== Changes to {old:?}\n{}\n=== End of Changes\n",
diff_lines(&cont_old, &cont_new)
);
Confirm::new()
.with_prompt("Do you want to apply these changes?")
.interact()
.into_diagnostic()
}
fn ensure_parent(parent: &Path) -> Result<(), miette::ErrReport> {
if parent.exists() {
return Ok(());
}
log::info!("Creating {parent:?}");
fs::create_dir_all(&parent)
.into_diagnostic()
.with_context(|| format!("Creating directory {parent:?}"))
}

@ -0,0 +1,16 @@
use miette::Result;
use std::path::Path;
mod buffered;
pub use buffered::BufferedFsAccess;
pub trait FsAccess {
/// Write all bytes to dst
fn write_all(&mut self, dst: &Path, buf: &[u8]) -> Result<()>;
/// Copy src to dst
fn copy(&mut self, src: &Path, dst: &Path) -> Result<()>;
/// Persist the changes if necessary
fn persist(&mut self) -> Result<()>;
}

@ -8,6 +8,7 @@ use repo::SiloRepo;
mod args;
mod config;
mod fs_access;
mod repo;
mod templating;

@ -1,21 +1,16 @@
use std::{
fs::{self, File},
io::Write,
fs::{self},
path::{Path, PathBuf},
process::Command,
rc::Rc,
};
use crate::templating;
use super::{ApplyContext, ParseContext};
use chksum::sha2_256::chksum;
use dialoguer::Confirm;
use globset::{Glob, GlobSet, GlobSetBuilder};
use lazy_static::lazy_static;
use miette::{Context, IntoDiagnostic, Result};
use serde::{Deserialize, Serialize};
use tempfile::NamedTempFile;
#[derive(Clone, Debug)]
pub struct Contents {
@ -28,7 +23,7 @@ impl Contents {
Ok(Self { root })
}
pub fn apply(&self, actx: &ApplyContext, cwd: &Path) -> Result<()> {
pub fn apply(&self, actx: &mut ApplyContext, cwd: &Path) -> Result<()> {
self.root.apply(actx, cwd)
}
}
@ -105,17 +100,12 @@ impl DirEntry {
}
}
fn apply(&self, ctx: &ApplyContext, cwd: &Path) -> Result<()> {
fn apply(&self, ctx: &mut ApplyContext, cwd: &Path) -> Result<()> {
match self {
DirEntry::File(file) => {
ensure_cwd(cwd)?;
file.apply(ctx, cwd)
}
DirEntry::File(file) => file.apply(ctx, cwd),
DirEntry::Dir(p, children) => {
let cwd = if p != cwd {
let cwd = cwd.join(p.file_name().unwrap());
ensure_cwd(&cwd)?;
cwd
cwd.join(p.file_name().unwrap())
} else {
p.to_owned()
};
@ -154,39 +144,24 @@ impl FileEntry {
}
}
fn apply(&self, ctx: &ApplyContext, cwd: &Path) -> Result<()> {
fn apply(&self, ctx: &mut ApplyContext, cwd: &Path) -> Result<()> {
match self {
FileEntry::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 new_path = path.with_extension("");
let filename = new_path.file_name().unwrap();
let dest = cwd.join(filename);
let dest = cwd.join(filename);
let render_contents = templating::render(&contents, &ctx.config.template_context)?;
if confirm_changes(&ctx.config.diff_tool, &render_contents, &dest)? {
log::info!("Render {path:?} -> {dest:?}");
fs::write(&dest, render_contents)
.into_diagnostic()
.context("writing changes")?;
} else {
log::info!("Skipping {path:?} !-> {dest:?}");
}
ctx.fs.write_all(&dest, &render_contents.into_bytes())?
}
FileEntry::Plain(path) => {
let filename = path.file_name().unwrap();
let dest = cwd.join(filename);
if confirm_write(&ctx.config.diff_tool, path, &dest)? {
log::info!("Copying {path:?} -> {dest:?}");
fs::copy(path, &dest)
.into_diagnostic()
.with_context(|| format!("copy {path:?} to {dest:?}"))?;
} else {
log::info!("Skipping {path:?} !-> {dest:?}");
}
ctx.fs.copy(path, &dest)?;
}
}
@ -221,52 +196,3 @@ impl RootDirData {
.with_context(|| format!("parsing metadata file {path:?}"))
}
}
fn confirm_changes(diff_tool: &str, changes: &str, original: &Path) -> Result<bool> {
let mut tmp = NamedTempFile::new()
.into_diagnostic()
.context("create tmp file")?;
tmp.write_all(changes.as_bytes())
.into_diagnostic()
.context("write tmp file")?;
confirm_write(diff_tool, &tmp.into_temp_path(), original)
}
fn confirm_write(diff_tool: &str, a: &Path, b: &Path) -> Result<bool> {
if !b.exists() {
return Ok(true);
}
let f1 = File::open(a)
.into_diagnostic()
.with_context(|| format!("opening file {a:?}"))?;
let f2 = File::open(b)
.into_diagnostic()
.with_context(|| format!("opening file {b:?}"))?;
if chksum(f1).into_diagnostic()?.as_bytes() == chksum(f2).into_diagnostic()?.as_bytes() {
return Ok(true);
}
Command::new(diff_tool)
.arg(b)
.arg(a)
.spawn()
.into_diagnostic()
.context("spawn diff tool")?
.wait()
.into_diagnostic()
.context("wait for diff tool to exit")?;
Confirm::new()
.with_prompt("Do you want to apply these changes?")
.interact()
.into_diagnostic()
}
fn ensure_cwd(cwd: &Path) -> Result<(), miette::ErrReport> {
if cwd.exists() {
return Ok(());
}
log::info!("Creating {cwd:?}");
fs::create_dir_all(&cwd)
.into_diagnostic()
.with_context(|| format!("Creating directory {cwd:?}"))
}

@ -5,7 +5,10 @@ use miette::{bail, IntoDiagnostic, Result};
use std::{env, path::Path};
use crate::config::{read_config, SiloConfig};
use crate::{
config::{read_config, SiloConfig},
fs_access::{BufferedFsAccess, FsAccess},
};
use self::contents::Contents;
@ -31,10 +34,13 @@ impl SiloRepo {
pub fn apply(&self) -> Result<()> {
let cwd = dirs::home_dir().unwrap_or(env::current_dir().into_diagnostic()?);
let ctx = ApplyContext {
let fs_access: Box<dyn FsAccess> = Box::new(BufferedFsAccess::new());
let mut ctx = ApplyContext {
config: self.config.clone(),
fs: fs_access,
};
self.contents.apply(&ctx, &cwd)
self.contents.apply(&mut ctx, &cwd)?;
ctx.fs.persist()
}
}
@ -51,4 +57,5 @@ impl ParseContext {
pub struct ApplyContext {
config: SiloConfig,
fs: Box<dyn FsAccess>,
}

Loading…
Cancel
Save