Improve error handling
+ Alias for returning Result with varying Error types + Update all Result returns to use new alias + Documentation comments for functions and structs + Add move_dir and move_file functions to fs wrapper module + Add `--force` flag for overwriting an existing configuration backup
This commit is contained in:
parent
fded317a65
commit
069f5cc128
72
src/kot.rs
72
src/kot.rs
|
@ -13,20 +13,24 @@ pub mod cli;
|
||||||
pub mod fs;
|
pub mod fs;
|
||||||
pub mod io;
|
pub mod io;
|
||||||
|
|
||||||
|
/// Result alias to return result with Error of various types
|
||||||
|
pub type Result<T> = std::result::Result<T, Box<dyn std::error::Error>>;
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
// IMPLEMENTATION
|
// IMPLEMENTATION
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
// Creates symbolic links to the configurations we're installing
|
/// Creates symbolic links to the configurations we're installing
|
||||||
// TODO: On error, revert to last good state
|
// TODO: On error, revert to last good state
|
||||||
// TODO: User script to execute after installing configs successfully
|
// TODO: User script to execute after installing configs successfully
|
||||||
// TODO: Function to uninstall configs. Loop through dotfiles and restore backup files or delete configs
|
pub fn install_configs(args: & cli::Cli) -> Result<()> {
|
||||||
pub fn install_configs(args: & cli::Cli) -> std::io::Result<()> {
|
|
||||||
// Get the configurations and their target installation paths
|
// Get the configurations and their target installation paths
|
||||||
// + Checks for conflicts and prompts user to abort or continue
|
// + Checks for conflicts and prompts user to abort or continue
|
||||||
let config_map = fs::get_target_paths(&args)?;
|
let config_map = fs::get_target_paths(&args)?;
|
||||||
|
|
||||||
|
// Check if there are any existing files in the install directory that are also within the dotfiles to install
|
||||||
handle_collisions(&args, &config_map)?;
|
handle_collisions(&args, &config_map)?;
|
||||||
|
|
||||||
// At this point there are either no conflicts or the user agreed to them
|
// At this point there are either no conflicts or the user agreed to them
|
||||||
|
@ -36,7 +40,7 @@ pub fn install_configs(args: & cli::Cli) -> std::io::Result<()> {
|
||||||
match std::os::unix::fs::symlink(config_path, target_path) {
|
match std::os::unix::fs::symlink(config_path, target_path) {
|
||||||
Ok(()) => (), // Configuration installed successfully
|
Ok(()) => (), // Configuration installed successfully
|
||||||
Err(_e) => {
|
Err(_e) => {
|
||||||
// Attempt to remove the file or directory, and then symlink the new config
|
// Attempt to remove the file or directory first, and then symlink the new config
|
||||||
match target_path.is_dir() {
|
match target_path.is_dir() {
|
||||||
true => fs_extra::dir::remove(target_path)
|
true => fs_extra::dir::remove(target_path)
|
||||||
.expect(&format!("Error: Unable to remove directory: {:?}", target_path)),
|
.expect(&format!("Error: Unable to remove directory: {:?}", target_path)),
|
||||||
|
@ -44,21 +48,23 @@ pub fn install_configs(args: & cli::Cli) -> std::io::Result<()> {
|
||||||
.expect(&format!("Error: Unable to remove file: {:?}", target_path)),
|
.expect(&format!("Error: Unable to remove file: {:?}", target_path)),
|
||||||
};
|
};
|
||||||
// Try to symlink the config again, if failure exit with error
|
// Try to symlink the config again, if failure exit with error
|
||||||
std::os::unix::fs::symlink(config_path, target_path)
|
std::os::unix::fs::symlink(config_path, target_path)?;
|
||||||
.expect(&format!("Unable to symlink config: {:?}", config_path));
|
|
||||||
},
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Handles collisions between existing files and dotfiles we're installing
|
||||||
fn handle_collisions(args : & cli::Cli,
|
fn handle_collisions(args : & cli::Cli,
|
||||||
config_map : & fs::HashMap<PathBuf, PathBuf>) -> io::Result<()> {
|
config_map : & fs::HashMap<PathBuf, PathBuf>) -> Result<()> {
|
||||||
let conflicts = check_collisions(&config_map)
|
// Check if we found any collisions in the configurations
|
||||||
.expect("Error: Failed to check collisions");
|
match check_collisions(&config_map) {
|
||||||
|
None => {
|
||||||
// If we found collisions in the configurations
|
return Ok(()) // There were no collisions, configurations pass pre-install checks
|
||||||
if &conflicts.len() > &0 {
|
},
|
||||||
|
Some(conflicts) => {
|
||||||
// Ask client if they would like to abort given the config collisions
|
// Ask client if they would like to abort given the config collisions
|
||||||
let mut msg = format!("The following configurations already exist:");
|
let mut msg = format!("The following configurations already exist:");
|
||||||
for config in conflicts.iter() {
|
for config in conflicts.iter() {
|
||||||
|
@ -69,44 +75,58 @@ fn handle_collisions(args : & cli::Cli,
|
||||||
\nAbort? Enter y/n or Y/N: ", &args.backup_dir).as_str();
|
\nAbort? Enter y/n or Y/N: ", &args.backup_dir).as_str();
|
||||||
|
|
||||||
// If we abort, exit; If we continue, back up the configs
|
// If we abort, exit; If we continue, back up the configs
|
||||||
|
// TODO: Group this in with the --force flag?; Or make a new --adopt flag?
|
||||||
match io::prompt(msg) {
|
match io::prompt(msg) {
|
||||||
true => return Err(std::io::Error::from(std::io::ErrorKind::AlreadyExists)),
|
true => return Ok(()),
|
||||||
false => {
|
false => {
|
||||||
// Backup each conflicting config at the install location
|
// Backup each conflicting config at the install location
|
||||||
for backup_target in conflicts.iter() {
|
for backup_target in conflicts.iter() {
|
||||||
backup_config(backup_target, &args.backup_dir)
|
backup_config(backup_target, &args)?;
|
||||||
.expect(format!("Error: Unable to backup config: {:?}", backup_target)
|
|
||||||
.as_str())
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
// Creates a backup of configurations that conflict
|
// Creates a backup of configurations that conflict
|
||||||
// + Backup directory location is specified by CLI --backup-dir
|
// + Backup directory location is specified by CLI --backup-dir
|
||||||
// TODO: Automatically create backup directory
|
|
||||||
// TODO: .kotignore in dotfiles repo to specify files to not install / backup
|
// TODO: .kotignore in dotfiles repo to specify files to not install / backup
|
||||||
// TODO: .kotrc in dotfiles repo or home dir to set backup-dir and install-dir?
|
// TODO: .kotrc in dotfiles repo or home dir to set backup-dir and install-dir?
|
||||||
fn backup_config(config_path: & fs::PathBuf, backup_dir: & fs::PathBuf) -> io::Result<()> {
|
fn backup_config(config_path: & fs::PathBuf, args: & cli::Cli) -> Result<()> {
|
||||||
let mut backup_path = backup_dir.to_owned();
|
let mut backup_path = args.backup_dir.to_owned();
|
||||||
backup_path.push(config_path.file_name().unwrap());
|
backup_path.push(config_path.file_name().unwrap());
|
||||||
|
|
||||||
|
// Check if the configuration we're backing up is a directory or a single file
|
||||||
match config_path.is_dir() {
|
match config_path.is_dir() {
|
||||||
true => {
|
true => {
|
||||||
// Copy directory with recursion using fs_extra::dir::move_dir
|
// Copy directory with recursion using move_dir() wrapper function
|
||||||
let mut options = fs::dir::CopyOptions::new();
|
let mut options = fs::dir::CopyOptions::new();
|
||||||
options.copy_inside = true;
|
options.copy_inside = true;
|
||||||
// TODO: Add a flag to overwrite backups, otherwise warn and abort
|
options.overwrite = args.force;
|
||||||
options.overwrite = true;
|
if let Err(e) = fs::move_dir(config_path, &backup_path, Some(&options))
|
||||||
fs::dir::move_dir(config_path, backup_path, &options)
|
.map_err(|e| e.into()) {
|
||||||
|
return Err(e)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
false => {
|
false => {
|
||||||
// Copy single configuration file
|
// Copy single configuration file
|
||||||
let mut options = fs_extra::file::CopyOptions::new();
|
let mut options = fs_extra::file::CopyOptions::new();
|
||||||
options.overwrite = true;
|
options.overwrite = args.force;
|
||||||
fs_extra::file::move_file(config_path, backup_path, &options)
|
if let Err(e) = fs::move_file(config_path, &backup_path, Some(&options))
|
||||||
|
.map_err(|e| e.into()) {
|
||||||
|
return Err(e)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
}.expect(&format!("Error: Unable to backup config: {:?}", config_path));
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
// TODO: Function to uninstall configs.
|
||||||
|
// + Loops through dotfiles and restore backup files or delete configs
|
||||||
|
fn _uninstall_configs() -> Result<()> {
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
|
@ -6,6 +6,7 @@
|
||||||
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
|
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
|
||||||
##############################################################################*/
|
##############################################################################*/
|
||||||
|
|
||||||
|
use std::path::Path;
|
||||||
use structopt::StructOpt;
|
use structopt::StructOpt;
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
@ -14,6 +15,7 @@ use structopt::StructOpt;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
/// Struct to outline behavior and features of kot CLI
|
||||||
#[derive(Debug, StructOpt)]
|
#[derive(Debug, StructOpt)]
|
||||||
#[structopt(
|
#[structopt(
|
||||||
name="kot",
|
name="kot",
|
||||||
|
@ -44,6 +46,12 @@ pub struct Cli {
|
||||||
parse(from_os_str)
|
parse(from_os_str)
|
||||||
)]
|
)]
|
||||||
pub backup_dir: std::path::PathBuf,
|
pub backup_dir: std::path::PathBuf,
|
||||||
|
|
||||||
|
#[structopt(
|
||||||
|
help="Overwrites existing backups",
|
||||||
|
short, long
|
||||||
|
)]
|
||||||
|
pub force: bool,
|
||||||
}
|
}
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
@ -52,28 +60,54 @@ pub struct Cli {
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
// Augment implementation of from_args to limit scope of StructOpt
|
/// Augment implementation of from_args to limit scope of StructOpt
|
||||||
// + Also enforces use of Cli::normalize()
|
/// + Also enforces use of Cli::normalize()
|
||||||
// https://docs.rs/structopt/0.3.23/src/structopt/lib.rs.html#1121-1126
|
/// + https://docs.rs/structopt/0.3.23/src/structopt/lib.rs.html#1121-1126
|
||||||
pub fn from_args() -> Cli {
|
pub fn from_args() -> super::Result<Cli> {
|
||||||
let s = Cli::from_clap(&Cli::clap().get_matches());
|
let s = Cli::from_clap(&Cli::clap().get_matches());
|
||||||
s.normalize()
|
s.normalize()
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cli {
|
impl Cli {
|
||||||
// Helper function to normalize arguments passed to program
|
/// Helper function to normalize arguments passed to program
|
||||||
pub fn normalize(mut self) -> Self {
|
pub fn normalize(mut self) -> super::Result<Self> {
|
||||||
// If the path to the dotfiles doesn't exist, exit with error
|
// If the path to the dotfiles doesn't exist, exit with error
|
||||||
if !&self.dotfiles_dir.exists() {
|
if !&self.dotfiles_dir.exists() {
|
||||||
panic!("Error: Dotfiles configuration at {:?} does not exist", self.dotfiles_dir);
|
panic!("Error: Dotfiles configuration at {:?} does not exist", self.dotfiles_dir);
|
||||||
}
|
}
|
||||||
self.dotfiles_dir = self.dotfiles_dir.canonicalize().unwrap();
|
self.dotfiles_dir = self.dotfiles_dir.canonicalize()?;
|
||||||
|
|
||||||
// If either the install or backup dir don't exist, create them
|
// If either the install or backup dir don't exist, create them
|
||||||
std::fs::create_dir_all(&self.install_dir).ok();
|
std::fs::create_dir_all(&self.install_dir)?;
|
||||||
self.install_dir = self.install_dir.canonicalize().unwrap();
|
self.install_dir = self.install_dir.canonicalize()?;
|
||||||
std::fs::create_dir_all(&self.backup_dir).ok();
|
std::fs::create_dir_all(&self.backup_dir)?;
|
||||||
self.backup_dir = self.backup_dir.canonicalize().unwrap();
|
self.backup_dir = self.backup_dir.canonicalize()?;
|
||||||
self
|
|
||||||
|
// + To enforce the correction when error is encountered
|
||||||
|
// Get the number of configs currently in backup directory
|
||||||
|
// + An empty backup directory returns a count of 1
|
||||||
|
let current_backups = self.backup_dir.read_dir()?.count();
|
||||||
|
// If there are files in the backup directory already
|
||||||
|
if current_backups > 1 {
|
||||||
|
// If the --force flag is not set, warn and abort
|
||||||
|
if !self.force {
|
||||||
|
panic!("\n Error: Backups already exist at {:?}\
|
||||||
|
\n Set the --force flag to overwrite configurations stored here" , self.backup_dir)
|
||||||
|
}
|
||||||
|
// If the --force flag is set, remove backups and create new
|
||||||
|
// + Move backups to /tmp/<BACKUP_DIRNAME>
|
||||||
|
// + If we encounter an error, we can move these temp files back to args.backup_dir
|
||||||
|
// + On success we can delete them since new backups will have been created at args.backup_dir
|
||||||
|
let mut options = fs_extra::dir::CopyOptions::new();
|
||||||
|
options.copy_inside = true;
|
||||||
|
options.overwrite = true;
|
||||||
|
let mut temp_path = Path::new("/tmp/").to_path_buf();
|
||||||
|
temp_path.push(self.backup_dir.file_name().unwrap());
|
||||||
|
// Move the old backups to /tmp/ and create a new empty backup directory
|
||||||
|
super::fs::move_dir( &self.backup_dir, &temp_path, Some(&options))?;
|
||||||
|
std::fs::create_dir_all(&self.backup_dir)?;
|
||||||
|
}
|
||||||
|
|
||||||
|
Ok(self)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -19,39 +19,36 @@ use std::fs;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
// Initialize and return a HashMap<config_dir, config_install_location>
|
/// Initialize and return a HashMap<config_dir, config_install_location>
|
||||||
// Later used to check each install location for conflicts before installing
|
/// + Later used to check each install location for conflicts before installing
|
||||||
// This function does not create or modify any files or directories
|
/// + This function does not create or modify any files or directories
|
||||||
pub fn get_target_paths(args: & super::cli::Cli) -> super::io::Result<HashMap<PathBuf, PathBuf>> {
|
pub fn get_target_paths(args: & super::cli::Cli) -> super::Result<HashMap<PathBuf, PathBuf>> {
|
||||||
let mut config_map = HashMap::new();
|
let mut config_map = HashMap::new();
|
||||||
|
|
||||||
// Local variable for the installation directory as an absolute path
|
// Local variable for the installation directory as an absolute path
|
||||||
let mut config_target = args.install_dir.to_owned();
|
let mut config_target = args.install_dir.to_owned();
|
||||||
// For each file or directory within the dotfiles we're installing
|
// For each file or directory within the dotfiles we're installing
|
||||||
for config_entry in fs::read_dir(&args.dotfiles_dir)? {
|
for config_entry in fs::read_dir(&args.dotfiles_dir)? {
|
||||||
// Match result from reading each item in dotfiles, return error if any
|
let entry = config_entry?;
|
||||||
match config_entry {
|
|
||||||
Err(err) => return Err(err),
|
|
||||||
Ok(entry) => {
|
|
||||||
// Create full path to target config file (or directory) by push onto install path
|
// Create full path to target config file (or directory) by push onto install path
|
||||||
config_target.push(entry.file_name());
|
config_target.push(entry.file_name());
|
||||||
|
|
||||||
// If the entry doesn't already exist, insert it into the config_map
|
// If the entry doesn't already exist, insert it into the config_map
|
||||||
// Key is full path to source config from dotfiles repo we're installing
|
// + Key is full path to source config from dotfiles repo we're installing
|
||||||
// Value is desired full path to config at final install location
|
// + Value is desired full path to config at final install location
|
||||||
// TODO: If the entry does exist, should there be an exception?
|
// TODO: If the entry does exist, should there be an exception?
|
||||||
config_map.entry(entry.path().to_owned())
|
config_map.entry(entry.path().to_owned())
|
||||||
.or_insert(config_target.to_owned());
|
.or_insert(config_target.to_owned());
|
||||||
|
|
||||||
// Reset config_target to be equal to requested install_dir
|
// Reset config_target to be equal to requested install_dir
|
||||||
config_target.pop();
|
config_target.pop();
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Ok(config_map)
|
Ok(config_map)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn check_collisions(config_map : & HashMap<PathBuf, PathBuf>) -> super::io::Result<Vec<PathBuf>> {
|
/// Checks if any config to install collides with existing files or directories
|
||||||
|
/// + Returns a count of collisions within Some(), else returns None
|
||||||
|
pub fn check_collisions(config_map : & HashMap<PathBuf, PathBuf>) -> Option<Vec<PathBuf>> {
|
||||||
let mut config_conflicts = vec![];
|
let mut config_conflicts = vec![];
|
||||||
for (_path, target_config) in config_map.iter() {
|
for (_path, target_config) in config_map.iter() {
|
||||||
// If the target configuration file or directory already exists
|
// If the target configuration file or directory already exists
|
||||||
|
@ -59,6 +56,35 @@ pub fn check_collisions(config_map : & HashMap<PathBuf, PathBuf>) -> super::io::
|
||||||
config_conflicts.push(target_config.to_owned());
|
config_conflicts.push(target_config.to_owned());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Ok(config_conflicts)
|
if config_conflicts.len() > 0 {
|
||||||
|
return Some(config_conflicts)
|
||||||
|
}
|
||||||
|
return None
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Moves a single file from one location to another; Can be used to rename files
|
||||||
|
/// + To specify options such as overwrite for the copy operation, a custom CopyOptions can be provided
|
||||||
|
pub fn move_file(src: & PathBuf, dst: & PathBuf,
|
||||||
|
options: Option< & fs_extra::file::CopyOptions>) -> super::Result<()> {
|
||||||
|
if options.is_none() {
|
||||||
|
// Default CopyOptions for moving files
|
||||||
|
let mut options = fs_extra::file::CopyOptions::new();
|
||||||
|
options.overwrite = false;
|
||||||
|
}
|
||||||
|
fs_extra::file::move_file(src, dst, options.unwrap())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Moves a directory and all of it's contents recursively
|
||||||
|
/// + To specify options such as overwrite for the copy operation, a custom CopyOptions can be provided
|
||||||
|
pub fn move_dir(src: & PathBuf, dst: & PathBuf,
|
||||||
|
options: Option< & fs_extra::dir::CopyOptions>) -> super::Result<()> {
|
||||||
|
if options.is_none() {
|
||||||
|
// Default CopyOptions for moving directories
|
||||||
|
let mut options = fs_extra::dir::CopyOptions::new();
|
||||||
|
options.copy_inside = true;
|
||||||
|
options.overwrite = false;
|
||||||
|
}
|
||||||
|
fs_extra::dir::move_dir(src, dst, options.unwrap())?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
|
@ -17,8 +17,8 @@ use std::io;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
// Asks user for y/n Y/N input, returns true/false respectively
|
/// Asks user for y/n Y/N input, returns true/false respectively
|
||||||
// + Prompt output defined by msg parameter String
|
/// + Prompt output defined by msg parameter String
|
||||||
pub fn prompt(msg: String) -> bool {
|
pub fn prompt(msg: String) -> bool {
|
||||||
println!("{}", msg);
|
println!("{}", msg);
|
||||||
let mut reply = String::new();
|
let mut reply = String::new();
|
||||||
|
|
23
src/main.rs
23
src/main.rs
|
@ -6,6 +6,8 @@
|
||||||
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
|
## Contact: shaunrd0@gmail.com | URL: www.shaunreed.com | GitHub: shaunrd0 ##
|
||||||
##############################################################################*/
|
##############################################################################*/
|
||||||
|
|
||||||
|
use std::path::PathBuf;
|
||||||
|
|
||||||
mod kot;
|
mod kot;
|
||||||
|
|
||||||
// =============================================================================
|
// =============================================================================
|
||||||
|
@ -14,9 +16,9 @@ mod kot;
|
||||||
|
|
||||||
// -----------------------------------------------------------------------------
|
// -----------------------------------------------------------------------------
|
||||||
|
|
||||||
fn main() {
|
fn main() -> kot::Result<()> {
|
||||||
// Call augmented kot::cli::from_args() to parse CLI arguments
|
// Call augmented kot::cli::from_args() to parse CLI arguments
|
||||||
let args = kot::cli::from_args();
|
let args = kot::cli::from_args()?;
|
||||||
// At this point all paths exist and have been converted to absolute paths
|
// At this point all paths exist and have been converted to absolute paths
|
||||||
println!("args: {:?}\n", args);
|
println!("args: {:?}\n", args);
|
||||||
|
|
||||||
|
@ -26,10 +28,19 @@ fn main() {
|
||||||
// If there was an error, show the error type and run settings
|
// If there was an error, show the error type and run settings
|
||||||
println!(
|
println!(
|
||||||
"Error: {:?}\n+ Configs used: {:?}\n+ Install directory: {:?}\n",
|
"Error: {:?}\n+ Configs used: {:?}\n+ Install directory: {:?}\n",
|
||||||
e.kind(), args.dotfiles_dir, args.install_dir
|
e, args.dotfiles_dir, args.install_dir
|
||||||
)
|
);
|
||||||
|
|
||||||
|
// If we were forcing a backup and met some error, revert backups to last good state
|
||||||
|
// TODO: Isolate this to limit error scope to backup related functions
|
||||||
|
if args.force {
|
||||||
|
let mut temp_path : PathBuf = kot::fs::Path::new("/tmp/").to_path_buf();
|
||||||
|
temp_path.push(args.backup_dir.file_name().unwrap());
|
||||||
|
kot::fs::move_dir(&temp_path, &args.backup_dir, None)?;
|
||||||
|
}
|
||||||
},
|
},
|
||||||
|
_ => ()
|
||||||
|
}
|
||||||
// Configurations installed successfully
|
// Configurations installed successfully
|
||||||
Ok(()) => (),
|
Ok(())
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in New Issue