use std::{any, error::Error as StdError, fmt, io, str::FromStr};
pub mod console;
#[cfg(test)]
mod tests;
type BoxResult<T> = Result<T, Box<dyn StdError + Send + Sync + 'static>>;
pub trait INode {
fn name(&self) -> &str;
fn as_node(&mut self) -> Node<'_>;
fn as_inode(&mut self) -> &mut dyn INode;
}
#[derive(Debug)]
pub enum Node<'a> {
Prop(&'a mut dyn IProperty),
List(&'a mut dyn IList),
Action(&'a mut dyn IAction),
}
impl INode for Node<'_> {
fn name(&self) -> &str {
match self {
Node::Prop(prop) => prop.name(),
Node::List(list) => list.name(),
Node::Action(act) => act.name(),
}
}
fn as_node(&mut self) -> Node<'_> {
match self {
Node::Prop(prop) => Node::Prop(*prop),
Node::List(list) => Node::List(*list),
Node::Action(act) => Node::Action(*act),
}
}
fn as_inode(&mut self) -> &mut dyn INode {
self
}
}
#[derive(Copy, Clone, Debug, Eq, PartialEq)]
pub enum PropState {
Default,
UserSet,
Invalid,
}
pub trait IProperty: INode {
fn get(&self) -> String;
fn set(&mut self, val: &str) -> BoxResult<()>;
fn reset(&mut self);
fn default(&self) -> String;
fn state(&self) -> PropState;
fn flags(&self) -> u32 {
0
}
#[cfg(feature = "type_name")]
fn type_name(&self) -> &str {
any::type_name::<Self>()
}
fn values(&self) -> Option<&[&str]> {
None
}
}
impl fmt::Debug for dyn IProperty + '_ {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
let mut debug = f.debug_struct("IProperty");
debug.field("name", &self.name());
debug.field("value", &self.get());
debug.field("default", &self.default());
debug.field("state", &self.state());
debug.field("flags", &self.flags());
#[cfg(feature = "type_name")]
debug.field("type", &self.type_name());
debug.field("values", &self.values());
debug.finish()
}
}
pub struct Property<'a, T> {
name: &'a str,
variable: &'a mut T,
default: T,
}
#[allow(non_snake_case)]
pub fn Property<'a, T>(name: &'a str, variable: &'a mut T, default: T) -> Property<'a, T> {
Property { name, variable, default }
}
impl<'a, T> Property<'a, T> {
pub fn new(name: &'a str, variable: &'a mut T, default: T) -> Property<'a, T> {
Property { name, variable, default }
}
}
impl<'a, T> INode for Property<'a, T>
where T: FromStr + ToString + Clone + PartialEq,
T::Err: StdError + Send + Sync + 'static
{
fn name(&self) -> &str {
self.name
}
fn as_node(&mut self) -> Node<'_> {
Node::Prop(self)
}
fn as_inode(&mut self) -> &mut dyn INode {
self
}
}
impl<'a, T> IProperty for Property<'a, T>
where T: FromStr + ToString + Clone + PartialEq,
T::Err: StdError + Send + Sync + 'static
{
fn get(&self) -> String {
self.variable.to_string()
}
fn set(&mut self, val: &str) -> BoxResult<()> {
*self.variable = T::from_str(val)?;
Ok(())
}
fn reset(&mut self) {
self.variable.clone_from(&self.default);
}
fn default(&self) -> String {
self.default.to_string()
}
fn state(&self) -> PropState {
match *self.variable == self.default {
true => PropState::Default,
false => PropState::UserSet,
}
}
}
pub struct ClampedProp<'a, T> {
name: &'a str,
variable: &'a mut T,
default: T,
min: T,
max: T,
}
#[allow(non_snake_case)]
pub fn ClampedProp<'a, T>(name: &'a str, variable: &'a mut T, default: T, min: T, max: T) -> ClampedProp<'a, T> {
ClampedProp { name, variable, default, min, max }
}
impl<'a, T> ClampedProp<'a, T> {
pub fn new(name: &'a str, variable: &'a mut T, default: T, min: T, max: T) -> ClampedProp<'a, T> {
ClampedProp { name, variable, default, min, max }
}
}
impl<'a, T> INode for ClampedProp<'a, T>
where T: FromStr + ToString + Clone + PartialEq + PartialOrd,
T::Err: StdError + Send + Sync + 'static
{
fn name(&self) -> &str {
self.name
}
fn as_node(&mut self) -> Node<'_> {
Node::Prop(self)
}
fn as_inode(&mut self) -> &mut dyn INode {
self
}
}
impl<'a, T> IProperty for ClampedProp<'a, T>
where T: FromStr + ToString + Clone + PartialEq + PartialOrd,
T::Err: StdError + Send + Sync + 'static
{
fn get(&self) -> String {
self.variable.to_string()
}
fn set(&mut self, val: &str) -> BoxResult<()> {
*self.variable = T::from_str(val)?;
if *self.variable < self.min {
self.variable.clone_from(&self.min);
}
if *self.variable > self.max {
self.variable.clone_from(&self.max);
}
Ok(())
}
fn reset(&mut self) {
self.variable.clone_from(&self.default);
}
fn default(&self) -> String {
self.default.to_string()
}
fn state(&self) -> PropState {
match *self.variable == self.default {
true => PropState::Default,
false => PropState::UserSet,
}
}
}
pub struct ReadOnlyProp<'a, T> {
name: &'a str,
variable: &'a T,
default: T,
}
#[allow(non_snake_case)]
pub fn ReadOnlyProp<'a, T>(name: &'a str, variable: &'a T, default: T) -> ReadOnlyProp<'a, T> {
ReadOnlyProp { name, variable, default }
}
impl<'a, T> ReadOnlyProp<'a, T> {
pub fn new(name: &'a str, variable: &'a T, default: T) -> ReadOnlyProp<'a, T> {
ReadOnlyProp { name, variable, default }
}
}
impl<'a, T: ToString + PartialEq> INode for ReadOnlyProp<'a, T> {
fn name(&self) -> &str {
self.name
}
fn as_node(&mut self) -> Node<'_> {
Node::Prop(self)
}
fn as_inode(&mut self) -> &mut dyn INode {
self
}
}
impl<'a, T: ToString + PartialEq> IProperty for ReadOnlyProp<'a, T> {
fn get(&self) -> String {
self.variable.to_string()
}
fn set(&mut self, _val: &str) -> BoxResult<()> {
Err("cannot set read-only property".into())
}
fn reset(&mut self) {}
fn default(&self) -> String {
self.default.to_string()
}
fn state(&self) -> PropState {
match *self.variable == self.default {
true => PropState::Default,
false => PropState::UserSet,
}
}
}
pub struct OwnedProp<T> {
pub name: String,
pub variable: T,
pub default: T,
_private: (),
}
#[allow(non_snake_case)]
pub fn OwnedProp<T>(name: String, variable: T, default: T) -> OwnedProp<T> {
OwnedProp { name, variable, default, _private: () }
}
impl<T> OwnedProp<T> {
pub fn new(name: String, variable: T, default: T) -> OwnedProp<T> {
OwnedProp { name, variable, default, _private: () }
}
}
impl<T> INode for OwnedProp<T>
where T: FromStr + ToString + Clone + PartialEq,
T::Err: StdError + Send + Sync + 'static
{
fn name(&self) -> &str { &self.name }
fn as_node(&mut self) -> Node<'_> { Node::Prop(self) }
fn as_inode(&mut self) -> &mut dyn INode { self }
}
impl<T> IProperty for OwnedProp<T>
where T: FromStr + ToString + Clone + PartialEq,
T::Err: StdError + Send + Sync + 'static
{
fn get(&self) -> String {
self.variable.to_string()
}
fn set(&mut self, val: &str) -> BoxResult<()> {
self.variable = T::from_str(val)?;
Ok(())
}
fn reset(&mut self) {
self.variable.clone_from(&self.default);
}
fn default(&self) -> String {
self.default.to_string()
}
fn state(&self) -> PropState {
match self.variable == self.default {
true => PropState::Default,
false => PropState::UserSet,
}
}
}
pub trait IVisit {
fn visit(&mut self, f: &mut dyn FnMut(&mut dyn INode));
}
impl fmt::Debug for dyn IVisit + '_ {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.write_str("IVisit { .. }")
}
}
#[derive(Copy, Clone, Debug)]
pub struct Visit<F: FnMut(&mut dyn FnMut(&mut dyn INode))>(pub F);
impl<F: FnMut(&mut dyn FnMut(&mut dyn INode))> IVisit for Visit<F> {
fn visit(&mut self, f: &mut dyn FnMut(&mut dyn INode)) {
let Self(this) = self;
this(f)
}
}
pub trait IList: INode {
fn as_ivisit(&mut self) -> &mut dyn IVisit;
}
impl fmt::Debug for dyn IList + '_ {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("IList")
.field("name", &self.name())
.finish()
}
}
#[derive(Debug)]
pub struct List<'a> {
name: &'a str,
visitor: &'a mut dyn IVisit,
}
#[allow(non_snake_case)]
pub fn List<'a>(name: &'a str, visitor: &'a mut dyn IVisit) -> List<'a> {
List { name, visitor }
}
impl<'a> List<'a> {
pub fn new(name: &'a str, visitor: &'a mut dyn IVisit) -> List<'a> {
List { name, visitor }
}
}
impl<'a> INode for List<'a> {
fn name(&self) -> &str {
self.name
}
fn as_node(&mut self) -> Node<'_> {
Node::List(self)
}
fn as_inode(&mut self) -> &mut dyn INode {
self
}
}
impl<'a> IList for List<'a> {
fn as_ivisit(&mut self) -> &mut dyn IVisit {
self.visitor
}
}
pub trait IConsole: any::Any + fmt::Write {
fn write_error(&mut self, err: &(dyn StdError + 'static));
}
impl IConsole for String {
fn write_error(&mut self, err: &(dyn StdError + 'static)) {
let _ = writeln!(self as &mut dyn fmt::Write, "error: {}", err);
}
}
pub struct NullConsole;
impl fmt::Write for NullConsole {
fn write_str(&mut self, _s: &str) -> fmt::Result { Ok(()) }
fn write_char(&mut self, _c: char) -> fmt::Result { Ok(()) }
fn write_fmt(&mut self, _args: fmt::Arguments) -> fmt::Result { Ok(()) }
}
impl IConsole for NullConsole {
fn write_error(&mut self, _err: &(dyn StdError + 'static)) {}
}
pub struct IoConsole<W>(pub W);
impl<W: io::Write> fmt::Write for IoConsole<W> {
fn write_str(&mut self, s: &str) -> fmt::Result {
let Self(this) = self;
io::Write::write_all(this, s.as_bytes()).map_err(|_| fmt::Error)
}
fn write_fmt(&mut self, args: fmt::Arguments) -> fmt::Result {
let Self(this) = self;
io::Write::write_fmt(this, args).map_err(|_| fmt::Error)
}
}
impl<W: io::Write + 'static> IConsole for IoConsole<W> {
fn write_error(&mut self, err: &(dyn StdError + 'static)) {
let Self(this) = self;
let _ = writeln!(this, "error: {}", err);
}
}
impl IoConsole<io::Stdout> {
pub fn stdout() -> IoConsole<io::Stdout> {
IoConsole(io::stdout())
}
}
impl IoConsole<io::Stderr> {
pub fn stderr() -> IoConsole<io::Stderr> {
IoConsole(io::stderr())
}
}
pub trait IAction: INode {
fn invoke(&mut self, args: &str, console: &mut dyn IConsole);
}
impl fmt::Debug for dyn IAction + '_ {
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
f.debug_struct("IAction")
.field("name", &self.name())
.finish()
}
}
#[derive(Debug)]
pub struct Action<'a, F: FnMut(&str, &mut dyn IConsole)> {
name: &'a str,
invoke: F,
}
#[allow(non_snake_case)]
pub fn Action<'a, F: FnMut(&str, &mut dyn IConsole)>(name: &'a str, invoke: F) -> Action<'a, F> {
Action { name, invoke }
}
impl<'a, F: FnMut(&str, &mut dyn IConsole)> Action<'a, F> {
pub fn new(name: &'a str, invoke: F) -> Action<'a, F> {
Action { name, invoke }
}
}
impl<'a, F: FnMut(&str, &mut dyn IConsole)> INode for Action<'a, F> {
fn name(&self) -> &str {
self.name
}
fn as_node(&mut self) -> Node<'_> {
Node::Action(self)
}
fn as_inode(&mut self) -> &mut dyn INode {
self
}
}
impl<'a, F: FnMut(&str, &mut dyn IConsole)> IAction for Action<'a, F> {
fn invoke(&mut self, args: &str, console: &mut dyn IConsole) {
(self.invoke)(args, console)
}
}