Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1,780 changes: 1,016 additions & 764 deletions Cargo.lock

Large diffs are not rendered by default.

26 changes: 13 additions & 13 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,54 +3,54 @@ name = "dotter"
version = "0.13.5"
authors = ["SuperCuber <amit.gold01@gmail.com>"]
description = "A dotfile manager and templater written in rust"
edition = "2021"
edition = "2024"
repository = "https://github.com/SuperCuber/dotter"
readme = "README.md"
keywords = ["dotter", "dotfiles", "manager"]
categories = ["command-line-utilities"]
license = "Unlicense"
rust-version = "1.70"
rust-version = "1.85"

[dependencies]
anyhow = "1.*"
clap = { version = "4.0.26", features = ["derive"] }
clap_complete = "4.0.5"
crossterm = "0.25.0"
clap = { version = "4.4.18", features = ["derive"] }
clap_complete = "4.4.10"
crossterm = "0.29.0"
diff = "0.1.*"
handlebars = "6.*"
hostname = "0.3.*"
log = "0.4.*"
maplit = "1.*"
evalexpr = "11"
evalexpr = "13"
serde = { version = "1.*", features = ["derive"] }
shellexpand = "2.*"
simplelog = "0.12.*"
tokio = "1.*"
toml = "0.4.*"
watchexec = { version = "3", optional = true }
watchexec-events = { version = "2.0.1", optional = true }
watchexec-filterer-tagged = { version = "1.0.0", optional = true }
toml = "0.9.6"
watchexec = { version = "8", optional = true }
watchexec-events = { version = "6.0.0", optional = true }
watchexec-filterer-globset = { version = "8", optional = true }

[features]
default = ["scripting", "watch"]
scripting = ["handlebars/script_helper"]
watch = ["watchexec", "watchexec-events", "watchexec-filterer-tagged"]
watch = ["watchexec", "watchexec-events", "watchexec-filterer-globset"]

[dependencies.handlebars_misc_helpers]
version = "0.17.*"
default-features = false
features = ["string", "json"]

[dev-dependencies]
mockall = "0.11.3"
mockall = "0.12.1"
# Enable this instead for better failure messages (on nightly only)
# mockall = { version = "0.9.*", features = ["nightly"] }

[target.'cfg(windows)'.dependencies]
dunce = "1.*"

[target.'cfg(unix)'.dependencies]
libc = "0.2.137"
libc = "0.2.180"

[profile.release]
strip = true
Expand Down
5 changes: 4 additions & 1 deletion src/actions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -269,7 +269,10 @@ pub fn create_symlink(
Ok(true)
}
SymlinkComparison::Identical => {
warn!("Creating symlink {:?} -> {:?} but target already exists and points at source. Adding to cache anyways", source, target.target);
warn!(
"Creating symlink {:?} -> {:?} but target already exists and points at source. Adding to cache anyways",
source, target.target
);
Ok(true)
}
SymlinkComparison::OnlyTargetExists | SymlinkComparison::BothMissing => {
Expand Down
36 changes: 17 additions & 19 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -256,25 +256,24 @@ pub fn save_dummy_config(
}

fn recursive_extend_map(
original: &mut BTreeMap<String, toml::Value>,
new: BTreeMap<String, toml::Value>,
original: &mut toml::map::Map<String, toml::Value>,
new: toml::map::Map<String, toml::Value>,
) {
for (key, new_value) in new {
original
.entry(key)
.and_modify(|original_value| {
match (
original_value.as_table().cloned(),
new_value.as_table().cloned(),
) {
(Some(mut original_table), Some(new_table)) => {
recursive_extend_map(&mut original_table, new_table);
*original_value = original_table.into();
}
_ => *original_value = new_value.clone(),
if let Some(original_value) = original.get_mut(&key) {
match (
original_value.as_table().cloned(),
new_value.as_table().cloned(),
) {
(Some(mut original_table), Some(new_table)) => {
recursive_extend_map(&mut original_table, new_table);
*original_value = toml::Value::Table(original_table);
}
})
.or_insert(new_value);
_ => *original_value = new_value.clone(),
}
} else {
original.insert(key, new_value);
}
}
}

Expand Down Expand Up @@ -368,8 +367,7 @@ fn merge_configuration_files(
}

for (variable_name, variable_value) in package.variables {
if let Some(first_value) = first_package.variables.get_mut(&variable_name).as_mut()
{
if let Some(first_value) = first_package.variables.get_mut(&variable_name) {
match (first_value, variable_value) {
(toml::Value::Table(first_value), toml::Value::Table(variable_value)) => {
trace!("Merging {:?} tables", variable_name);
Expand Down Expand Up @@ -434,7 +432,7 @@ impl FileTarget {

pub fn set_path(&mut self, new_path: impl Into<PathBuf>) {
match self {
FileTarget::Automatic(ref mut path) => *path = new_path.into(),
FileTarget::Automatic(path) => *path = new_path.into(),
FileTarget::Symbolic(SymbolicTarget { target, .. })
| FileTarget::ComplexTemplate(TemplateTarget { target, .. }) => {
*target = new_path.into();
Expand Down
62 changes: 37 additions & 25 deletions src/deploy.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ use crate::actions::{self, ActionRunner, RealActionRunner};
use crate::args::Options;
use crate::config::{self, Cache, FileTarget, SymbolicTarget, TemplateTarget};
use crate::display_error;
use crate::filesystem::{self, load_file, Filesystem};
use crate::filesystem::{self, Filesystem, load_file};
use crate::handlebars_helpers::create_new_handlebars;
use crate::hooks;

Expand Down Expand Up @@ -135,7 +135,9 @@ Proceeding by copying instead of symlinking."
// === Post-deploy ===

if suggest_force {
error!("Some files were skipped. To ignore errors and overwrite unexpected target files, use the --force flag.");
error!(
"Some files were skipped. To ignore errors and overwrite unexpected target files, use the --force flag."
);
error_occurred = true;
}

Expand Down Expand Up @@ -223,7 +225,9 @@ pub fn undeploy(opt: &Options) -> Result<bool> {
// === Post-undeploy ===

if suggest_force {
error!("Some files were skipped. To ignore errors and overwrite unexpected target files, use the --force flag.");
error!(
"Some files were skipped. To ignore errors and overwrite unexpected target files, use the --force flag."
);
error_occurred = true;
}

Expand Down Expand Up @@ -697,7 +701,7 @@ mod test {

let opt = Options::default();
let handlebars = handlebars::Handlebars::new();
let variables = BTreeMap::new();
let variables = toml::map::Map::new();

// Expectation:
// create_symlink
Expand Down Expand Up @@ -780,16 +784,20 @@ mod test {
opt.force,
opt.diff_context_lines,
);
assert!(runner
.create_symlink(&PathBuf::from("a_in"), &PathBuf::from("a_out").into())
.unwrap());
assert!(runner
.create_template(
&PathBuf::from("b_in"),
&PathBuf::from("cache/b_cache"),
&PathBuf::from("b_out").into(),
)
.unwrap());
assert!(
runner
.create_symlink(&PathBuf::from("a_in"), &PathBuf::from("a_out").into())
.unwrap()
);
assert!(
runner
.create_template(
&PathBuf::from("b_in"),
&PathBuf::from("cache/b_cache"),
&PathBuf::from("b_out").into(),
)
.unwrap()
);
}

#[test]
Expand All @@ -800,7 +808,7 @@ mod test {

let opt = Options::default();
let handlebars = handlebars::Handlebars::new();
let variables = BTreeMap::new();
let variables = toml::map::Map::new();

// Expectation:
// create_symlink
Expand Down Expand Up @@ -830,15 +838,19 @@ mod test {
);

// Both should skip
assert!(!runner
.create_symlink(&PathBuf::from("a_in"), &PathBuf::from("a_out").into())
.unwrap());
assert!(!runner
.create_template(
&PathBuf::from("b_in"),
&PathBuf::from("cache/b_cache"),
&PathBuf::from("b_out").into(),
)
.unwrap());
assert!(
!runner
.create_symlink(&PathBuf::from("a_in"), &PathBuf::from("a_out").into())
.unwrap()
);
assert!(
!runner
.create_template(
&PathBuf::from("b_in"),
&PathBuf::from("cache/b_cache"),
&PathBuf::from("b_out").into(),
)
.unwrap()
);
}
}
9 changes: 7 additions & 2 deletions src/filesystem.rs
Original file line number Diff line number Diff line change
Expand Up @@ -248,7 +248,9 @@ impl RealFilesystem {
if !self.sudo_occurred {
warn!("Elevating permissions ({})", goal.as_ref());
if !log_enabled!(log::Level::Debug) {
warn!("To see more than the first time elevated permissions are used, use verbosity 2 or more (-vv)");
warn!(
"To see more than the first time elevated permissions are used, use verbosity 2 or more (-vv)"
);
}
self.sudo_occurred = true;
} else {
Expand Down Expand Up @@ -825,7 +827,10 @@ pub fn is_template(source: &Path) -> Result<bool> {
let mut buf = String::new();

if file.read_to_string(&mut buf).is_err() {
warn!("File {:?} is not valid UTF-8 - detecting as symlink. Explicitly specify it to silence this message.", source);
warn!(
"File {:?} is not valid UTF-8 - detecting as symlink. Explicitly specify it to silence this message.",
source
);
Ok(false)
} else {
Ok(buf.contains("{{"))
Expand Down
30 changes: 17 additions & 13 deletions src/handlebars_helpers.rs
Original file line number Diff line number Diff line change
Expand Up @@ -371,7 +371,7 @@ mod test {
fn eval_condition_simple() {
let mut config = Configuration {
files: Files::new(),
variables: maplit::btreemap! { "foo".into() => 2.into() },
variables: toml::map::Map::from_iter([("foo".into(), 2.into())]),
#[cfg(feature = "scripting")]
helpers: Helpers::new(),
packages: maplit::btreemap! { "default".into() => true, "disabled".into() => false },
Expand All @@ -386,12 +386,14 @@ mod test {
assert!(
!eval_condition(&handlebars, &config.variables, "dotter.packages.nonexist").unwrap()
);
assert!(!eval_condition(
&handlebars,
&config.variables,
"(and true dotter.packages.disabled)"
)
.unwrap());
assert!(
!eval_condition(
&handlebars,
&config.variables,
"(and true dotter.packages.disabled)"
)
.unwrap()
);
}

#[test]
Expand All @@ -407,12 +409,14 @@ mod test {
};
let handlebars = create_new_handlebars(&mut config).unwrap();

assert!(!eval_condition(
&handlebars,
&config.variables,
"(is_executable \"no_such_executable_please\")"
)
.unwrap());
assert!(
!eval_condition(
&handlebars,
&config.variables,
"(is_executable \"no_such_executable_please\")"
)
.unwrap()
);
assert!(
eval_condition(&handlebars, &config.variables, "(eq (math \"5+5\") \"10\")").unwrap()
);
Expand Down
52 changes: 16 additions & 36 deletions src/watch.rs
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
use anyhow::{Context, Result};
use watchexec::sources::fs::Watcher;
use watchexec::{Config, Watchexec};
use watchexec_filterer_tagged::{Filter, Matcher, Op, Pattern, TaggedFilterer};
use watchexec_filterer_globset::GlobsetFilterer;

use super::display_error;
use crate::args::Options;
Expand All @@ -13,41 +13,21 @@ pub(crate) async fn watch(opt: Options) -> Result<()> {
config.file_watcher(Watcher::Native);
config.pathset(["."]);

let filter = TaggedFilterer::new(".".into(), std::env::current_dir()?)
.await
.unwrap();
filter
.add_filters(&[
Filter {
in_path: None,
on: Matcher::Path,
op: Op::NotGlob,
pat: Pattern::Glob(format!("{}/", opt.cache_directory.display())),
negate: false,
},
Filter {
in_path: None,
on: Matcher::Path,
op: Op::NotGlob,
pat: Pattern::Glob(opt.cache_file.to_string_lossy().into()),
negate: false,
},
Filter {
in_path: None,
on: Matcher::Path,
op: Op::NotGlob,
pat: Pattern::Glob(".git/".into()),
negate: false,
},
Filter {
in_path: None,
on: Matcher::Path,
op: Op::NotEqual,
pat: Pattern::Exact("DOTTER_SYMLINK_TEST".into()),
negate: false,
},
])
.await?;
let filter = GlobsetFilterer::new(
std::env::current_dir()?,
vec![
(format!("!{}/", opt.cache_directory.display()), None),
(format!("!{}", opt.cache_file.display()), None),
("!.git/".to_string(), None),
("!DOTTER_SYMLINK_TEST".to_string(), None),
],
vec![],
vec![],
vec![],
vec![], // Add the 6th argument (extensions)
)
.await?;

config.filterer(filter);

config.on_action(move |mut action| {
Expand Down