diff --git a/CHANGELOG.md b/CHANGELOG.md index 84eb564..40d92b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,13 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 ## [Unreleased] +## [v0.4.1] - 2022-06-30 + +### Fixed + +- Wildcard "*" in install command not working +- Defaults and global values in `dot.(yaml|toml|json)` files not working correctly + ## [v0.4.0] - 2022-06-29 ### Added @@ -89,7 +96,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 - Dotfile linking - Error handling -[Unreleased]: https://github.com/volllly/rotz/compare/v0.4.0...HEAD +[Unreleased]: https://github.com/volllly/rotz/compare/v0.4.1...HEAD +[v0.4.1]: https://github.com/volllly/rotz/releases/tag/v0.3.2 [v0.4.0]: https://github.com/volllly/rotz/releases/tag/v0.3.2 [v0.3.2]: https://github.com/volllly/rotz/releases/tag/v0.3.2 [v0.3.1]: https://github.com/volllly/rotz/releases/tag/v0.3.1 diff --git a/Cargo.toml b/Cargo.toml index 4091d04..8db81b6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,6 +1,6 @@ [package] name = "rotz" -version = "0.4.0" +version = "0.4.1" edition = "2021" authors = [ "Paul Volavsek " ] license = "MIT" diff --git a/src/commands/install.rs b/src/commands/install.rs index f95e67f..418a9a8 100644 --- a/src/commands/install.rs +++ b/src/commands/install.rs @@ -94,52 +94,50 @@ impl Install { } } - if !installs.cmd.is_empty() { - println!("{}Installing {}{}\n", Attribute::Bold, entry.0.as_str().blue(), Attribute::Reset); - - let inner_cmd = HANDLEBARS + println!("{}Installing {}{}\n", Attribute::Bold, entry.0.as_str().blue(), Attribute::Reset); + + let inner_cmd = HANDLEBARS + .render_template( + &installs.cmd.to_string(), + &json!({ + "name": entry.0 + }), + ) + .map_err(|err| Error::RenderingTemplate(entry.0.to_string(), err))?; + + let cmd = if let Some(shell_command) = self.config.shell_command.as_ref() { + HANDLEBARS .render_template( - &installs.cmd.to_string(), + shell_command, &json!({ - "name": entry.0 + "name": entry.0, + "cmd": &inner_cmd }), ) - .map_err(|err| Error::RenderingTemplate(entry.0.to_string(), err))?; - - let cmd = if let Some(shell_command) = self.config.shell_command.as_ref() { - HANDLEBARS - .render_template( - shell_command, - &json!({ - "name": entry.0, - "cmd": &inner_cmd - }), - ) - .map_err(|err| Error::RenderingTemplate(entry.0.to_string(), err))? - } else { - inner_cmd.clone() - }; - - let cmd = shellwords::split(&cmd).map_err(|err| Error::ParsingInstallCommand(entry.0.to_string(), err))?; - - println!("{}{}{}\n", Attribute::Italic, inner_cmd, Attribute::Reset); - - if !globals.dry_run { - let output = process::Command::new(&cmd[0]) - .args(&cmd[1..]) - .stdin(process::Stdio::null()) - .stdout(process::Stdio::inherit()) - .stderr(process::Stdio::inherit()) - .output() - .map_err(|e| Error::InstallSpawn(entry.0.to_string(), e))?; - - if !install_command.continue_on_error && !output.status.success() { - return Error::InstallExecute(entry.0.to_string(), output.status.code()).error(); - } + .map_err(|err| Error::RenderingTemplate(entry.0.to_string(), err))? + } else { + inner_cmd.clone() + }; + + let cmd = shellwords::split(&cmd).map_err(|err| Error::ParsingInstallCommand(entry.0.to_string(), err))?; + + println!("{}{}{}\n", Attribute::Italic, inner_cmd, Attribute::Reset); + + if !globals.dry_run { + let output = process::Command::new(&cmd[0]) + .args(&cmd[1..]) + .stdin(process::Stdio::null()) + .stdout(process::Stdio::inherit()) + .stderr(process::Stdio::inherit()) + .output() + .map_err(|e| Error::InstallSpawn(entry.0.to_string(), e))?; + + if !install_command.continue_on_error && !output.status.success() { + return Error::InstallExecute(entry.0.to_string(), output.status.code()).error(); } - - installed.insert(entry.0.as_str()); } + + installed.insert(entry.0.as_str()); } if !(install_command.skip_all_dependencies || install_command.skip_dependencies) { @@ -186,7 +184,7 @@ impl Command for Install { let mut installed: HashSet<&str> = HashSet::new(); for dot in dots.iter() { - if install_command.dots.contains(&"".to_string()) || install_command.dots.contains(dot.0) { + if install_command.dots.contains(&"*".to_string()) || install_command.dots.contains(dot.0) { self.install(&dots, dot, &mut installed, IndexSet::new(), (&globals, &install_command))?; } } diff --git a/src/dot.rs b/src/dot.rs index fc17eef..7cfdcd8 100644 --- a/src/dot.rs +++ b/src/dot.rs @@ -4,11 +4,14 @@ mod repr { path::PathBuf, }; + use derive_more::IsVariant; #[cfg(test)] use fake::{Dummy, Fake}; use serde::Deserialize; use somok::Somok; + use crate::helpers::os; + #[derive(Deserialize, Debug, Default)] #[cfg_attr(test, derive(Dummy))] struct DotSimplified { @@ -16,7 +19,7 @@ mod repr { capabilities: Capabilities, } - #[derive(Deserialize, Debug, Default)] + #[derive(Deserialize, Debug, Default, Clone)] #[cfg_attr(test, derive(Dummy))] pub struct Dot { pub global: Option>, @@ -54,7 +57,7 @@ mod repr { }, } - #[derive(Deserialize, Clone, Debug)] + #[derive(Deserialize, Clone, Debug, IsVariant)] #[serde(untagged)] #[cfg_attr(test, derive(Dummy))] pub enum Installs { @@ -123,6 +126,38 @@ mod repr { } } + impl From for Capabilities { + fn from( + Dot { + global, + windows, + linux, + darwin, + windows_linux, + linux_darwin, + darwin_windows, + }: Dot, + ) -> Self { + let mut capabilities: Option = global.map(|g| (*g).clone()); + + if os::OS.is_windows() { + capabilities = capabilities.merge(windows_linux); + capabilities = capabilities.merge(darwin_windows); + capabilities = capabilities.merge(windows); + } else if os::OS.is_linux() { + capabilities = capabilities.merge(windows_linux); + capabilities = capabilities.merge(linux_darwin); + capabilities = capabilities.merge(linux); + } else if os::OS.is_darwin() { + capabilities = capabilities.merge(linux_darwin); + capabilities = capabilities.merge(darwin_windows); + capabilities = capabilities.merge(darwin); + } + + capabilities.unwrap_or_default() + } + } + pub trait Merge { fn merge(self, merge: T) -> Self; } @@ -189,35 +224,39 @@ mod repr { if let Some(i) = &mut self.installs { if let Some(installs) = installs { - let cmd_outer: Option; - let mut depends_outer: HashSet = HashSet::new(); - - match installs { - Installs::None(t) => cmd_outer = if !t { "".to_string().some() } else { None }, - Installs::Simple(cmd) => cmd_outer = cmd.some(), - Installs::Full { cmd, depends } => { - cmd_outer = cmd.some(); - depends_outer = depends; + if installs.is_none() { + self.installs = None; + } else { + let cmd_outer: String; + let mut depends_outer: HashSet = HashSet::new(); + + match installs { + Installs::Simple(cmd) => cmd_outer = cmd, + Installs::Full { cmd, depends } => { + cmd_outer = cmd; + depends_outer = depends; + } + Installs::None(_) => panic!(), } - } - *i = match i { - Installs::None(_) => Installs::Full { - cmd: cmd_outer.unwrap_or_else(|| "".to_string()), - depends: depends_outer, - }, - Installs::Simple(cmd) => Installs::Full { - cmd: cmd_outer.unwrap_or_else(|| cmd.to_string()), - depends: depends_outer, - }, - Installs::Full { cmd, depends } => { - depends_outer.extend(depends.clone()); - Installs::Full { - cmd: cmd_outer.unwrap_or_else(|| cmd.to_string()), + *i = match i { + Installs::None(_) => Installs::Full { + cmd: cmd_outer, depends: depends_outer, + }, + Installs::Simple(_) => Installs::Full { + cmd: cmd_outer, + depends: depends_outer, + }, + Installs::Full { cmd: _, depends } => { + depends_outer.extend(depends.clone()); + Installs::Full { + cmd: cmd_outer, + depends: depends_outer, + } } - } - }; + }; + } } } else { self.installs = installs; @@ -258,12 +297,12 @@ pub struct Installs { pub(crate) depends: HashSet, } -impl From for Installs { +impl From for Option { fn from(from: repr::Installs) -> Self { match from { - repr::Installs::None(t) => Self { cmd: "".to_string(), depends: Default::default() }, - repr::Installs::Simple(cmd) => Self { cmd, depends: Default::default() }, - repr::Installs::Full { cmd, depends } => Self { cmd, depends }, + repr::Installs::None(_) => None, + repr::Installs::Simple(cmd) => Installs { cmd, depends: Default::default() }.some(), + repr::Installs::Full { cmd, depends } => Installs { cmd, depends }.some(), } } } @@ -306,56 +345,62 @@ impl Merge<&Self> for Dot { } } +fn from_str_with_defaults(s: &str, defaults: Option<&Capabilities>) -> Result { + let repr::Dot { + global, + windows, + linux, + darwin, + windows_linux, + linux_darwin, + darwin_windows, + } = repr::Dot::parse(s)?; + + let capabilities: Option = if let Some(defaults) = defaults { (*defaults).clone().some() } else { None }; + + let mut capabilities: Option = global.map_or(capabilities.clone(), |g| capabilities.merge(g.some())); + + if os::OS.is_windows() { + capabilities = capabilities.merge(windows_linux); + capabilities = capabilities.merge(darwin_windows); + capabilities = capabilities.merge(windows); + } else if os::OS.is_linux() { + capabilities = capabilities.merge(windows_linux); + capabilities = capabilities.merge(linux_darwin); + capabilities = capabilities.merge(linux); + } else if os::OS.is_darwin() { + capabilities = capabilities.merge(linux_darwin); + capabilities = capabilities.merge(darwin_windows); + capabilities = capabilities.merge(darwin); + } + + if let Some(capabilities) = capabilities { + Dot { + links: capabilities.links.map(|c| match c { + repr::Links::One { links } => links + .into_iter() + .map(|l| { + let mut hs = HashSet::::new(); + hs.insert(l.1); + (l.0, hs) + }) + .collect(), + repr::Links::Many { links } => links, + }), + installs: capabilities.installs.and_then(|i| i.into()), + depends: capabilities.depends.map(|c| c.depends), + } + } else { + Dot::default() + } + .okay() +} + impl FromStr for Dot { type Err = repr::ParseError; fn from_str(s: &str) -> Result { - let repr::Dot { - global, - windows, - linux, - darwin, - windows_linux, - linux_darwin, - darwin_windows, - } = repr::Dot::parse(s)?; - - let mut capabilities: Option = None; - - if os::OS.is_windows() { - capabilities = windows.map(|g| *g).merge(global); - capabilities = capabilities.merge(windows_linux); - capabilities = capabilities.merge(darwin_windows); - } else if os::OS.is_linux() { - capabilities = linux.map(|g| *g).merge(global); - capabilities = capabilities.merge(windows_linux); - capabilities = capabilities.merge(linux_darwin); - } else if os::OS.is_darwin() { - capabilities = darwin.map(|g| *g).merge(global); - capabilities = capabilities.merge(linux_darwin); - capabilities = capabilities.merge(darwin_windows); - } - - if let Some(capabilities) = capabilities { - Self { - links: capabilities.links.map(|c| match c { - repr::Links::One { links } => links - .into_iter() - .map(|l| { - let mut hs = HashSet::::new(); - hs.insert(l.1); - (l.0, hs) - }) - .collect(), - repr::Links::Many { links } => links, - }), - installs: capabilities.installs.map(|i| i.into()), - depends: capabilities.depends.map(|c| c.depends), - } - } else { - Self::default() - } - .okay() + from_str_with_defaults(s, None) } } @@ -388,14 +433,15 @@ pub enum Error { } pub fn read_dots(dotfiles_path: &Path, dots: &[String]) -> miette::Result> { - let global = dotfiles_path.join(format!("dots.{FILE_EXTENSION}")); - let global = match fs::read_to_string(global.clone()) { - Ok(text) => text.parse::().map_err(|e| Error::ParseDot(global, e))?.some(), + let defaults = dotfiles_path.join(format!("dots.{FILE_EXTENSION}")); + let defaults = match fs::read_to_string(defaults.clone()) { + Ok(text) => repr::Dot::parse(&text).map_err(|e| Error::ParseDot(defaults, e))?.some(), Err(err) => match err.kind() { std::io::ErrorKind::NotFound => None, _ => panic!("{}", err), }, }; + let defaults = defaults.map(Into::::into); let wildcard = dots.contains(&"*".to_string()); @@ -427,7 +473,7 @@ pub fn read_dots(dotfiles_path: &Path, dots: &[String]) -> miette::Result match text.parse::() { + Ok((name, Ok(text))) => match from_str_with_defaults(&text, defaults.as_ref()) { Ok(dot) => (name, dot).okay().some(), Err(err) => Error::ParseDot(Path::new(&format!("{name}/dot.{FILE_EXTENSION}")).to_path_buf(), err).error().some(), }, @@ -438,8 +484,6 @@ pub fn read_dots(dotfiles_path: &Path, dots: &[String]) -> miette::Result err.error().some(), }); - let dots = dots.map_ok(|d| (d.0, if let Some(global) = &global { global.clone().merge(&d.1) } else { d.1 })); - let dots = crate::helpers::join_err_result(dots.collect())?; if dots.is_empty() { println!("Warning: {}", "No dots found".yellow());