From 929e069a84db5cb90dedc0abd8fef82fda1753df Mon Sep 17 00:00:00 2001 From: brunoczim Date: Fri, 8 Sep 2023 19:58:26 -0300 Subject: [PATCH] introduce try methods --- examples/simple_pedia.rs | 16 ++-- src/component/page.rs | 2 +- src/component/section.rs | 4 +- src/lib.rs | 16 ++-- src/location.rs | 171 ++++++++++++++++++++++++++++++++++----- src/site.rs | 41 ++++------ 6 files changed, 187 insertions(+), 63 deletions(-) diff --git a/examples/simple_pedia.rs b/examples/simple_pedia.rs index a079ff7..844c098 100644 --- a/examples/simple_pedia.rs +++ b/examples/simple_pedia.rs @@ -50,12 +50,12 @@ fn index() -> impl FullRender + Send + Sync + 'static { children: harray![ Section { title: "Random Section", - id: Some(Id::new("random").unwrap()), + id: Some(Id::new("random")), body: Paragraph("This is a random paragraph."), children: harray![ Section { title: "Randomly First", - id: Some(Id::new("random-first").unwrap()), + id: Some(Id::new("random-first")), body: Paragraph( "This the first (really?) random paragraph." ), @@ -63,7 +63,7 @@ fn index() -> impl FullRender + Send + Sync + 'static { }, Section { title: "Randomly Second", - id: Some(Id::new("random-second").unwrap()), + id: Some(Id::new("random-second")), body: Paragraph( "This the second (really??) random paragraph." ), @@ -73,7 +73,7 @@ fn index() -> impl FullRender + Send + Sync + 'static { }, Section { title: "Weird Title", - id: Some(Id::new("weird").unwrap()), + id: Some(Id::new("weird")), body: Paragraph("Weird paragraph as an example"), children: harray![], } @@ -124,19 +124,19 @@ fn simple_pedia_site() -> Site> { site.root .insert_index(InternalPath::root(), Entry::Page(index().into_dyn())); site.root.insert_index( - InternalPath::parse("foo").unwrap(), + InternalPath::parse("foo"), Entry::Page(foo_page().into_dyn()), ); site.root.insert_index( - InternalPath::parse("bar").unwrap(), + InternalPath::parse("bar"), Entry::Page(bar_page().into_dyn()), ); site.root.insert_index( - InternalPath::parse("bar/baz").unwrap(), + InternalPath::parse("bar/baz"), Entry::Page(baz_page().into_dyn()), ); site.root.insert_path( - &InternalPath::parse("styles/main.css").unwrap(), + &InternalPath::parse("styles/main.css"), Entry::Resource, ); site diff --git a/src/component/page.rs b/src/component/page.rs index a283bf8..d04630a 100644 --- a/src/component/page.rs +++ b/src/component/page.rs @@ -413,7 +413,7 @@ mod test { }, Section { title: "Good", - id: Some(Id::new("good").unwrap()), + id: Some(Id::new("good")), body: Paragraph("Afternoon!"), children: harray![Section { title: "By", diff --git a/src/component/section.rs b/src/component/section.rs index da995d7..24c4768 100644 --- a/src/component/section.rs +++ b/src/component/section.rs @@ -344,7 +344,7 @@ mod test { let rendered = RenderAsDisplay::new( Section { title: "Hello", - id: Some(Id::new("hello").unwrap()), + id: Some(Id::new("hello")), body: Paragraph("World!"), children: harray![], }, @@ -389,7 +389,7 @@ mod test { }, Section { title: "Good", - id: Some(Id::new("good").unwrap()), + id: Some(Id::new("good")), body: Paragraph("Afternoon!"), children: harray![Section { title: "By", diff --git a/src/lib.rs b/src/lib.rs index 33f32ba..f420933 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -98,12 +98,12 @@ //! children: harray![ //! Section { //! title: "Random Section", -//! id: Some(Id::new("random").unwrap()), +//! id: Some(Id::new("random")), //! body: Paragraph("This is a random paragraph."), //! children: harray![ //! Section { //! title: "Randomly First", -//! id: Some(Id::new("random-first").unwrap()), +//! id: Some(Id::new("random-first")), //! body: Paragraph( //! "This the first (really?) random paragraph." //! ), @@ -111,7 +111,7 @@ //! }, //! Section { //! title: "Randomly Second", -//! id: Some(Id::new("random-second").unwrap()), +//! id: Some(Id::new("random-second")), //! body: Paragraph( //! "This the second (really??) random paragraph." //! ), @@ -121,7 +121,7 @@ //! }, //! Section { //! title: "Weird Title", -//! id: Some(Id::new("weird").unwrap()), +//! id: Some(Id::new("weird")), //! body: Paragraph("Weird paragraph as an example"), //! children: harray![], //! } @@ -172,19 +172,19 @@ //! site.root //! .insert_index(InternalPath::root(), Entry::Page(index().into_dyn())); //! site.root.insert_index( -//! InternalPath::parse("foo").unwrap(), +//! InternalPath::parse("foo"), //! Entry::Page(foo_page().into_dyn()), //! ); //! site.root.insert_index( -//! InternalPath::parse("bar").unwrap(), +//! InternalPath::parse("bar"), //! Entry::Page(bar_page().into_dyn()), //! ); //! site.root.insert_index( -//! InternalPath::parse("bar/baz").unwrap(), +//! InternalPath::parse("bar/baz"), //! Entry::Page(baz_page().into_dyn()), //! ); //! site.root.insert_path( -//! &InternalPath::parse("styles/main.css").unwrap(), +//! &InternalPath::parse("styles/main.css"), //! Entry::Resource, //! ); //! site diff --git a/src/location.rs b/src/location.rs index c6d4733..147f05a 100644 --- a/src/location.rs +++ b/src/location.rs @@ -6,6 +6,7 @@ use crate::{ }; use percent_encoding::{percent_encode, CONTROLS}; use std::{ + convert::Infallible, error::Error, fmt::{self, Write}, path::PathBuf, @@ -59,18 +60,25 @@ impl Location { Location::Url(Url::parse(contents.as_ref()).expect("bad URL")) } + /// Parses an internal location but returns a generic location. + pub fn try_internal(contents: S) -> Result + where + S: AsRef, + { + InternalLoc::try_parse(contents).map(Location::Internal) + } + /// Parses an internal location but returns a generic location. /// /// # Panics /// - /// Panics if the location string is invalid. + /// Panics if the location string is invalid (for a non-panicking version, + /// see [`Location::try_internal`]. pub fn internal(contents: S) -> Self where S: AsRef, { - Location::Internal( - InternalLoc::parse(contents).expect("bad internal location"), - ) + Self::try_internal(contents).expect("failed to parse internal location") } } @@ -127,7 +135,7 @@ pub struct InternalPath { impl InternalPath { /// Parser the internal path. Fragments separated by "/". - pub fn parse(string: S) -> Result + pub fn try_parse(string: S) -> Result where S: AsRef, { @@ -136,13 +144,26 @@ impl InternalPath { if string.len() > 0 { for fragment in string.split('/') { - this.fragments.push(Fragment::new(fragment)?); + this.fragments.push(Fragment::try_new(fragment)?); } } Ok(this) } + /// Parser the internal path. Fragments separated by "/". + /// + /// # Panic + /// + /// Panics if the given string is not a valid internal path (for a + /// non-panicking version, see [`InternalPath::try_parse`]. + pub fn parse(string: S) -> Self + where + S: AsRef, + { + Self::try_parse(string).expect("failed to parse InternalPath") + } + /// Path to the root of the encyclopedia. pub fn root() -> Self { Self { fragments: Vec::new() } @@ -165,9 +186,27 @@ impl InternalPath { /// Appends a fragment (a piece) to the end of this path. Returns the /// modified path. - pub fn append(mut self, fragment: Fragment) -> Self { - self.fragments.push(fragment); - self + /// + /// # Panic + /// + /// Panics if the given appendable is not a valid fragment (for a + /// non-panicking version see [`InternalPath::try_append`]. + pub fn append(self, appendable: A) -> Self + where + A: PathAppendable, + { + self.try_append(appendable) + .expect("failed to parse appendable fragment") + } + + /// Appends a fragment (a piece) to the end of this path. Returns the + /// modified path. + pub fn try_append(mut self, appendable: A) -> Result + where + A: PathAppendable, + { + appendable.append(&mut self)?; + Ok(self) } /// Compares two locations taking "index" into account and ignoring its @@ -316,10 +355,10 @@ impl From for InternalLoc { } impl InternalLoc { - /// Parses247 } an internal location. Path fragments separated by "/", + /// Parses an internal location. Path fragments separated by "/", /// ID appended to the end with "#" between the path and the ID, if any /// ID at all. - pub fn parse(string: S) -> Result + pub fn try_parse(string: S) -> Result where S: AsRef, { @@ -331,15 +370,30 @@ impl InternalLoc { .unwrap_or(string.len()); Ok(Self { - path: InternalPath::parse(&string[.. hash])?, + path: InternalPath::try_parse(&string[.. hash])?, id: if hash == string.len() { None } else { - Some(Id::new(&string[hash + 1 ..])?) + Some(Id::try_new(&string[hash + 1 ..])?) }, }) } + /// Parses an internal location. Path fragments separated by "/", + /// ID appended to the end with "#" between the path and the ID, if any + /// ID at all. + /// + /// # Panic + /// + /// Panics if the given string is not a valid internal location (for a + /// non-panicking version, see [`InternalLoc::try_parse`]. + pub fn parse(string: S) -> Self + where + S: AsRef, + { + Self::try_parse(string).expect("failed to parse internal location") + } + fn render_as_url( &self, renderer: &mut Renderer, @@ -425,7 +479,7 @@ pub struct Id { impl Id { /// Creates an ID from the desired string contents. The string can only /// contain alphanumeric characters or '_' or '-'. - pub fn new(contents: S) -> Result + pub fn try_new(contents: S) -> Result where S: AsRef + Into>, { @@ -442,6 +496,20 @@ impl Id { Ok(Self { contents: contents.into() }) } + /// Creates an ID from the desired string contents. The string can only + /// contain alphanumeric characters or '_' or '-'. + /// + /// # Panic + /// + /// Panics if the ID is invalid (for a non-panicking version, see + /// [`Id::try_new`]). + pub fn new(contents: S) -> Self + where + S: AsRef + Into>, + { + Self::try_new(contents).expect("failed parsing Id") + } + /// The string contents of this ID. pub fn as_str(&self) -> &str { &self.contents @@ -494,7 +562,7 @@ impl Fragment { /// Creates a fragment from the desired string contents. The string cannot /// contain '/' or '#', it cannot be empty or composed of only "." or /// ".." as well. - pub fn new(contents: S) -> Result + pub fn try_new(contents: S) -> Result where S: AsRef + Into>, { @@ -511,6 +579,21 @@ impl Fragment { Ok(Self { contents: contents.into() }) } + /// Creates a fragment from the desired string contents. The string cannot + /// contain '/' or '#', it cannot be empty or composed of only "." or + /// ".." as well. + /// + /// # Panic + /// + /// Panics if the string is not a valid fragment (For non-panicking version, + /// see [`Fragment::try_new`]). + pub fn new(contents: S) -> Self + where + S: AsRef + Into>, + { + Self::try_new(contents).expect("failed to parse Fragment") + } + /// The string contents of this fragment. pub fn as_str(&self) -> &str { &self.contents @@ -523,15 +606,67 @@ impl fmt::Display for Fragment { } } +/// Trait generalizing the operation [`InternalPath::append`] and +/// [`InternalPath::try_append`]. Such methods are intended to be API-friendly +/// methods for appending fragments to internal paths. +pub trait PathAppendable { + /// Possible error happening during the appending. + type Error: fmt::Debug; + + /// Append `self` to `to_path` at the end of the path. + fn append(self, to_path: &mut InternalPath) -> Result<(), Self::Error>; +} + +impl PathAppendable for Fragment { + type Error = Infallible; + + fn append(self, to_path: &mut InternalPath) -> Result<(), Self::Error> { + to_path.fragments.push(self); + Ok(()) + } +} + +impl<'input> PathAppendable for &'input str { + type Error = InvalidFragment; + + fn append(self, to_path: &mut InternalPath) -> Result<(), Self::Error> { + match Fragment::try_new(self)?.append(to_path) { + Ok(()) => Ok(()), + Err(impossible) => match impossible {}, + } + } +} + +impl PathAppendable for String { + type Error = InvalidFragment; + + fn append(self, to_path: &mut InternalPath) -> Result<(), Self::Error> { + match Fragment::try_new(self)?.append(to_path) { + Ok(()) => Ok(()), + Err(impossible) => match impossible {}, + } + } +} + +impl PathAppendable for Box { + type Error = InvalidFragment; + + fn append(self, to_path: &mut InternalPath) -> Result<(), Self::Error> { + match Fragment::try_new(self)?.append(to_path) { + Ok(()) => Ok(()), + Err(impossible) => match impossible {}, + } + } +} + #[cfg(test)] mod test { use super::InternalPath; #[test] fn eq_index() { - let left = InternalPath::parse("langs/div-prt/phonology").unwrap(); - let right = - InternalPath::parse("langs/div-prt/phonology/index.html").unwrap(); + let left = InternalPath::parse("langs/div-prt/phonology"); + let right = InternalPath::parse("langs/div-prt/phonology/index.html"); assert!(left.eq_index(&right)); assert!(right.eq_index(&left)); } diff --git a/src/site.rs b/src/site.rs index ecec7ec..b7f7e43 100644 --- a/src/site.rs +++ b/src/site.rs @@ -286,7 +286,7 @@ where /// # fn main() { /// let mut directory = Directory::default(); /// directory.try_insert_index( - /// InternalPath::parse("foo/bar").unwrap(), + /// InternalPath::parse("foo/bar"), /// Entry::Page(Page { /// title: String::from("Foo Bar"), /// banner: harray![], @@ -299,7 +299,7 @@ where /// /// assert!( /// directory - /// .get(InternalPath::parse("foo/bar/index.html").unwrap()) + /// .get(InternalPath::parse("foo/bar/index.html")) /// .unwrap() /// .is_page() /// ); @@ -310,10 +310,7 @@ where path: InternalPath, new_entry: Entry

, ) -> Result<(), InsertPathError> { - self.try_insert_path( - &path.append(Fragment::new("index.html").unwrap()), - new_entry, - ) + self.try_insert_path(&path.append("index.html"), new_entry) } /// Inserts the given new entry at the given path, assuming it points to a @@ -338,7 +335,7 @@ where /// # fn main() { /// let mut directory = Directory::default(); /// directory.insert_index( - /// InternalPath::parse("foo/bar").unwrap(), + /// InternalPath::parse("foo/bar"), /// Entry::Page(Page { /// title: String::from("Foo Bar"), /// banner: harray![], @@ -349,7 +346,7 @@ where /// ); /// assert!( /// directory - /// .get(InternalPath::parse("foo/bar/index.html").unwrap()) + /// .get(InternalPath::parse("foo/bar/index.html")) /// .unwrap() /// .is_page() /// ); @@ -523,11 +520,11 @@ mod test { Directory { entries: [ ( - Fragment::new("avocado").unwrap(), + Fragment::new("avocado"), Entry::Directory(Directory { entries: [ ( - Fragment::new("apple").unwrap(), + Fragment::new("apple"), Entry::Page( Page { banner: InlineBlock("My Banner"), @@ -539,16 +536,13 @@ mod test { .into_dyn(), ), ), - ( - Fragment::new("audio.ogg").unwrap(), - Entry::Resource, - ), + (Fragment::new("audio.ogg"), Entry::Resource), ] .into_iter() .collect(), }), ), - (Fragment::new("pineapple").unwrap(), Entry::Resource), + (Fragment::new("pineapple"), Entry::Resource), ] .into_iter() .collect(), @@ -558,23 +552,20 @@ mod test { #[test] fn access_fragment_valid() { let dir = make_directory(); - assert!(dir - .get(Fragment::new("avocado").unwrap()) - .unwrap() - .is_directory()); + assert!(dir.get(Fragment::new("avocado")).unwrap().is_directory()); } #[test] fn access_fragment_invalid() { let dir = make_directory(); - assert!(dir.get(Fragment::new("grapes").unwrap()).is_none()); + assert!(dir.get(Fragment::new("grapes")).is_none()); } #[test] fn access_internal_path_valid() { let dir = make_directory(); assert!(dir - .get(InternalPath::parse("avocado/apple").unwrap()) + .get(InternalPath::parse("avocado/apple")) .unwrap() .is_page()); } @@ -582,16 +573,14 @@ mod test { #[test] fn access_internal_path_invalid() { let dir = make_directory(); - assert!(dir - .get(InternalPath::parse("avocado/grapes").unwrap()) - .is_none()); + assert!(dir.get(InternalPath::parse("avocado/grapes")).is_none()); } #[test] fn insert_index_valid() { let mut dir = make_directory(); dir.insert_index( - InternalPath::parse("new-fruit/morgot").unwrap(), + InternalPath::parse("new-fruit/morgot"), Entry::Page( Page { banner: InlineBlock("My Banner"), @@ -604,7 +593,7 @@ mod test { ), ); assert!(dir - .get(InternalPath::parse("new-fruit/morgot/index.html").unwrap()) + .get(InternalPath::parse("new-fruit/morgot/index.html")) .unwrap() .is_page()); }