Bro we are pathing
This commit is contained in:
commit
eda2e86aa2
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
|
@ -0,0 +1,2 @@
|
|||
/target
|
||||
Cargo.lock
|
12
Cargo.toml
Normal file
12
Cargo.toml
Normal file
|
@ -0,0 +1,12 @@
|
|||
[package]
|
||||
name = "path-gen"
|
||||
version = "0.1.0"
|
||||
edition = "2018"
|
||||
|
||||
[features]
|
||||
default = []
|
||||
test = []
|
||||
|
||||
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html
|
||||
|
||||
[dependencies]
|
681
src/lib.rs
Normal file
681
src/lib.rs
Normal file
|
@ -0,0 +1,681 @@
|
|||
use std::{borrow::Cow, fmt::Display, marker::PhantomData, str::FromStr};
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct PathGen<'a> {
|
||||
root: Cow<'a, str>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Prefix<Inner, Segment> {
|
||||
inner: Inner,
|
||||
segment: PhantomData<Segment>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Field<Inner, Segment> {
|
||||
inner: Inner,
|
||||
segment: Segment,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
pub struct Node<Inner, Segment> {
|
||||
inner: Inner,
|
||||
segment: Segment,
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub struct DeconstructError;
|
||||
|
||||
impl std::fmt::Display for DeconstructError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "Invalid path format")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for DeconstructError {}
|
||||
|
||||
pub struct PathParser<Inner> {
|
||||
phantom: PhantomData<Inner>,
|
||||
}
|
||||
|
||||
pub trait PathNode: Display + FromStr {
|
||||
const NAME: &'static str;
|
||||
}
|
||||
|
||||
pub trait PathField: Display + FromStr {}
|
||||
|
||||
impl PathGen<'static> {
|
||||
pub fn new_owned(root: String) -> Self {
|
||||
PathGen {
|
||||
root: Cow::Owned(root),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a> PathGen<'a> {
|
||||
pub const fn new(root: &'a str) -> Self {
|
||||
PathGen {
|
||||
root: Cow::Borrowed(root),
|
||||
}
|
||||
}
|
||||
|
||||
pub const fn parser() -> PathParser<Self> {
|
||||
PathParser {
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn push<N>(self, node: N) -> Node<Self, N>
|
||||
where
|
||||
N: PathNode,
|
||||
{
|
||||
Node {
|
||||
inner: self,
|
||||
segment: node,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prefix<N>(self) -> Prefix<Self, N>
|
||||
where
|
||||
N: PathNode,
|
||||
{
|
||||
Prefix {
|
||||
inner: self,
|
||||
segment: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Inner, Segment> Node<Inner, Segment> {
|
||||
pub fn push<N>(self, node: N) -> Node<Self, N>
|
||||
where
|
||||
N: PathNode,
|
||||
{
|
||||
Node {
|
||||
inner: self,
|
||||
segment: node,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn pop(self) -> Inner {
|
||||
self.inner
|
||||
}
|
||||
|
||||
pub fn field<F>(self, field: F) -> Field<Self, F>
|
||||
where
|
||||
F: PathField,
|
||||
{
|
||||
Field {
|
||||
inner: self,
|
||||
segment: field,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prefix<N>(self) -> Prefix<Self, N>
|
||||
where
|
||||
N: PathNode,
|
||||
{
|
||||
Prefix {
|
||||
inner: self,
|
||||
segment: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Inner, Segment> Field<Inner, Segment>
|
||||
where
|
||||
Self: Construct,
|
||||
{
|
||||
pub fn construct(&self) -> String {
|
||||
Construct::construct(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Inner, Segment> Prefix<Inner, Segment>
|
||||
where
|
||||
Self: Construct,
|
||||
{
|
||||
pub fn construct(&self) -> String {
|
||||
Construct::construct(self)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Inner, Segment> Display for Field<Inner, Segment>
|
||||
where
|
||||
Self: Construct,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.construct())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Inner, Segment> Display for Prefix<Inner, Segment>
|
||||
where
|
||||
Self: Construct,
|
||||
{
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.construct())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Inner> PathParser<Inner> {
|
||||
pub fn push<N>(self) -> PathParser<Node<Inner, N>>
|
||||
where
|
||||
N: PathNode,
|
||||
{
|
||||
PathParser {
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn prefix<N>(self) -> PathParser<Prefix<Inner, N>>
|
||||
where
|
||||
N: PathNode,
|
||||
{
|
||||
PathParser {
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn field<F>(self) -> PathParser<Field<Inner, F>>
|
||||
where
|
||||
F: PathField,
|
||||
{
|
||||
PathParser {
|
||||
phantom: PhantomData,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<Inner, F> PathParser<Field<Inner, F>> {
|
||||
pub fn parse<'a, E>(&self, s: &'a str) -> Result<Field<Inner, F>, E>
|
||||
where
|
||||
Field<Inner, F>: Deconstruct<'a, E> + 'a,
|
||||
{
|
||||
Field::<Inner, F>::deconstruct(s)
|
||||
}
|
||||
}
|
||||
|
||||
impl<Inner, N> PathParser<Prefix<Inner, N>> {
|
||||
pub fn parse<'a, E>(&self, s: &'a str) -> Result<Prefix<Inner, N>, E>
|
||||
where
|
||||
Prefix<Inner, N>: Deconstruct<'a, E> + 'a,
|
||||
{
|
||||
Prefix::<Inner, N>::deconstruct(s)
|
||||
}
|
||||
}
|
||||
|
||||
mod hidden {
|
||||
pub trait Construct {
|
||||
fn construct(&self) -> String;
|
||||
}
|
||||
|
||||
pub trait Deconstruct<'a, E> {
|
||||
fn deconstruct(s: &'a str) -> Result<Self, E>
|
||||
where
|
||||
Self: Sized + 'a;
|
||||
}
|
||||
}
|
||||
use hidden::{Construct, Deconstruct};
|
||||
|
||||
impl<'a> Construct for PathGen<'a> {
|
||||
fn construct(&self) -> String {
|
||||
self.root.to_string()
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E> Deconstruct<'a, E> for PathGen<'a> {
|
||||
fn deconstruct(s: &'a str) -> Result<Self, E> {
|
||||
Ok(PathGen {
|
||||
root: Cow::Borrowed(s),
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E, Inner, N> Deconstruct<'a, E> for Node<Inner, N>
|
||||
where
|
||||
Inner: Deconstruct<'a, E>,
|
||||
N: PathNode,
|
||||
E: From<<N as FromStr>::Err>,
|
||||
E: From<DeconstructError>,
|
||||
{
|
||||
fn deconstruct(s: &'a str) -> Result<Self, E>
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
{
|
||||
if let Some((rest, value)) = rsplit_once_escaped(s) {
|
||||
if let Some((rest, name)) = rsplit_once_escaped(rest) {
|
||||
if unescape(name) == N::NAME {
|
||||
let inner = Inner::deconstruct(rest)?;
|
||||
let segment = N::from_str(&unescape(value))?;
|
||||
return Ok(Node { inner, segment });
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Err(DeconstructError.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E, Inner, F> Deconstruct<'a, E> for Field<Inner, F>
|
||||
where
|
||||
Inner: Deconstruct<'a, E>,
|
||||
F: PathField,
|
||||
E: From<<F as FromStr>::Err>,
|
||||
E: From<DeconstructError>,
|
||||
{
|
||||
fn deconstruct(s: &'a str) -> Result<Self, E>
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
{
|
||||
if let Some((rest, value)) = rsplit_once_escaped(s) {
|
||||
let inner = Inner::deconstruct(rest)?;
|
||||
let segment = F::from_str(&unescape(value))?;
|
||||
return Ok(Field { inner, segment });
|
||||
} else {
|
||||
Err(DeconstructError.into())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl<'a, E, Inner, N> Deconstruct<'a, E> for Prefix<Inner, N>
|
||||
where
|
||||
Inner: Deconstruct<'a, E>,
|
||||
N: PathNode,
|
||||
E: From<<N as FromStr>::Err>,
|
||||
E: From<DeconstructError>,
|
||||
{
|
||||
fn deconstruct(s: &'a str) -> Result<Self, E>
|
||||
where
|
||||
Self: Sized + 'a,
|
||||
{
|
||||
if let Some((rest, name)) = rsplit_once_escaped(s) {
|
||||
if unescape(name) == N::NAME {
|
||||
let inner = Inner::deconstruct(rest)?;
|
||||
return Ok(Prefix {
|
||||
inner,
|
||||
segment: PhantomData,
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Err(DeconstructError.into())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Inner, N> Construct for Node<Inner, N>
|
||||
where
|
||||
Inner: Construct,
|
||||
N: PathNode,
|
||||
{
|
||||
fn construct(&self) -> String {
|
||||
self.inner.construct() + "." + &escape(N::NAME) + "." + &escape(&self.segment.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Inner, F> Construct for Field<Inner, F>
|
||||
where
|
||||
Inner: Construct,
|
||||
F: PathField,
|
||||
{
|
||||
fn construct(&self) -> String {
|
||||
self.inner.construct() + "." + &escape(&self.segment.to_string())
|
||||
}
|
||||
}
|
||||
|
||||
impl<Inner, N> Construct for Prefix<Inner, N>
|
||||
where
|
||||
Inner: Construct,
|
||||
N: PathNode,
|
||||
{
|
||||
fn construct(&self) -> String {
|
||||
self.inner.construct() + "." + &escape(N::NAME)
|
||||
}
|
||||
}
|
||||
|
||||
const ESCAPE_CHARS: &'static [char] = &['.', '\\'];
|
||||
|
||||
fn rsplit_once_escaped<'a>(s: &'a str) -> Option<(&'a str, &'a str)> {
|
||||
let mut position: usize = s.len();
|
||||
|
||||
while let Some(index) = s[..position].rfind('.') {
|
||||
let mut escaped = false;
|
||||
for prev in (0..index).rev() {
|
||||
if s.get(prev..prev + 1) != Some("\\") {
|
||||
break;
|
||||
}
|
||||
|
||||
escaped = !escaped;
|
||||
}
|
||||
|
||||
if !escaped {
|
||||
let (left, right) = s.split_at(index);
|
||||
return Some((left, &right[1..]));
|
||||
}
|
||||
|
||||
position = index;
|
||||
}
|
||||
|
||||
None
|
||||
}
|
||||
|
||||
fn escape(s: &str) -> Cow<'_, str> {
|
||||
if s.find(ESCAPE_CHARS).is_some() {
|
||||
let v = s
|
||||
.split('.')
|
||||
.map(|part| part.split('\\').collect::<Vec<&str>>().join("\\\\"))
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
return Cow::Owned(v.join("\\."));
|
||||
}
|
||||
|
||||
Cow::Borrowed(s)
|
||||
}
|
||||
|
||||
fn unescape(s: &str) -> Cow<'_, str> {
|
||||
if s.find(ESCAPE_CHARS).is_some() {
|
||||
let v = s
|
||||
.split("\\\\")
|
||||
.map(|part| part.split("\\.").collect::<Vec<&str>>().join("."))
|
||||
.collect::<Vec<String>>();
|
||||
|
||||
return Cow::Owned(v.join("\\"));
|
||||
}
|
||||
|
||||
Cow::Borrowed(s)
|
||||
}
|
||||
|
||||
#[cfg(feature = "test")]
|
||||
pub mod test {
|
||||
use super::{PathField, PathNode};
|
||||
use std::{
|
||||
fmt::{Debug, Display},
|
||||
str::FromStr,
|
||||
};
|
||||
|
||||
pub fn test_pathnode<N>(path_node: N)
|
||||
where
|
||||
N: PathNode + Debug + Eq,
|
||||
<N as FromStr>::Err: Debug,
|
||||
{
|
||||
test_roundtrip(path_node);
|
||||
}
|
||||
|
||||
pub fn test_field<F>(path_field: F)
|
||||
where
|
||||
F: PathField + Debug + Eq,
|
||||
<F as FromStr>::Err: Debug,
|
||||
{
|
||||
test_roundtrip(path_field)
|
||||
}
|
||||
|
||||
fn test_roundtrip<T>(item: T)
|
||||
where
|
||||
T: Display + FromStr + Debug + Eq,
|
||||
<T as FromStr>::Err: Debug,
|
||||
{
|
||||
let string = item.to_string();
|
||||
let round_trip = string.parse::<T>().unwrap();
|
||||
let plus_one = round_trip.to_string();
|
||||
|
||||
assert_eq!(round_trip, item);
|
||||
assert_eq!(string, plus_one);
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::{fmt::Display, str::FromStr};
|
||||
|
||||
use super::{escape, rsplit_once_escaped, unescape, PathField, PathGen, PathNode};
|
||||
|
||||
const ROOT: PathGen<'static> = PathGen::new("test-root");
|
||||
|
||||
#[test]
|
||||
fn splits_on_dot() {
|
||||
let input = "left of dot . right of dot";
|
||||
let (left, right) = rsplit_once_escaped(input).unwrap();
|
||||
assert_eq!(left, "left of dot ");
|
||||
assert_eq!(right, " right of dot");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn splits_on_ending_dot() {
|
||||
let input = "left of dot .";
|
||||
let (left, right) = rsplit_once_escaped(input).unwrap();
|
||||
assert_eq!(left, "left of dot ");
|
||||
assert_eq!(right, "");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn splits_on_starting_dot() {
|
||||
let input = ". right of dot";
|
||||
let (left, right) = rsplit_once_escaped(input).unwrap();
|
||||
assert_eq!(left, "");
|
||||
assert_eq!(right, " right of dot");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn splits_last_dot() {
|
||||
let input = "left of dots . between dots . right of dots";
|
||||
let (left, right) = rsplit_once_escaped(input).unwrap();
|
||||
assert_eq!(left, "left of dots . between dots ");
|
||||
assert_eq!(right, " right of dots");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn doesnt_split_escaped_dot() {
|
||||
let input = "left of dot \\. right of dot";
|
||||
let opt = rsplit_once_escaped(input);
|
||||
assert!(opt.is_none());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn splits_unescaped_dot_with_preceding_escaped_dots() {
|
||||
let input = "left of dots . between dots \\. right of dots";
|
||||
let (left, right) = rsplit_once_escaped(input).unwrap();
|
||||
assert_eq!(left, "left of dots ");
|
||||
assert_eq!(right, " between dots \\. right of dots");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn splits_unescaped_dot_with_preceding_backslashes() {
|
||||
let input = "left of dots . between dots \\\\. right of dots";
|
||||
let (left, right) = rsplit_once_escaped(input).unwrap();
|
||||
assert_eq!(left, "left of dots . between dots \\\\");
|
||||
assert_eq!(right, " right of dots");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn splits_unescaped_dot_with_preceding_escaped_dots_with_preceding_backslashes() {
|
||||
let input = "left of dots . between dots \\\\\\. right of dots";
|
||||
let (left, right) = rsplit_once_escaped(input).unwrap();
|
||||
assert_eq!(left, "left of dots ");
|
||||
assert_eq!(right, " between dots \\\\\\. right of dots");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn escape_output() {
|
||||
let inputs = [
|
||||
(
|
||||
"..\\.\\.\\..\\\\\\...",
|
||||
"\\.\\.\\\\\\.\\\\\\.\\\\\\.\\.\\\\\\\\\\\\\\.\\.\\.",
|
||||
),
|
||||
(".", "\\."),
|
||||
("\\", "\\\\"),
|
||||
("\\.", "\\\\\\."),
|
||||
(".\\", "\\.\\\\"),
|
||||
(".\\\\\\", "\\.\\\\\\\\\\\\"),
|
||||
("\\\\\\.", "\\\\\\\\\\\\\\."),
|
||||
("...\\", "\\.\\.\\.\\\\"),
|
||||
("\\...", "\\\\\\.\\.\\."),
|
||||
("Some text with a . in it", "Some text with a \\. in it"),
|
||||
("Some text with a \\ in it", "Some text with a \\\\ in it"),
|
||||
(
|
||||
"Some text with a \\ and a . in it",
|
||||
"Some text with a \\\\ and a \\. in it",
|
||||
),
|
||||
(
|
||||
"Some text with a . and a \\ in it",
|
||||
"Some text with a \\. and a \\\\ in it",
|
||||
),
|
||||
];
|
||||
|
||||
for (start, escaped) in inputs {
|
||||
assert_eq!(&escape(start), escaped);
|
||||
assert_eq!(&unescape(escaped), start);
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn construct_field() {
|
||||
let s = ROOT
|
||||
.push(Deck("deck-id".to_owned()))
|
||||
.push(Key(5))
|
||||
.field(CommandField)
|
||||
.construct();
|
||||
|
||||
assert_eq!(&s, "test-root.deck.deck-id.key.5.command")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn construct_prefix() {
|
||||
let s = ROOT
|
||||
.push(Deck("deck-id".to_owned()))
|
||||
.prefix::<Key>()
|
||||
.construct();
|
||||
|
||||
assert_eq!(&s, "test-root.deck.deck-id.key")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn construct_escaped_prefix() {
|
||||
let s = ROOT
|
||||
.push(Deck("deck.id".to_owned()))
|
||||
.prefix::<Key>()
|
||||
.construct();
|
||||
|
||||
assert_eq!(&s, "test-root.deck.deck\\.id.key")
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deconstruct_field() {
|
||||
let parser = PathGen::parser()
|
||||
.push::<Deck>()
|
||||
.push::<Key>()
|
||||
.field::<CommandField>();
|
||||
|
||||
let s = "test-root.deck.deck-id.key.5.command";
|
||||
|
||||
let path = parser.parse::<Box<dyn std::error::Error>>(s).unwrap();
|
||||
|
||||
assert_eq!(
|
||||
path,
|
||||
ROOT.push(Deck("deck-id".to_owned()))
|
||||
.push(Key(5))
|
||||
.field(CommandField)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deconstruct_prefix() {
|
||||
let parser = PathGen::parser().push::<Deck>().prefix::<Key>();
|
||||
|
||||
let s = "test-root.deck.deck-id.key";
|
||||
|
||||
let path = parser.parse::<Box<dyn std::error::Error>>(s).unwrap();
|
||||
|
||||
assert_eq!(path, ROOT.push(Deck("deck-id".to_owned())).prefix::<Key>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn deconstruct_escaped_prefix() {
|
||||
let parser = PathGen::parser().push::<Deck>().prefix::<Key>();
|
||||
|
||||
let s = "test-root.deck.deck\\.id.key";
|
||||
|
||||
let path = parser.parse::<Box<dyn std::error::Error>>(s).unwrap();
|
||||
|
||||
assert_eq!(path, ROOT.push(Deck("deck.id".to_owned())).prefix::<Key>());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn test_parts() {
|
||||
super::test::test_pathnode(Deck("hello".to_string()));
|
||||
super::test::test_pathnode(Key(5));
|
||||
super::test::test_field(CommandField);
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
struct Deck(String);
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
struct Key(u8);
|
||||
#[derive(Clone, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
|
||||
struct CommandField;
|
||||
#[derive(Debug)]
|
||||
struct CommandFieldError;
|
||||
|
||||
impl std::fmt::Display for CommandFieldError {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "string did not match 'command'")
|
||||
}
|
||||
}
|
||||
|
||||
impl std::error::Error for CommandFieldError {}
|
||||
|
||||
impl PathNode for Deck {
|
||||
const NAME: &'static str = "deck";
|
||||
}
|
||||
|
||||
impl PathNode for Key {
|
||||
const NAME: &'static str = "key";
|
||||
}
|
||||
|
||||
impl PathField for CommandField {}
|
||||
|
||||
impl FromStr for Deck {
|
||||
type Err = std::convert::Infallible;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Deck(s.to_owned()))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for Key {
|
||||
type Err = <u8 as FromStr>::Err;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
Ok(Key(s.parse()?))
|
||||
}
|
||||
}
|
||||
|
||||
impl FromStr for CommandField {
|
||||
type Err = CommandFieldError;
|
||||
|
||||
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||
if s == "command" {
|
||||
Ok(CommandField)
|
||||
} else {
|
||||
Err(CommandFieldError)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Deck {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for Key {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "{}", self.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Display for CommandField {
|
||||
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
|
||||
write!(f, "command")
|
||||
}
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue