From 89838ae594634a3cb15c6b6a60577a7d3c64ef15 Mon Sep 17 00:00:00 2001 From: Thomas Forgione Date: Mon, 7 Dec 2020 20:00:07 +0100 Subject: [PATCH] Getting shit working --- Cargo.toml | 1 - src/lib.rs | 163 +++++++++++++++++++++++++++++++++++++++++++++++------ 2 files changed, 147 insertions(+), 17 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index b770be8..4b408cd 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -7,4 +7,3 @@ edition = "2018" # See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html [dependencies] -termion = "1.5.5" diff --git a/src/lib.rs b/src/lib.rs index 339f1e9..31755e0 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,13 +1,70 @@ +use std::ffi::OsStr; use std::fmt; +use std::fs::{metadata, symlink_metadata}; +use std::path::PathBuf; use std::process::Command; +macro_rules! unwrap_or_false { + ($expr: expr) => { + match $expr { + Ok(v) => v, + Err(_) => return false, + } + }; +} + +/// Returns the home directory. +pub fn home() -> PathBuf { + PathBuf::from(std::env::var("HOME").unwrap()) +} + +/// A path from the user's home. +pub fn from_home(path: &str) -> PathBuf { + home().join(path) +} + +/// Tests that a command exists. +pub fn test_command(cmd: &str) -> bool { + unwrap_or_false!(command("sh", &["-c", &format!("command -v {}", cmd)]).output()) + .status + .success() +} + /// Helper to create commands in one line. -pub fn command(program: &str, args: &[&str]) -> Command { +pub fn command(program: &str, args: I) -> Command +where + I: IntoIterator, + S: AsRef, +{ let mut command = Command::new(program); command.args(args); command } +/// Helper to create commands for cloning a github repository. +pub fn github_clone>(user: &str, repo: &str, dest: T) -> Command { + command( + "git", + &[ + &"clone".as_ref(), + &format!("https://github.com/{}/{}", user, repo).as_ref(), + dest.as_ref(), + ], + ) +} + +/// Helper to create commands for cloning a gitea repository. +pub fn gitea_clone>(user: &str, repo: &str, dest: T) -> Command { + command( + "git", + &[ + &"clone".as_ref(), + &format!("https://gitea.tforgione.fr/{}/{}", user, repo).as_ref(), + dest.as_ref(), + ], + ) +} + /// The supported package manager. #[derive(Copy, Clone)] pub enum PackageManager { @@ -22,14 +79,16 @@ impl PackageManager { /// Returns the command to install a page using the right package manager. pub fn install<'a>(&self, package: &'a str) -> Command { match self { - PackageManager::Apt => command("sudo", &["apt", "install", package]), + PackageManager::Apt => { + command("sh", &["-c", &format!("sudo apt install -y {}", package)]) + } PackageManager::Pacman => command("sudo", &["pacman", "-S", package]), } } } /// All the installable components. -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq, Eq)] pub enum Component { /// Whether the user is allowed to run sudo commands. Sudo, @@ -42,31 +101,66 @@ pub enum Component { /// The configuration files. Dotfiles, + + /// The zshrc config file + Zshrc, + + /// The oh my zsh repository. + OhMyZsh, } impl Component { /// List all components. pub fn all() -> Vec { - vec![Component::Git, Component::Zsh, Component::Dotfiles] + vec![ + Component::Git, + Component::Zsh, + Component::Dotfiles, + Component::OhMyZsh, + Component::Zshrc, + ] + } + + /// Returns the command that installs a component. + pub fn install_command(self, package_manager: PackageManager) -> Command { + match self { + Component::Sudo => panic!("you can't install sudo"), + Component::Git => package_manager.install("git"), + Component::Zsh => package_manager.install("zsh"), + Component::Dotfiles => { + gitea_clone("tforgione", "dotfiles", from_home(".config/dotfiles")) + } + Component::OhMyZsh => github_clone("ohmyzsh", "ohmyzsh", from_home(".config/ohmyzsh")), + Component::Zshrc => command( + "ln", + &[ + AsRef::::as_ref("-s"), + &from_home(".config/dotfiles/zshrc").as_ref(), + &from_home(".zshrc").as_ref(), + ], + ), + } } /// Installs a component. pub fn install(self) -> Result<()> { - match self { - Component::Sudo => todo!(), - Component::Git => todo!(), - Component::Zsh => todo!(), - Component::Dotfiles => todo!(), - } + self.install_command(PackageManager::Apt).output().unwrap(); + Ok(()) } /// Returns a command that tests if a component is installed. pub fn is_installed(self) -> bool { match self { - Component::Sudo => panic!("can't use is_installed() on Component::Sudo"), - Component::Git => todo!(), - Component::Zsh => todo!(), - Component::Dotfiles => todo!(), + Component::Sudo => true, + Component::Git => test_command("git"), + Component::Zsh => test_command("zsh"), + Component::Dotfiles => { + unwrap_or_false!(metadata(from_home(".config/dotfiles"))).is_dir() + } + Component::OhMyZsh => unwrap_or_false!(metadata(from_home(".config/ohmyzsh"))).is_dir(), + Component::Zshrc => unwrap_or_false!(symlink_metadata(from_home(".zshrc"))) + .file_type() + .is_symlink(), } } @@ -77,6 +171,8 @@ impl Component { Component::Git => &[Component::Sudo], Component::Zsh => &[Component::Sudo], Component::Dotfiles => &[Component::Git], + Component::Zshrc => &[Component::Zsh, Component::Dotfiles], + Component::OhMyZsh => &[Component::Git], } } } @@ -88,6 +184,8 @@ impl fmt::Display for Component { 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"), } } } @@ -114,6 +212,33 @@ impl Installer { pub fn print(&self) { print!("{}", self); } + + /// Runs the installer. + pub fn start(&self) -> Result<()> { + for (component, to_install) in &self.components { + if *to_install { + self.install_component(*component)?; + } + } + Ok(()) + } + + /// Installs a component. + pub fn install_component(&self, component: Component) -> Result<()> { + if component.is_installed() || component == Component::Sudo { + return Ok(()); + } + println!("Install {}", component); + + for dep in component.dependencies() { + self.install_component(*dep)?; + } + + if !component.is_installed() { + component.install()?; + } + Ok(()) + } } impl fmt::Display for Installer { @@ -121,7 +246,7 @@ impl fmt::Display for Installer { for (index, (component, to_install)) in self.components.iter().enumerate() { writeln!( fmt, - "{} [{}] {} (requires {})", + "{} [{}] {} (requires {}{})", if self.cursor == index { ">" } else { " " }, if *to_install { "x" } else { " " }, component, @@ -130,7 +255,12 @@ impl fmt::Display for Installer { .into_iter() .map(|x| format!("{}", x)) .collect::>() - .join(", ") + .join(", "), + if component.is_installed() { + ", already installed" + } else { + "" + } )?; } Ok(()) @@ -153,5 +283,6 @@ pub type Result = std::result::Result; pub fn main() -> Result<()> { let installer = Installer::new(); installer.print(); + installer.start()?; Ok(()) }