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(©.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(©.src, &_dst)); 108 } else { 109 errors |= handle_error(stage_file(©.src, &_dst, staging_dir) 110 .with_context(|| { 111 format!("Failed to copy {} to staging directory", ©.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 }