use std::collections::HashMap;
use std::convert::TryFrom;
use std::env;
use std::error::Error;
use std::fmt::Display;
use std::fs;
use std::io::{Read, Write};
#[cfg(feature = "async-std")]
use async_std::prelude::*;
use serde::{Deserialize, Serialize};
pub use crate::handler::app::{CommandLineHandler, DefaultCommandLineHandler};
#[cfg(feature = "async")]
use crate::handler::async_app::DefaultAsyncCLIHandler;
use crate::*;
pub struct App<
'b,
T: Serialize + for<'a> Deserialize<'a>,
H: CommandLineHandler,
> {
pub commands: HashMap<&'b str, Command<T>>,
pub state: T,
pub handler: H,
pub description: String,
}
impl<
'f: 't,
't,
T: Serialize + for<'a> Deserialize<'a>,
M: Display,
H: Handler<T>,
I: InputHandler,
> TryFrom<Shell<'f, T, M, H, I>> for App<'t, T, DefaultCommandLineHandler>
{
type Error = Box<dyn Error>;
fn try_from(shell: Shell<'f, T, M, H, I>) -> Result<Self, Box<dyn Error>> {
let mut this = Self {
commands: shell.commands,
state: shell.state,
handler: DefaultCommandLineHandler { proj_name: None },
description: shell.description,
};
this.load_cache()?;
Ok(this)
}
}
#[cfg(feature = "async")]
impl<'f: 't, 't, T: Serialize + for<'a> Deserialize<'a> + Send>
App<'t, T, DefaultAsyncCLIHandler>
{
pub fn try_from_async<M: Display, H: AsyncHandler<T>, I: InputHandler>(
shell: Shell<'f, T, M, H, I>,
) -> Result<Self, Box<dyn Error>> {
let mut this = Self {
commands: shell.commands,
state: shell.state,
handler: DefaultAsyncCLIHandler { proj_name: None },
description: shell.description,
};
this.load_cache()?;
Ok(this)
}
}
impl<T: Serialize + for<'a> Deserialize<'a>>
App<'_, T, DefaultCommandLineHandler>
{
pub fn new(state: T, project_name: String) -> Result<Self, Box<dyn Error>> {
let mut this = Self {
commands: HashMap::new(),
state,
handler: DefaultCommandLineHandler {
proj_name: Some(project_name),
},
description: String::new(),
};
this.load_cache()?;
Ok(this)
}
}
impl<T: Serialize + for<'a> Deserialize<'a>, H: CommandLineHandler>
App<'_, T, H>
{
pub fn new_with_handler(
state: T,
handler: H,
) -> Result<Self, Box<dyn Error>> {
let mut this = Self {
commands: HashMap::new(),
state,
handler,
description: String::new(),
};
this.load_cache()?;
Ok(this)
}
pub fn load_cache(&mut self) -> Result<(), Box<dyn Error>> {
if let Some(cache) = self.handler.get_cache() {
if let Ok(mut file) = fs::File::open(cache) {
let mut string = String::new();
file.read_to_string(&mut string)?;
self.state = serde_json::from_str(&string)?;
}
}
Ok(())
}
}
impl<
T: Serialize + for<'a> Deserialize<'a>,
H: CommandLineHandler + Handler<T>,
> App<'_, T, H>
{
pub fn run_vec(&mut self, vec: Vec<String>) -> std::io::Result<bool> {
let result = self.handler.handle(
vec,
&self.commands,
&mut self.state,
&self.description,
);
match result {
true => {
if let Some(cache) = self.handler.get_cache() {
fs::remove_file(cache)?;
}
}
false => {
if let Some(cache) = self.handler.get_cache() {
if let Some(dir) = cache.parent() {
fs::create_dir_all(dir)?;
}
let mut file = fs::File::create(cache)?;
file.write_all(
serde_json::to_string(&self.state)?.as_bytes(),
)?;
}
}
}
Ok(result)
}
pub fn run_args(&mut self) -> std::io::Result<bool> {
self.run_vec(env::args().collect())
}
}
#[cfg(feature = "async")]
impl<
T: Serialize + for<'a> Deserialize<'a> + Send,
H: CommandLineHandler + AsyncHandler<T>,
> App<'_, T, H>
{
pub async fn run_vec_async(
&mut self,
vec: Vec<String>,
) -> std::io::Result<bool> {
let result = self
.handler
.handle_async(
vec,
&self.commands,
&mut self.state,
&self.description,
)
.await;
match result {
true => {
if let Some(cache) = self.handler.get_cache() {
cfg_if::cfg_if! {
if #[cfg(feature = "async-std")] {
async_std::fs::remove_file(cache).await?;
} else if #[cfg(feature = "tokio")] {
tokio::fs::remove_file(cache).await?;
} else {
fs::remove_file(cache)?;
}
}
}
}
false => {
if let Some(cache) = self.handler.get_cache() {
cfg_if::cfg_if! {
if #[cfg(feature = "async-std")] {
if let Some(dir) = cache.parent() {
async_std::fs::create_dir_all(dir).await?;
}
let mut file = async_std::fs::File::create(cache).await?;
file.write_all(
serde_json::to_string(&self.state)?.as_bytes(),
).await?;
} else if #[cfg(feature = "tokio")] {
if let Some(dir) = cache.parent() {
tokio::fs::create_dir_all(dir).await?;
}
let mut file = tokio::fs::File::create(cache).await?;
file.write_all(
serde_json::to_string(&self.state)?.as_bytes(),
).await?;
} else {
if let Some(dir) = cache.parent() {
fs::create_dir_all(dir)?;
}
let mut file = fs::File::create(cache)?;
file.write_all(
serde_json::to_string(&self.state)?.as_bytes(),
)?;
}
}
}
}
}
Ok(result)
}
pub async fn run_args_async(&mut self) -> std::io::Result<bool> {
self.run_vec_async(env::args().collect()).await
}
}