diff --git a/Cargo.toml b/Cargo.toml index 4b408cd..7381fe5 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,3 +7,4 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] +dialoguer = "0.7.1" diff --git a/src/lib.rs b/src/lib.rs index 31755e0..af29955 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -4,6 +4,8 @@ use std::fs::{metadata, symlink_metadata}; use std::path::PathBuf; use std::process::Command; +use dialoguer::{theme::SimpleTheme, Confirm, MultiSelect}; + macro_rules! unwrap_or_false { ($expr: expr) => { match $expr { @@ -171,22 +173,41 @@ impl Component { Component::Git => &[Component::Sudo], Component::Zsh => &[Component::Sudo], Component::Dotfiles => &[Component::Git], - Component::Zshrc => &[Component::Zsh, Component::Dotfiles], + Component::Zshrc => &[Component::Zsh, Component::Dotfiles, Component::OhMyZsh], Component::OhMyZsh => &[Component::Git], } } + + /// Returns the str representation of the command. + pub fn to_str(self) -> &'static str { + match self { + Component::Sudo => "sudo", + Component::Git => "git", + Component::Zsh => "zsh", + Component::Dotfiles => "dotfiles", + Component::Zshrc => "zshrc", + Component::OhMyZsh => "ohmyzsh", + } + } } impl fmt::Display for Component { fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - match self { - Component::Sudo => write!(fmt, "sudo"), - Component::Git => write!(fmt, "git"), - Component::Zsh => write!(fmt, "zsh"), - Component::Dotfiles => write!(fmt, "dotfiles"), - Component::Zshrc => write!(fmt, "zshrc"), - Component::OhMyZsh => write!(fmt, "ohmyzsh"), - } + write!( + fmt, + "{} (requires {}{})", + self.to_str(), + self.dependencies() + .into_iter() + .map(|x| x.to_str()) + .collect::>() + .join(", "), + if self.is_installed() { + ", already installed" + } else { + "" + } + ) } } @@ -194,23 +215,52 @@ impl fmt::Display for Component { pub struct Installer { /// All the components to install, and whether the users want to install them. components: Vec<(Component, bool)>, - - /// Where the users cursor is. - cursor: usize, } impl Installer { /// Creates a new installer. pub fn new() -> Installer { Installer { - components: Component::all().into_iter().map(|x| (x, true)).collect(), - cursor: 0, + components: Component::all().into_iter().map(|x| (x, false)).collect(), } } - /// Prints the installer. - pub fn print(&self) { - print!("{}", self); + /// Asks interactively to the user which components to install. + pub fn interact(&mut self) -> bool { + let multiselected = self + .components + .iter() + .map(|(component, _)| format!("{}", component)) + .collect::>(); + + let defaults = self + .components + .iter() + .map(|(_, active)| *active) + .collect::>(); + + let selections = MultiSelect::with_theme(&SimpleTheme) + .items(&multiselected[..]) + .defaults(&defaults[..]) + .interact() + .unwrap(); + + for i in selections { + self.components[i].1 = true; + } + + println!("The following components will be installed:"); + for (comp, to_install) in &self.components { + if *to_install { + println!(" - {}", comp.to_str()); + } + } + let message = "Is this correct?"; + + Confirm::with_theme(&SimpleTheme) + .with_prompt(message) + .interact() + .unwrap() } /// Runs the installer. @@ -228,7 +278,8 @@ impl Installer { if component.is_installed() || component == Component::Sudo { return Ok(()); } - println!("Install {}", component); + + println!("Install {}", component.to_str()); for dep in component.dependencies() { self.install_component(*dep)?; @@ -241,32 +292,6 @@ impl Installer { } } -impl fmt::Display for Installer { - fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { - for (index, (component, to_install)) in self.components.iter().enumerate() { - writeln!( - fmt, - "{} [{}] {} (requires {}{})", - if self.cursor == index { ">" } else { " " }, - if *to_install { "x" } else { " " }, - component, - component - .dependencies() - .into_iter() - .map(|x| format!("{}", x)) - .collect::>() - .join(", "), - if component.is_installed() { - ", already installed" - } else { - "" - } - )?; - } - Ok(()) - } -} - /// The error type of this library. pub enum Error {} @@ -281,8 +306,9 @@ pub type Result = std::result::Result; /// Runs the installer pub fn main() -> Result<()> { - let installer = Installer::new(); - installer.print(); - installer.start()?; + let mut installer = Installer::new(); + if installer.interact() { + installer.start()?; + } Ok(()) }