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