Bro we are pathing

This commit is contained in:
Aode (Lion) 2021-10-05 18:34:01 -05:00
commit eda2e86aa2
3 changed files with 695 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
/target
Cargo.lock

12
Cargo.toml Normal file
View 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
View 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")
}
}
}