mirror of
https://codeberg.org/unspeaker/tengri.git
synced 2025-12-06 19:56:44 +01:00
input: add InputMap; dsl/output/tui: Atom->Dsl
Some checks are pending
/ build (push) Waiting to run
Some checks are pending
/ build (push) Waiting to run
This commit is contained in:
parent
47b7f7e7f9
commit
35ad371205
15 changed files with 374 additions and 277 deletions
|
|
@ -1,54 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
#[macro_export] macro_rules! defcom {
|
||||
([$self:ident, $app:ident:$App:ty] $(($Command:ident $((
|
||||
$Variant:ident [$($($param:ident: $Param:ty),+)?] $expr:expr
|
||||
))*))*) => {
|
||||
$(#[derive(Clone, Debug)] pub enum $Command {
|
||||
$($Variant $(($($Param),+))?),*
|
||||
})*
|
||||
$(command!(|$self: $Command, $app: $App|match $self {
|
||||
$($Command::$Variant $(($($param),+))? => $expr),*
|
||||
});)*
|
||||
};
|
||||
(|$self:ident, $app:ident:$App:ty| $($Command:ident { $(
|
||||
$Variant:ident $(($($param:ident: $Param:ty),+))? => $expr:expr
|
||||
)* $(,)? })*) => {
|
||||
$(#[derive(Clone, Debug)] pub enum $Command {
|
||||
$($Variant $(($($Param),+))?),*
|
||||
})*
|
||||
$(command!(|$self: $Command, $app: $App|match $self {
|
||||
$($Command::$Variant $(($($param),+))? => $expr),*
|
||||
});)*
|
||||
};
|
||||
}
|
||||
|
||||
#[macro_export] macro_rules! command {
|
||||
($(<$($l:lifetime),+>)?|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => {
|
||||
impl$(<$($l),+>)? Command<$State> for $Command {
|
||||
fn execute ($self, $state: &mut $State) -> Perhaps<Self> {
|
||||
Ok($handler)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
pub trait Command<S>: Send + Sync + Sized {
|
||||
fn execute (self, state: &mut S) -> Perhaps<Self>;
|
||||
fn delegate <T> (self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps<T>
|
||||
where Self: Sized
|
||||
{
|
||||
Ok(self.execute(state)?.map(wrap))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T: Command<S>> Command<S> for Option<T> {
|
||||
fn execute (self, _: &mut S) -> Perhaps<Self> {
|
||||
Ok(None)
|
||||
}
|
||||
fn delegate <U> (self, _: &mut S, _: impl Fn(Self)->U) -> Perhaps<U>
|
||||
where Self: Sized
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
21
input/src/input_command.rs
Normal file
21
input/src/input_command.rs
Normal file
|
|
@ -0,0 +1,21 @@
|
|||
use crate::*;
|
||||
|
||||
pub trait Command<S>: Send + Sync + Sized {
|
||||
fn execute (self, state: &mut S) -> Perhaps<Self>;
|
||||
fn delegate <T> (self, state: &mut S, wrap: impl Fn(Self)->T) -> Perhaps<T>
|
||||
where Self: Sized
|
||||
{
|
||||
Ok(self.execute(state)?.map(wrap))
|
||||
}
|
||||
}
|
||||
|
||||
impl<S, T: Command<S>> Command<S> for Option<T> {
|
||||
fn execute (self, _: &mut S) -> Perhaps<Self> {
|
||||
Ok(None)
|
||||
}
|
||||
fn delegate <U> (self, _: &mut S, _: impl Fn(Self)->U) -> Perhaps<U>
|
||||
where Self: Sized
|
||||
{
|
||||
Ok(None)
|
||||
}
|
||||
}
|
||||
162
input/src/input_dsl.rs
Normal file
162
input/src/input_dsl.rs
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
use crate::*;
|
||||
|
||||
/// A [Command] that can be constructed from a [Token].
|
||||
pub trait DslCommand<'a, C>: TryFromDsl<'a, C> + Command<C> {}
|
||||
|
||||
impl<'a, C, T: TryFromDsl<'a, C> + Command<C>> DslCommand<'a, C> for T {}
|
||||
|
||||
/// [Input] state that can be matched against a [Value].
|
||||
pub trait DslInput: Input {
|
||||
fn matches_dsl (&self, token: &str) -> bool;
|
||||
}
|
||||
|
||||
/// A pre-configured mapping of input events to commands.
|
||||
pub trait KeyMap<'a, S, C: DslCommand<'a, S>, I: DslInput> {
|
||||
/// Try to find a command that matches the current input event.
|
||||
fn command (&'a self, state: &'a S, input: &'a I) -> Option<C>;
|
||||
}
|
||||
|
||||
/// A [SourceIter] can be a [KeyMap].
|
||||
impl<'a, S, C: DslCommand<'a, S>, I: DslInput> KeyMap<'a, S, C, I> for SourceIter<'a> {
|
||||
fn command (&'a self, state: &'a S, input: &'a I) -> Option<C> {
|
||||
let mut iter = self.clone();
|
||||
while let Some((token, rest)) = iter.next() {
|
||||
iter = rest;
|
||||
match token {
|
||||
Token { value: Value::Exp(0, exp_iter), .. } => {
|
||||
let mut exp_iter = exp_iter.clone();
|
||||
match exp_iter.next() {
|
||||
Some(Token { value: Value::Sym(binding), .. }) => {
|
||||
if input.matches_dsl(binding) {
|
||||
if let Some(command) = C::try_from_expr(state, exp_iter.clone()) {
|
||||
return Some(command)
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => panic!("invalid config (expected symbol)")
|
||||
}
|
||||
},
|
||||
_ => panic!("invalid config (expected expression)")
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A [TokenIter] can be a [KeyMap].
|
||||
impl<'a, S, C: DslCommand<'a, S>, I: DslInput> KeyMap<'a, S, C, I> for TokenIter<'a> {
|
||||
fn command (&'a self, state: &'a S, input: &'a I) -> Option<C> {
|
||||
let mut iter = self.clone();
|
||||
while let Some(next) = iter.next() {
|
||||
match next {
|
||||
Token { value: Value::Exp(0, exp_iter), .. } => {
|
||||
let mut exp_iter = exp_iter.clone();
|
||||
match exp_iter.next() {
|
||||
Some(Token { value: Value::Sym(binding), .. }) => {
|
||||
if input.matches_dsl(binding) {
|
||||
if let Some(command) = C::try_from_expr(state, exp_iter.clone()) {
|
||||
return Some(command)
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => panic!("invalid config (expected symbol)")
|
||||
}
|
||||
},
|
||||
_ => panic!("invalid config (expected expression)")
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A collection of pre-configured mappings of input events to commands,
|
||||
/// which may be made available subject to given conditions.
|
||||
pub struct InputMap<'a, S, C: DslCommand<'a, S>, I: DslInput> {
|
||||
layers: Vec<(
|
||||
Box<dyn Fn(&S)->bool + Send + Sync + 'a>,
|
||||
Box<dyn KeyMap<'a, S, C, I> + Send + Sync + 'a>
|
||||
)>,
|
||||
}
|
||||
|
||||
impl<'a, S, C: DslCommand<'a, S>, I: DslInput> Default for InputMap<'a, S, C, I> {
|
||||
fn default () -> Self {
|
||||
Self {
|
||||
layers: vec![]
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, S, C: DslCommand<'a, S>, I: DslInput> InputMap<'a, S, C, I> {
|
||||
pub fn new (keymap: impl KeyMap<'a, S, C, I> + Send + Sync + 'a) -> Self {
|
||||
Self::default().layer(keymap)
|
||||
}
|
||||
pub fn layer (
|
||||
mut self, keymap: impl KeyMap<'a, S, C, I> + Send + Sync + 'a
|
||||
) -> Self {
|
||||
self.add_layer(keymap);
|
||||
self
|
||||
}
|
||||
pub fn add_layer (
|
||||
&mut self, keymap: impl KeyMap<'a, S, C, I> + Send + Sync + 'a
|
||||
) -> &mut Self {
|
||||
self.add_layer_if(|_|true, keymap);
|
||||
self
|
||||
}
|
||||
pub fn layer_if (
|
||||
mut self,
|
||||
condition: impl Fn(&S)->bool + Send + Sync + 'a,
|
||||
keymap: impl KeyMap<'a, S, C, I> + Send + Sync + 'a
|
||||
) -> Self {
|
||||
self.add_layer_if(condition, keymap);
|
||||
self
|
||||
}
|
||||
pub fn add_layer_if (
|
||||
&mut self,
|
||||
condition: impl Fn(&S)->bool + Send + Sync + 'a,
|
||||
keymap: impl KeyMap<'a, S, C, I> + Send + Sync + 'a
|
||||
) -> &mut Self {
|
||||
self.layers.push((Box::new(condition), Box::new(keymap)));
|
||||
self
|
||||
}
|
||||
}
|
||||
|
||||
/// An [InputMap] can be a [KeyMap].
|
||||
impl<'a, S, C: DslCommand<'a, S>, I: DslInput> KeyMap<'a, S, C, I> for InputMap<'a, S, C, I> {
|
||||
fn command (&'a self, state: &'a S, input: &'a I) -> Option<C> {
|
||||
todo!()
|
||||
}
|
||||
}
|
||||
|
||||
//fn handle <C> (&self, state: &mut T, input: &U) -> Perhaps<bool> {
|
||||
//for layer in self.layers.iter() {
|
||||
//if !(layer.0)(state) {
|
||||
//continue
|
||||
//}
|
||||
//let command: Option<C> = SourceIter(layer.1).command::<_, _, _>(state, input);
|
||||
//if let Some(command) = command {
|
||||
//if let Some(undo) = command.execute(state)? {
|
||||
////app.history.push(undo);
|
||||
//}
|
||||
//return Ok(Some(true))
|
||||
//}
|
||||
//}
|
||||
//Ok(None)
|
||||
//}
|
||||
//}
|
||||
//fn layer (mut self, keymap: &'static str) -> Self {
|
||||
//self.add_layer(keymap);
|
||||
//self
|
||||
//}
|
||||
//fn layer_if (mut self, condition: impl Fn(&T)->bool + 'static, keymap: &'static str) -> Self {
|
||||
//self.add_layer_if(condition, keymap);
|
||||
//self
|
||||
//}
|
||||
//fn add_layer (&mut self, keymap: &'static str) -> &mut Self {
|
||||
//self.layers.push((Box::new(|_|true), keymap));
|
||||
//self
|
||||
//}
|
||||
//fn add_layer_if (&mut self, condition: impl Fn(&T)->bool + 'static, keymap: &'static str) -> &mut Self {
|
||||
//self.layers.push((Box::new(condition), keymap));
|
||||
//self
|
||||
//}
|
||||
//}
|
||||
131
input/src/input_macros.rs
Normal file
131
input/src/input_macros.rs
Normal file
|
|
@ -0,0 +1,131 @@
|
|||
use crate::*;
|
||||
|
||||
/** Implement `Command` for given `State` and collection
|
||||
* of `Variant` to `handler` mappings. */
|
||||
#[macro_export] macro_rules! defcom {
|
||||
([$self:ident, $state:ident:$State:ty] $(($Command:ident $((
|
||||
$Variant:ident [$($($param:ident: $Param:ty),+)?] $expr:expr
|
||||
))*))*) => {
|
||||
$(#[derive(Clone, Debug)] pub enum $Command {
|
||||
$($Variant $(($($Param),+))?),*
|
||||
})*
|
||||
$(command!(|$self: $Command, $state: $State|match $self {
|
||||
$($Command::$Variant $(($($param),+))? => $expr),*
|
||||
});)*
|
||||
};
|
||||
(|$self:ident, $state:ident:$State:ty| $($Command:ident { $(
|
||||
$Variant:ident $(($($param:ident: $Param:ty),+))? => $expr:expr
|
||||
)* $(,)? })*) => {
|
||||
$(#[derive(Clone, Debug)] pub enum $Command {
|
||||
$($Variant $(($($Param),+))?),*
|
||||
})*
|
||||
$(command!(|$self: $Command, $state: $State|match $self {
|
||||
$($Command::$Variant $(($($param),+))? => $expr),*
|
||||
});)*
|
||||
};
|
||||
}
|
||||
|
||||
/** Implement `Command` for given `State` and `handler` */
|
||||
#[macro_export] macro_rules! command {
|
||||
($(<$($l:lifetime),+>)?|$self:ident:$Command:ty,$state:ident:$State:ty|$handler:expr) => {
|
||||
impl$(<$($l),+>)? Command<$State> for $Command {
|
||||
fn execute ($self, $state: &mut $State) -> Perhaps<Self> {
|
||||
Ok($handler)
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
/** Implement `DslCommand` for given `State` and `Command` */
|
||||
#[cfg(feature = "dsl")]
|
||||
#[macro_export] macro_rules! atom_command {
|
||||
($Command:ty : |$state:ident:<$State:ident: $Trait:path>| { $((
|
||||
// identifier
|
||||
$key:literal [
|
||||
// named parameters
|
||||
$(
|
||||
// argument name
|
||||
$arg:ident
|
||||
// if type is not provided defaults to Dsl
|
||||
$(
|
||||
// type:name separator
|
||||
:
|
||||
// argument type
|
||||
$type:ty
|
||||
)?
|
||||
),*
|
||||
// rest of parameters
|
||||
$(, ..$rest:ident)?
|
||||
]
|
||||
// bound command:
|
||||
$command:expr
|
||||
))* }) => {
|
||||
impl<'a, $State: $Trait> TryFromDsl<'a, $State> for $Command {
|
||||
fn try_from_expr ($state: &$State, iter: TokenIter) -> Option<Self> {
|
||||
let iter = iter.clone();
|
||||
match iter.next() {
|
||||
$(Some(Token { value: Value::Key($key), .. }) => {
|
||||
let iter = iter.clone();
|
||||
$(
|
||||
let next = iter.next();
|
||||
if next.is_none() { panic!("no argument: {}", stringify!($arg)); }
|
||||
let $arg = next.unwrap();
|
||||
$(let $arg: Option<$type> = Context::<$type>::get($state, &$arg.value);)?
|
||||
)*
|
||||
$(let $rest = iter.clone();)?
|
||||
return $command
|
||||
},)*
|
||||
_ => None
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
($Command:ty : |$state:ident:$State:ty| { $((
|
||||
// identifier
|
||||
$key:literal [
|
||||
// named parameters
|
||||
$(
|
||||
// argument name
|
||||
$arg:ident
|
||||
// if type is not provided defaults to Dsl
|
||||
$(
|
||||
// type:name separator
|
||||
:
|
||||
// argument type
|
||||
$type:ty
|
||||
)?
|
||||
),*
|
||||
// rest of parameters
|
||||
$(, ..$rest:ident)?
|
||||
]
|
||||
// bound command:
|
||||
$command:expr
|
||||
))* }) => {
|
||||
impl<'a> TryFromDsl<'a, $State> for $Command {
|
||||
fn try_from_expr ($state: &$State, iter: TokenIter) -> Option<Self> {
|
||||
let mut iter = iter.clone();
|
||||
match iter.next() {
|
||||
$(Some(Token { value: Value::Key($key), .. }) => {
|
||||
let mut iter = iter.clone();
|
||||
$(
|
||||
let next = iter.next();
|
||||
if next.is_none() { panic!("no argument: {}", stringify!($arg)); }
|
||||
let $arg = next.unwrap();
|
||||
$(let $arg: Option<$type> = Context::<$type>::get($state, &$arg.value);)?
|
||||
)*
|
||||
$(let $rest = iter.clone();)?
|
||||
return $command
|
||||
}),*
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
(@bind $state:ident =>$arg:ident ? : $type:ty) => {
|
||||
let $arg: Option<$type> = Context::<$type>::get($state, $arg);
|
||||
};
|
||||
(@bind $state:ident => $arg:ident : $type:ty) => {
|
||||
let $arg: $type = Context::<$type>::get_or_fail($state, $arg);
|
||||
};
|
||||
}
|
||||
|
|
@ -1,177 +0,0 @@
|
|||
use crate::*;
|
||||
|
||||
/// [Input] state that can be matched against a [Value].
|
||||
pub trait AtomInput: Input {
|
||||
fn matches_atom (&self, token: &str) -> bool;
|
||||
}
|
||||
|
||||
#[cfg(feature = "dsl")]
|
||||
pub trait KeyMap<'a> {
|
||||
/// Try to find a command that matches the current input event.
|
||||
fn command <S, C: AtomCommand<'a, S>, I: AtomInput> (&'a self, state: &'a S, input: &'a I)
|
||||
-> Option<C>;
|
||||
}
|
||||
|
||||
#[cfg(feature = "dsl")]
|
||||
impl<'a> KeyMap<'a> for SourceIter<'a> {
|
||||
fn command <S, C: AtomCommand<'a, S>, I: AtomInput> (&'a self, state: &'a S, input: &'a I)
|
||||
-> Option<C>
|
||||
{
|
||||
let mut iter = self.clone();
|
||||
while let Some((token, rest)) = iter.next() {
|
||||
iter = rest;
|
||||
match token {
|
||||
Token { value: Value::Exp(0, exp_iter), .. } => {
|
||||
let mut exp_iter = exp_iter.clone();
|
||||
match exp_iter.next() {
|
||||
Some(Token { value: Value::Sym(binding), .. }) => {
|
||||
if input.matches_atom(binding) {
|
||||
if let Some(command) = C::try_from_expr(state, exp_iter.clone()) {
|
||||
return Some(command)
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => panic!("invalid config (expected symbol)")
|
||||
}
|
||||
},
|
||||
_ => panic!("invalid config (expected expression)")
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(feature = "dsl")]
|
||||
impl<'a> KeyMap<'a> for TokenIter<'a> {
|
||||
fn command <S, C: AtomCommand<'a, S>, I: AtomInput> (&'a self, state: &'a S, input: &'a I)
|
||||
-> Option<C>
|
||||
{
|
||||
let mut iter = self.clone();
|
||||
while let Some(next) = iter.next() {
|
||||
match next {
|
||||
Token { value: Value::Exp(0, exp_iter), .. } => {
|
||||
let mut exp_iter = exp_iter.clone();
|
||||
match exp_iter.next() {
|
||||
Some(Token { value: Value::Sym(binding), .. }) => {
|
||||
if input.matches_atom(binding) {
|
||||
if let Some(command) = C::try_from_expr(state, exp_iter.clone()) {
|
||||
return Some(command)
|
||||
}
|
||||
}
|
||||
},
|
||||
_ => panic!("invalid config (expected symbol)")
|
||||
}
|
||||
},
|
||||
_ => panic!("invalid config (expected expression)")
|
||||
}
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
/// A [Command] that can be constructed from a [Token].
|
||||
#[cfg(feature = "dsl")]
|
||||
pub trait AtomCommand<'a, C>: TryFromAtom<'a, C> + Command<C> {}
|
||||
|
||||
#[cfg(feature = "dsl")]
|
||||
impl<'a, C, T: TryFromAtom<'a, C> + Command<C>> AtomCommand<'a, C> for T {}
|
||||
|
||||
/** Implement `AtomCommand` for given `State` and `Command` */
|
||||
#[cfg(feature = "dsl")]
|
||||
#[macro_export] macro_rules! atom_command {
|
||||
($Command:ty : |$state:ident:<$State:ident: $Trait:path>| { $((
|
||||
// identifier
|
||||
$key:literal [
|
||||
// named parameters
|
||||
$(
|
||||
// argument name
|
||||
$arg:ident
|
||||
// if type is not provided defaults to Atom
|
||||
$(
|
||||
// type:name separator
|
||||
:
|
||||
// argument type
|
||||
$type:ty
|
||||
)?
|
||||
),*
|
||||
// rest of parameters
|
||||
$(, ..$rest:ident)?
|
||||
]
|
||||
// bound command:
|
||||
$command:expr
|
||||
))* }) => {
|
||||
impl<'a, $State: $Trait> TryFromAtom<'a, $State> for $Command {
|
||||
fn try_from_expr ($state: &$State, iter: TokenIter) -> Option<Self> {
|
||||
let iter = iter.clone();
|
||||
match iter.next() {
|
||||
$(Some(Token { value: Value::Key($key), .. }) => {
|
||||
let iter = iter.clone();
|
||||
$(
|
||||
let next = iter.next();
|
||||
if next.is_none() { panic!("no argument: {}", stringify!($arg)); }
|
||||
let $arg = next.unwrap();
|
||||
$(let $arg: Option<$type> = Context::<$type>::get($state, &$arg.value);)?
|
||||
)*
|
||||
$(let $rest = iter.clone();)?
|
||||
return $command
|
||||
},)*
|
||||
_ => None
|
||||
}
|
||||
None
|
||||
}
|
||||
}
|
||||
};
|
||||
($Command:ty : |$state:ident:$State:ty| { $((
|
||||
// identifier
|
||||
$key:literal [
|
||||
// named parameters
|
||||
$(
|
||||
// argument name
|
||||
$arg:ident
|
||||
// if type is not provided defaults to Atom
|
||||
$(
|
||||
// type:name separator
|
||||
:
|
||||
// argument type
|
||||
$type:ty
|
||||
)?
|
||||
),*
|
||||
// rest of parameters
|
||||
$(, ..$rest:ident)?
|
||||
]
|
||||
// bound command:
|
||||
$command:expr
|
||||
))* }) => {
|
||||
impl<'a> TryFromAtom<'a, $State> for $Command {
|
||||
fn try_from_expr ($state: &$State, iter: TokenIter) -> Option<Self> {
|
||||
let mut iter = iter.clone();
|
||||
match iter.next() {
|
||||
$(Some(Token { value: Value::Key($key), .. }) => {
|
||||
let mut iter = iter.clone();
|
||||
$(
|
||||
let next = iter.next();
|
||||
if next.is_none() { panic!("no argument: {}", stringify!($arg)); }
|
||||
let $arg = next.unwrap();
|
||||
$(let $arg: Option<$type> = Context::<$type>::get($state, &$arg.value);)?
|
||||
)*
|
||||
$(let $rest = iter.clone();)?
|
||||
return $command
|
||||
}),*
|
||||
_ => None
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
(@bind $state:ident =>$arg:ident ? : $type:ty) => {
|
||||
let $arg: Option<$type> = Context::<$type>::get($state, $arg);
|
||||
};
|
||||
(@bind $state:ident => $arg:ident : $type:ty) => {
|
||||
let $arg: $type = Context::<$type>::get_or_fail($state, $arg);
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "dsl"))]
|
||||
#[test] fn test_atom_keymap () -> Usually<()> {
|
||||
let keymap = SourceIter::new("");
|
||||
Ok(())
|
||||
}
|
||||
|
|
@ -1,19 +1,15 @@
|
|||
#![feature(associated_type_defaults)]
|
||||
mod command; pub use self::command::*;
|
||||
mod handle; pub use self::handle::*;
|
||||
mod keymap; pub use self::keymap::*;
|
||||
pub(crate) use std::error::Error;
|
||||
pub(crate) type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
|
||||
#[cfg(test)] pub(crate) type Usually<T> = Result<T, Box<dyn Error>>;
|
||||
|
||||
#[cfg(feature = "dsl")] pub(crate) use ::tengri_dsl::*;
|
||||
#[cfg(feature = "dsl")] mod input_dsl;
|
||||
#[cfg(feature = "dsl")] pub use self::input_dsl::*;
|
||||
|
||||
/// Standard error trait.
|
||||
pub(crate) use std::error::Error;
|
||||
|
||||
/// Standard optional result type.
|
||||
pub(crate) type Perhaps<T> = Result<Option<T>, Box<dyn Error>>;
|
||||
|
||||
/// Standard result type.
|
||||
#[cfg(test)]
|
||||
pub(crate) type Usually<T> = Result<T, Box<dyn Error>>;
|
||||
mod input_macros;
|
||||
mod input_command; pub use self::input_command::*;
|
||||
mod input_handle; pub use self::input_handle::*;
|
||||
|
||||
#[cfg(test)]
|
||||
#[test] fn test_stub_input () -> Usually<()> {
|
||||
|
|
@ -36,3 +32,9 @@ pub(crate) type Usually<T> = Result<T, Box<dyn Error>>;
|
|||
assert!(!TestInput(false).is_done());
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[cfg(all(test, feature = "dsl"))]
|
||||
#[test] fn test_dsl_keymap () -> Usually<()> {
|
||||
let keymap = SourceIter::new("");
|
||||
Ok(())
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue