use capnp::{
message::{self, ReaderOptions},
serialize_packed as capn_serialize,
};
use serde::{Deserialize, Serialize};
use shellac_capnp::{request::Reader as RequestReader, response::Builder as ResponseBuilder};
use std::{
convert::TryInto,
fmt,
io::{self, BufRead, Write},
};
#[allow(dead_code)]
mod shellac_capnp {
include!(concat!(env!("OUT_DIR"), "/shellac_capnp.rs"));
}
#[derive(Debug)]
pub enum Error {
WordOutOfRange { word: u16, argv_len: u32 },
Capnp(capnp::Error),
NotInSchema(capnp::NotInSchema),
}
impl From<capnp::NotInSchema> for Error {
fn from(cause: capnp::NotInSchema) -> Self { Error::NotInSchema(cause) }
}
impl From<capnp::Error> for Error {
fn from(cause: capnp::Error) -> Self { Error::Capnp(cause) }
}
impl std::error::Error for Error {}
impl fmt::Display for Error {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
match self {
Error::WordOutOfRange { word, argv_len } => write!(
f,
"the word {} can't be autocompleted because it is out of bound for argc = {}",
word, argv_len
),
Error::Capnp(e) => write!(f, "{}", e),
Error::NotInSchema(e) => write!(f, "{}", e),
}
}
}
#[derive(Default, Clone, Debug, Hash, PartialEq, Eq, Deserialize)]
pub struct AutocompRequest {
argv: Vec<String>,
word: u16,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize)]
pub struct Reply<T> {
pub choices: Vec<Suggestion<T>>,
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize)]
pub enum SuggestionType<T> {
Literal(T),
Command { command: Vec<T>, prefix: T },
}
#[derive(Clone, Debug, Hash, PartialEq, Eq, Serialize)]
pub struct Suggestion<T> {
suggestion: SuggestionType<T>,
description: T,
}
impl AutocompRequest {
pub fn new(argv: Vec<String>, word: u16) -> Self {
if word as usize >= argv.len() {
eprintln!(
"Word {} is out of bound for argv '{:?}' in ShellAC autocompletion request",
word, argv
);
panic!();
}
Self { argv, word }
}
}
impl<T> Suggestion<T> {
pub const fn new(suggestion: SuggestionType<T>, description: T) -> Self {
Self { suggestion, description }
}
pub const fn description(&self) -> &T { &self.description }
pub const fn suggestion(&self) -> &SuggestionType<T> { &self.suggestion }
}
type Out<'a, E> = std::iter::Map<
capnp::traits::ListIter<
capnp::struct_list::Reader<'a, shellac_capnp::suggestion::Owned>,
shellac_capnp::suggestion::Reader<'a>,
>,
fn(shellac_capnp::suggestion::Reader<'a>) -> Result<(SuggestionType<&'a str>, &'a str), E>,
>;
fn convert<T: From<capnp::Error> + From<capnp::NotInSchema>>(
choice: shellac_capnp::suggestion::Reader,
) -> Result<(SuggestionType<&str>, &str), T> {
Ok((
match choice.get_arg().which()? {
shellac_capnp::suggestion::arg::Which::Literal(lit) => SuggestionType::Literal(lit?),
shellac_capnp::suggestion::arg::Which::Command(cmd) => {
let cmd = cmd?;
let prefix = cmd.get_prefix()?;
let command = cmd.get_args()?.iter().collect::<Result<Vec<_>, _>>()?;
SuggestionType::Command { command, prefix }
}
},
choice.get_description()?,
))
}
pub fn read_reply<R, T, E, F>(reader: &mut R, f: F) -> Result<T, E>
where
E: From<capnp::Error> + From<capnp::NotInSchema>,
R: BufRead,
F: FnOnce(Out<'_, E>) -> Result<T, E>,
{
let request = capnp::serialize_packed::read_message(reader, ReaderOptions::default())?;
let choices = request.get_root::<shellac_capnp::response::Reader>()?.get_choices()?;
f(choices.iter().map(convert))
}
pub fn write_request<W: Write>(writer: &mut W, input: &AutocompRequest) -> Result<(), io::Error> {
let mut message = capnp::message::Builder::new_default();
let mut output = message.init_root::<shellac_capnp::request::Builder>();
output.set_word(input.word);
let len = input.argv.len().try_into().expect("Too many output choices");
let mut reply_argv = output.init_argv(len);
for (i, arg) in input.argv.iter().enumerate() {
reply_argv.reborrow().set(i as u32, arg);
}
capnp::serialize_packed::write_message(writer, &message)
}
pub fn write_reply<'a, W: Write, T: AsRef<str> + 'a, I: IntoIterator<Item = &'a Suggestion<T>>>(
writer: &mut W,
choices: I,
) -> Result<(), io::Error>
where
I::IntoIter: ExactSizeIterator,
{
let mut message = message::Builder::new_default();
let reply = message.init_root::<ResponseBuilder>();
let choices = choices.into_iter();
let mut reply_choices =
reply.init_choices(choices.len().try_into().expect("Too many output choices"));
for (i, choice) in choices.enumerate() {
let mut reply_choice = reply_choices.reborrow().get(i as u32);
match choice.suggestion() {
SuggestionType::Literal(lit) => {
reply_choice.reborrow().init_arg().set_literal(lit.as_ref())
}
SuggestionType::Command { command, prefix } => {
let mut builder = reply_choice.reborrow().init_arg().init_command();
builder.set_prefix(prefix.as_ref());
let mut args = builder.init_args(command.len() as u32);
for (i, arg) in command.iter().enumerate() {
args.set(i as u32, arg.as_ref());
}
}
}
reply_choice.set_description(choice.description().as_ref());
}
capn_serialize::write_message(writer, &message)
}
pub fn read_request<
'a,
R: BufRead + 'a,
T,
E: From<Error>,
F: FnOnce(u16, capnp::text_list::Reader<'_>, &RequestReader) -> Result<T, E>,
>(
reader: &mut R,
f: F,
) -> Result<T, E> {
let request =
capn_serialize::read_message(reader, ReaderOptions::default()).map_err(Into::into)?;
let request = request.get_root::<RequestReader>().map_err(Into::into)?;
let argv = request.get_argv().map_err(Into::into)?;
let word = request.get_word();
if u32::from(word) > argv.len() {
Err(Error::WordOutOfRange { word, argv_len: argv.len() }.into())
} else {
f(word, argv, &request)
}
}