use std::string::String;
use std::collections::HashMap;
use std::path::Path;
use clap::clap_app;
use std::vec::Vec;
use install_framework_core::interface::Interface;
use install_framework_core::interface::ConstructibleInterface;
use install_framework_core::interface::Installer;
use install_framework_core::interface::InstallMethod;
use install_framework_core::interface::PostInstall;
use install_framework_core::interface::PostUninstall;
use install_framework_core::builder::InstallerBuilder;
use install_framework_base::interface::BaseInterface;
mod error;
mod interpreter;
use error::CliError;
pub struct CliInterface
{
base: BaseInterface<interpreter::CliInterpreter, CliError>,
install: bool,
status: bool,
local: bool,
yes: bool,
components: Vec<String>
}
impl ConstructibleInterface<'_> for CliInterface
{
fn run(mut builder: InstallerBuilder) -> i32
{
let mut interface = CliInterface
{
base: BaseInterface::new(interpreter::CliInterpreter::new()),
install: false,
status: false,
local: true,
yes: false,
components: Vec::new()
};
return builder.run(&mut interface);
}
}
impl CliInterface
{
fn check_install_status(&mut self, installer: &mut dyn Installer, install_dir: &Path, resources: &HashMap<&'static str, &'static [u8]>) -> Result<(), CliError>
{
println!("==> Install status <==");
let components = self.base.get_components(installer, resources)?;
let state = self.base.get_installation_state(components, installer, install_dir)?;
if let Some(v) = state
{
println!("{}", v.manifest);
println!("Installed components:");
for c in v.installed_components
{
println!(" - {} ({:?})", c.name, c.version);
}
println!("Not yet installed components:");
for c in v.non_installed_components
{
println!(" - {} ({:?})", c.name, c.version);
}
}
else
{
println!("Software not installed");
return Ok(());
}
println!("==> End <==");
return Ok(());
}
}
impl Interface for CliInterface
{
type ErrorType = CliError;
fn welcome(&mut self, name: &'static str, version: &'static str, author: &'static str) -> Result<(), Self::ErrorType>
{
println!("Starting application installer for {} ({})...", name, version);
let matches = clap_app!(name =>
(version: version)
(author: author)
(about: "Powered by InstallFramework <https://gitlab.com/Yuri6037/install-framework>")
(@arg install: -i --install "Install one or more component(s)")
(@arg uninstall: -u --uninstall "Uninstall one or more component(s)")
(@arg status: -s --status "Show install status including which component(s) have been installed")
(@arg components: +takes_value +multiple -c --component "Specifies a component to be modified")
(@arg local: -l --local "Run a local user install (if permitted by this installer)")
(@arg yes: -y --yes "Agree to all licenses")
(@arg props: +multiple -p --property +takes_value "Set a property to avoid user input prompt (usefull in CI builds)")
).get_matches();
if !matches.is_present("install") && !matches.is_present("uninstall") && !matches.is_present("status")
{
return Err(CliError::Generic(String::from("Nothing to do, please specify an install, uninstall or status command")));
}
if (matches.is_present("install") && matches.is_present("uninstall"))
|| (matches.is_present("install") && matches.is_present("status"))
|| (matches.is_present("uninstall") && matches.is_present("status"))
{
return Err(CliError::Generic(String::from("Multiple action commands cannot co-exist")));
}
self.base.set_static_info(name, version, author);
self.yes = matches.is_present("yes");
self.install = matches.is_present("install");
if matches.is_present("status")
{
self.status = true;
self.install = true;
}
if let Some(obj) = matches.values_of("components")
{
for v in obj
{
self.components.push(String::from(v));
}
}
if let Some(obj) = matches.values_of("props")
{
for v in obj
{
if let Some(idx) = v.find('=')
{
let key = String::from(&v[0..idx]);
let value = String::from(&v[idx + 1..]);
self.base.set_prop(key, value);
}
else
{
return Err(CliError::Generic(format!("Invalid property {}", v)));
}
}
}
self.local = matches.is_present("local");
return Ok(());
}
fn get_install_method(&mut self) -> Result<InstallMethod, Self::ErrorType>
{
let mut m = InstallMethod::SystemInstall; if self.local
{
m = InstallMethod::UserInstall;
}
return Ok(m);
}
fn should_uninstall(&self) -> Result<bool, Self::ErrorType>
{
return Ok(!self.install);
}
fn run_install(&mut self, installer: &mut dyn Installer, dir: &Path, method: InstallMethod, resources: &HashMap<&'static str, &'static [u8]>) -> Result<(), Self::ErrorType>
{
if self.status
{
self.check_install_status(installer, dir, resources)?;
return Err(CliError::Skip);
}
println!("==> Installing components... <==");
let components = self.base.get_components(installer, resources)?;
for c in &self.components
{
if let Some(c) = components.iter().find(|s| s.name == *c)
{
println!("");
println!("Installing component {}...", c.name);
if let Some(v) = &c.version
{
println!("Version: {}", v);
}
if let Some(v) = &c.readme
{
println!("> README");
println!("{}", v);
}
if let Some(v) = &c.license
{
println!("> LICENSE");
println!("{}", v);
}
if !self.yes
{
println!("Proceed (y for yes, else no)?");
let line = interpreter::read_console()?;
if !line.starts_with("y")
{
return Err(CliError::Generic(String::from("User canceled installation")));
}
}
self.base.install_component(&c, installer, dir, method, resources)?;
}
else
{
return Err(CliError::Generic(format!("Unknown component: {}", c)));
}
}
println!("==> End <==");
return Ok(());
}
fn run_post_install(&mut self, post: &mut dyn PostInstall, dir: &Path) -> Result<(), Self::ErrorType>
{
return self.base.run_post_install(post, dir);
}
fn run_uninstall(&mut self, installer: &mut dyn Installer, dir: &Path, method: InstallMethod, resources: &HashMap<&'static str, &'static [u8]>) -> Result<(), Self::ErrorType>
{
println!("==> Uninstalling components... <==");
let components = self.base.get_components(installer, resources)?;
for c in &self.components
{
if let Some(c) = components.iter().find(|s| s.name == *c)
{
println!("");
println!("Uninstalling component {}...", c.name);
if !self.yes
{
println!("Proceed (y for yes, else no)?");
let line = interpreter::read_console()?;
if !line.starts_with("y")
{
return Err(CliError::Generic(String::from("User canceled installation")));
}
}
self.base.uninstall_component(&c, installer, dir, method)?;
}
else
{
return Err(CliError::Generic(format!("Unknown component: {}", c)));
}
}
println!("==> End <==");
return Ok(());
}
fn run_post_uninstall(&mut self, post: &mut dyn PostUninstall, dir: &Path) -> Result<(), Self::ErrorType>
{
return self.base.run_post_uninstall(post, dir);
}
fn error(&mut self, e: Self::ErrorType) -> i32
{
match e
{
CliError::Generic(s) =>
{
eprintln!("A generic error has occured: {}", s);
return 1;
},
CliError::Io(e) =>
{
eprintln!("An IO error has occured: {}", e);
return 2;
},
CliError::Skip => return 0,
CliError::Network(e) =>
{
eprintln!("A network error has occured: {}", e);
return 3;
},
CliError::Zip(e) =>
{
eprintln!("A zip error has occured: {}", e);
return 4;
}
}
}
fn finish(&mut self) -> i32
{
println!("Install succeeded!");
return 0;
}
}