use crate::host::input_stream::*;
use crate::host::programs::*;
use crate::host::scene_context::*;
use crate::host::scene_message::*;
use super::error::*;
use super::fn_command::*;
use super::list_commands::*;
use super::describe_command::*;
use super::run_command::*;
use futures::prelude::*;
use futures::future::{BoxFuture};
use std::borrow::{Cow};
use std::collections::{HashMap};
use std::marker::{PhantomData};
use std::sync::*;
pub struct CommandLauncher<TParameter, TResponse> {
commands: HashMap<String, Arc<dyn Send + Sync + Fn(&TParameter, SceneContext) -> BoxFuture<'static, ()>>>,
summaries: HashMap<String, String>,
help: HashMap<String, Cow<'static, str>>,
response: PhantomData<TResponse>,
}
impl<TParameter, TResponse> CommandLauncher<TParameter, TResponse>
where
TParameter: 'static + Unpin + Send + Sync + Clone,
TParameter: TryInto<DescribeCommandRequest>,
TResponse: 'static + Unpin + Send + SceneMessage,
TResponse: From<ListCommandResponse>,
TResponse: From<CommandError>,
TResponse: From<DescribeCommandResponse>,
{
pub fn empty() -> Self {
CommandLauncher {
commands: HashMap::new(),
summaries: HashMap::new(),
help: HashMap::new(),
response: PhantomData
}
}
pub fn with_command<TFuture>(mut self, command_name: impl Into<String>, command: impl 'static + Send + Sync + Fn(&TParameter, SceneContext) -> TFuture) -> Self
where
TFuture: 'static + Send + Future<Output=()>,
{
self.commands.insert(command_name.into(), Arc::new(move |parameter, context| command(parameter, context).boxed()));
self
}
pub fn with_summary(mut self, command_name: impl Into<String>, summary: impl Into<String>) -> Self {
self.summaries.insert(command_name.into(), summary.into());
self
}
pub fn with_help(mut self, command_name: impl Into<String>, help: impl Into<Cow<'static, str>>) -> Self {
self.help.insert(command_name.into(), help.into());
self
}
pub fn to_subprogram(self) -> impl 'static + Send + FnOnce(InputStream<RunCommand<TParameter, TResponse>>, SceneContext) -> BoxFuture<'static, ()> {
move |input, context| async move {
let mut input = input;
while let Some(run_request) = input.next().await {
if run_request.name() == LIST_COMMANDS {
let command_target = run_request.target();
let response = ListCommandResponse(self.commands.iter()
.map(|(name, _)| CommandDescription { name: name.clone() })
.chain([CommandDescription { name: LIST_COMMANDS.into() }])
.collect::<Vec<_>>());
let list_commands_response = QueryResponse::with_data(response.into());
let response_stream = context.send::<QueryResponse<TResponse>>(command_target);
if let Ok(mut response_stream) = response_stream {
response_stream.send(list_commands_response).await.ok();
}
} else if run_request.name() == DESCRIBE_COMMAND {
let command_target = run_request.target();
let request = if let Ok(request) = run_request.parameter().clone().try_into() { request } else { continue; };
let response = DescribeCommandResponse {
summary: self.summaries.get(&request.0).cloned().unwrap_or_else(|| "".into()),
help: self.help.get(&request.0).cloned().unwrap_or_else(|| format!("# {}\n\nExecutes the `{}` command\n", request.0, request.0).into()),
};
let description_command_response = QueryResponse::with_data(response.into());
let response_stream = context.send::<QueryResponse<TResponse>>(command_target);
if let Ok(mut response_stream) = response_stream {
response_stream.send(description_command_response).await.ok();
}
} else if let Some(command) = self.commands.get(run_request.name()).cloned() {
let command_target = run_request.target();
let command_output = context.spawn_command(FnCommand::<(), TResponse>::new(move |_, context| {
let command = Arc::clone(&command);
let future = (*command)(run_request.parameter(), context);
async move {
future.await
}
}), stream::empty());
if let Ok(command_output) = command_output {
let response = context.send::<QueryResponse<TResponse>>(command_target);
if let Ok(mut response) = response {
response.send(QueryResponse::with_stream(command_output)).await.ok();
}
}
} else {
let command_target = run_request.target();
let command_name = run_request.name().to_string();
let response = context.send::<QueryResponse<TResponse>>(command_target);
if let Ok(mut response) = response {
response.send(QueryResponse::with_iterator([TResponse::from(CommandError::CommandNotFound(command_name))])).await.ok();
}
}
}
}.boxed()
}
}