coliru

A minimal, flexible, dotfile installer
git clone https://git.ashermorgan.net/coliru/
Log | Files | Refs | README

core.rs (5587B)


      1 //! Core manifest operation functions
      2 
      3 use anyhow::{Context, Result};
      4 use colored::{Colorize, ColoredString};
      5 use std::env::set_current_dir;
      6 use std::path::Path;
      7 use super::manifest::{Manifest, CopyLinkOptions, RunOptions, get_manifest_tags,
      8     filter_manifest_steps};
      9 use super::local::{copy_file, link_file, run_command};
     10 use super::ssh::{resolve_path, send_command, send_staged_files, stage_file};
     11 use tempfile::tempdir;
     12 
     13 /// The base directory for SSH installs, relative to the home directory
     14 const SSH_INSTALL_DIR: &str = ".coliru";
     15 
     16 /// Performs a dry-run check inside of a loop
     17 ///
     18 /// Will print `(DRY RUN)` and then continue to next loop iteration if `dry_run`
     19 /// evaluates to `true`.
     20 macro_rules! check_dry_run {
     21     ($dry_run:expr) => {
     22         if $dry_run {
     23             println!(" (DRY RUN)");
     24             continue;
     25         }
     26         println!("");
     27     }
     28 }
     29 
     30 /// Handles minor errors that occur during command execution and returns a bool
     31 /// indicating whether an error occurred
     32 fn handle_error(result: Result<()>) -> bool {
     33     if let Err(why) = result {
     34         eprintln!("  {} {:#}", "Error:".bold().red(), why);
     35         return true;
     36     }
     37     false
     38 }
     39 
     40 /// Prints the available tags in a manifest
     41 pub fn list_tags(manifest: Manifest) {
     42     for tag in get_manifest_tags(manifest) {
     43         println!("{}", tag);
     44     }
     45 }
     46 
     47 /// Executes the steps in a coliru manifest according to a set of tag rules
     48 ///
     49 /// Returns an Err if a critical error occurs and returns a bool indicating
     50 /// whether any minor errors occurred otherwise
     51 pub fn install_manifest(manifest: Manifest, tag_rules: Vec<String>, host: &str,
     52                         dry_run: bool, copy: bool) -> Result<bool> {
     53 
     54     let filtered_manifest = filter_manifest_steps(manifest, &tag_rules);
     55 
     56     let temp_dir = tempdir().context("Failed to create temporary directory")?;
     57     set_current_dir(filtered_manifest.base_dir)
     58         .context("Failed to set working directory")?;
     59 
     60     let mut errors = false;
     61 
     62     for (i, step) in filtered_manifest.steps.iter().enumerate() {
     63         let step_str = format!("[{}/{}]", i+1,
     64             filtered_manifest.steps.len()).bold();
     65 
     66         errors |= execute_copies(&step.copy, host, temp_dir.path(), dry_run,
     67                                  &step_str);
     68 
     69         if !copy && host == "" {
     70             errors |= execute_links(&step.link, dry_run, &step_str);
     71         } else {
     72             errors |= execute_copies(&step.link, host, temp_dir.path(), dry_run,
     73                            &step_str);
     74         }
     75 
     76         errors |= execute_runs(&step.run, &tag_rules, host, temp_dir.path(),
     77                                dry_run, &step_str);
     78     }
     79 
     80     Ok(errors)
     81 }
     82 
     83 /// Executes a set of copy commands and returns a bool indicating whether any
     84 /// error occurred
     85 fn execute_copies(copies: &[CopyLinkOptions], host: &str, staging_dir: &Path,
     86                   dry_run: bool, step_str: &ColoredString) -> bool {
     87 
     88     let mut errors = false;
     89 
     90     for copy in copies {
     91         // Resolve relative dst paths if installing over SSH
     92         let _dst = if host != "" {
     93             resolve_path(&copy.dst, &format!("~/{}", SSH_INSTALL_DIR))
     94         } else {
     95             copy.dst.clone()
     96         };
     97 
     98         print!("{} Copy {} to ", step_str, copy.src);
     99         if host != "" {
    100             print!("{}:", host);
    101         }
    102         print!("{}", _dst);
    103 
    104         check_dry_run!(dry_run);
    105 
    106         if host == "" {
    107             errors |= handle_error(copy_file(&copy.src, &_dst));
    108         } else {
    109             errors |= handle_error(stage_file(&copy.src, &_dst, staging_dir)
    110                .with_context(|| {
    111                    format!("Failed to copy {} to staging directory", &copy.src)
    112                }));
    113         }
    114     }
    115 
    116     if !dry_run {
    117         errors |= handle_error(send_staged_files(staging_dir, host)
    118             .context("Failed to transfer staged files"));
    119     }
    120 
    121     errors
    122 }
    123 
    124 /// Executes a set of link commands and returns a bool indicating whether any
    125 /// error occurred
    126 fn execute_links(links: &[CopyLinkOptions], dry_run: bool,
    127                  step_str: &ColoredString) -> bool {
    128 
    129     let mut errors = false;
    130 
    131     for link in links {
    132         print!("{} Link {} to {}", step_str, link.src, link.dst);
    133 
    134         check_dry_run!(dry_run);
    135 
    136         errors |= handle_error(link_file(&link.src, &link.dst));
    137     }
    138 
    139     errors
    140 }
    141 
    142 /// Executes a set of run commands and returns a bool indicating whether any
    143 /// error occurred
    144 fn execute_runs(runs: &[RunOptions], tag_rules: &[String], host: &str,
    145                 staging_dir: &Path, dry_run: bool, step_str: &ColoredString) ->
    146 bool {
    147 
    148     let mut errors = false;
    149 
    150     if host != "" {
    151         // Copy scripts to remote machine
    152         let run_copies: Vec<CopyLinkOptions> = runs.iter().map(|x| {
    153             CopyLinkOptions { src: x.src.clone(), dst: x.src.clone() }
    154         }).collect();
    155 
    156         errors |= execute_copies(&run_copies, host, staging_dir, dry_run,
    157                                  step_str);
    158     }
    159 
    160     for run in runs {
    161         let postfix = run.postfix.replace("$COLIRU_RULES",
    162                                           &tag_rules.join(" "));
    163         let cmd = format!("{} {} {}", run.prefix, run.src, postfix);
    164 
    165         print!("{} Run {}", step_str, cmd);
    166         if host != "" {
    167             print!(" on {}", host);
    168         }
    169 
    170         check_dry_run!(dry_run);
    171 
    172         if host == "" {
    173             errors |= handle_error(run_command(&cmd));
    174         } else {
    175             let ssh_cmd = format!("cd {} && {}", SSH_INSTALL_DIR, &cmd);
    176             errors |= handle_error(send_command(&ssh_cmd, host));
    177         }
    178     }
    179 
    180     errors
    181 }