diff --git a/yazi-binding/src/url.rs b/yazi-binding/src/url.rs index ebf29aaf..ef507564 100644 --- a/yazi-binding/src/url.rs +++ b/yazi-binding/src/url.rs @@ -1,6 +1,7 @@ use std::{ops::Deref, path::Path}; use mlua::{AnyUserData, ExternalError, FromLua, Lua, MetaMethod, UserData, UserDataFields, UserDataMethods, UserDataRef, Value}; +use yazi_shared::IntoOsStr; use crate::{Urn, cached_field}; @@ -32,6 +33,14 @@ impl From for yazi_shared::url::Url { fn from(value: Url) -> Self { value.inner } } +impl TryFrom<&[u8]> for Url { + type Error = mlua::Error; + + fn try_from(value: &[u8]) -> mlua::Result { + Ok(Self::new(yazi_shared::url::Url::try_from(value)?)) + } +} + impl Url { pub fn new(url: impl Into) -> Self { Self { @@ -52,7 +61,7 @@ impl Url { "Url", lua.create_function(|_, value: Value| { Ok(match value { - Value::String(s) => Self::new(s.to_str()?.as_ref()), + Value::String(s) => Self::try_from(s.as_bytes().as_ref())?, Value::UserData(ud) => Self::new(ud.borrow::()?.inner.clone()), _ => Err("Expected a string or a Url".into_lua_err())?, }) @@ -89,7 +98,7 @@ impl UserData for Url { cached_field!(fields, base, |_, me| { Ok(if me.base().as_os_str().is_empty() { None } else { Some(Self::new(me.base())) }) }); - cached_field!(fields, frag, |lua, me| lua.create_string(me.frag())); + cached_field!(fields, frag, |lua, me| lua.create_string(me.frag().as_encoded_bytes())); fields.add_field_method_get("is_regular", |_, me| Ok(me.is_regular())); fields.add_field_method_get("is_search", |_, me| Ok(me.is_search())); @@ -130,7 +139,7 @@ impl UserData for Url { }); methods.add_function_mut("into_search", |_, (ud, frag): (AnyUserData, mlua::String)| { - Ok(Self::new(ud.take::()?.inner.into_search(&frag.to_str()?))) + Ok(Self::new(ud.take::()?.inner.into_search(frag.as_bytes().as_ref().into_os_str()?))) }); methods.add_meta_method(MetaMethod::Eq, |_, me, other: UrlRef| Ok(me.inner == other.inner)); diff --git a/yazi-core/src/mgr/commands/update_mimes.rs b/yazi-core/src/mgr/commands/update_mimes.rs index 256d9249..638aacac 100644 --- a/yazi-core/src/mgr/commands/update_mimes.rs +++ b/yazi-core/src/mgr/commands/update_mimes.rs @@ -28,7 +28,7 @@ impl Mgr { let updates = opt .updates .into_iter() - .map(|(url, mime)| (Url::from(url.as_ref()), mime)) + .flat_map(|(url, mime)| Url::try_from(url.as_ref()).map(|u| (u, mime))) .filter(|(url, mime)| self.mimetype.by_url(url) != Some(mime)) .fold(HashMap::new(), |mut map, (u, m)| { for u in linked.from_file(&u) { diff --git a/yazi-core/src/mgr/mimetype.rs b/yazi-core/src/mgr/mimetype.rs index 0b0d248b..190345fb 100644 --- a/yazi-core/src/mgr/mimetype.rs +++ b/yazi-core/src/mgr/mimetype.rs @@ -1,7 +1,7 @@ use std::{borrow::Cow, collections::HashMap, path::PathBuf}; use yazi_fs::File; -use yazi_shared::{MIME_DIR, url::{Url, UrlScheme}}; +use yazi_shared::{MIME_DIR, url::{Scheme, Url}}; #[derive(Default)] pub struct Mimetype(HashMap); @@ -10,10 +10,10 @@ impl Mimetype { #[inline] pub fn by_url(&self, url: &Url) -> Option<&str> { match url.scheme() { - UrlScheme::Regular => self.0.get(url.as_path()), - UrlScheme::Search => None, - UrlScheme::SearchItem => self.0.get(url.as_path()), - UrlScheme::Archive => None, + Scheme::Regular => self.0.get(url.as_path()), + Scheme::Search => None, + Scheme::SearchItem => self.0.get(url.as_path()), + Scheme::Archive => None, } .map(|s| s.as_str()) } @@ -36,10 +36,10 @@ impl Mimetype { #[inline] pub fn contains(&self, url: &Url) -> bool { match url.scheme() { - UrlScheme::Regular => self.0.contains_key(url.as_path()), - UrlScheme::Search => false, - UrlScheme::SearchItem => self.0.contains_key(url.as_path()), - UrlScheme::Archive => false, + Scheme::Regular => self.0.contains_key(url.as_path()), + Scheme::Search => false, + Scheme::SearchItem => self.0.contains_key(url.as_path()), + Scheme::Archive => false, } } @@ -47,10 +47,10 @@ impl Mimetype { self.0.extend(iter.into_iter().filter_map(|(u, s)| { Some(( match u.scheme() { - UrlScheme::Regular => u.into_path(), - UrlScheme::Search => None?, - UrlScheme::SearchItem => u.into_path(), - UrlScheme::Archive => None?, + Scheme::Regular => u.into_path(), + Scheme::Search => None?, + Scheme::SearchItem => u.into_path(), + Scheme::Archive => None?, }, s, )) diff --git a/yazi-core/src/tab/commands/search.rs b/yazi-core/src/tab/commands/search.rs index fcb28852..bfeb3c0b 100644 --- a/yazi-core/src/tab/commands/search.rs +++ b/yazi-core/src/tab/commands/search.rs @@ -39,7 +39,7 @@ impl Tab { handle.abort(); } - let cwd = self.cwd().to_search(&opt.subject); + let cwd = self.cwd().to_search(opt.subject.as_ref()); let hidden = self.pref.show_hidden; self.search = Some(tokio::spawn(async move { diff --git a/yazi-core/src/tab/selected.rs b/yazi-core/src/tab/selected.rs index 364f5c10..769e626f 100644 --- a/yazi-core/src/tab/selected.rs +++ b/yazi-core/src/tab/selected.rs @@ -118,12 +118,14 @@ impl Selected { mod tests { use super::*; + fn url(s: &str) -> Url { Url::try_from(s).unwrap() } + #[test] fn test_insert_non_conflicting() { let mut s = Selected::default(); - assert!(s.add(&Url::from("/a/b"))); - assert!(s.add(&Url::from("/c/d"))); + assert!(s.add(&url("/a/b"))); + assert!(s.add(&url("/c/d"))); assert_eq!(s.inner.len(), 2); } @@ -131,27 +133,27 @@ mod tests { fn test_insert_conflicting_parent() { let mut s = Selected::default(); - assert!(s.add(&Url::from("/a"))); - assert!(!s.add(&Url::from("/a/b"))); + assert!(s.add(&url("/a"))); + assert!(!s.add(&url("/a/b"))); } #[test] fn test_insert_conflicting_child() { let mut s = Selected::default(); - assert!(s.add(&Url::from("/a/b/c"))); - assert!(!s.add(&Url::from("/a/b"))); - assert!(s.add(&Url::from("/a/b/d"))); + assert!(s.add(&url("/a/b/c"))); + assert!(!s.add(&url("/a/b"))); + assert!(s.add(&url("/a/b/d"))); } #[test] fn test_remove() { let mut s = Selected::default(); - assert!(s.add(&Url::from("/a/b"))); - assert!(!s.remove(&Url::from("/a/c"))); - assert!(s.remove(&Url::from("/a/b"))); - assert!(!s.remove(&Url::from("/a/b"))); + assert!(s.add(&url("/a/b"))); + assert!(!s.remove(&url("/a/c"))); + assert!(s.remove(&url("/a/b"))); + assert!(!s.remove(&url("/a/b"))); assert!(s.inner.is_empty()); assert!(s.parents.is_empty()); } @@ -162,11 +164,7 @@ mod tests { assert_eq!( 3, - s.add_same(&[ - &Url::from("/parent/child1"), - &Url::from("/parent/child2"), - &Url::from("/parent/child3") - ]) + s.add_same(&[&url("/parent/child1"), &url("/parent/child2"), &url("/parent/child3")]) ); } @@ -174,16 +172,16 @@ mod tests { fn insert_many_with_existing_parent_fails() { let mut s = Selected::default(); - s.add(&Url::from("/parent")); - assert_eq!(0, s.add_same(&[&Url::from("/parent/child1"), &Url::from("/parent/child2")])); + s.add(&url("/parent")); + assert_eq!(0, s.add_same(&[&url("/parent/child1"), &url("/parent/child2")])); } #[test] fn insert_many_with_existing_child_fails() { let mut s = Selected::default(); - s.add(&Url::from("/parent/child1")); - assert_eq!(2, s.add_same(&[&Url::from("/parent/child1"), &Url::from("/parent/child2")])); + s.add(&url("/parent/child1")); + assert_eq!(2, s.add_same(&[&url("/parent/child1"), &url("/parent/child2")])); } #[test] @@ -197,51 +195,48 @@ mod tests { fn insert_many_with_parent_as_child_of_another_url() { let mut s = Selected::default(); - s.add(&Url::from("/parent/child")); - assert_eq!( - 0, - s.add_same(&[&Url::from("/parent/child/child1"), &Url::from("/parent/child/child2")]) - ); + s.add(&url("/parent/child")); + assert_eq!(0, s.add_same(&[&url("/parent/child/child1"), &url("/parent/child/child2")])); } #[test] fn insert_many_with_direct_parent_fails() { let mut s = Selected::default(); - s.add(&Url::from("/a")); - assert_eq!(0, s.add_same(&[&Url::from("/a/b")])); + s.add(&url("/a")); + assert_eq!(0, s.add_same(&[&url("/a/b")])); } #[test] fn insert_many_with_nested_child_fails() { let mut s = Selected::default(); - s.add(&Url::from("/a/b")); - assert_eq!(0, s.add_same(&[&Url::from("/a")])); - assert_eq!(1, s.add_same(&[&Url::from("/b"), &Url::from("/a")])); + s.add(&url("/a/b")); + assert_eq!(0, s.add_same(&[&url("/a")])); + assert_eq!(1, s.add_same(&[&url("/b"), &url("/a")])); } #[test] fn insert_many_sibling_directories_success() { let mut s = Selected::default(); - assert_eq!(2, s.add_same(&[&Url::from("/a/b"), &Url::from("/a/c")])); + assert_eq!(2, s.add_same(&[&url("/a/b"), &url("/a/c")])); } #[test] fn insert_many_with_grandchild_fails() { let mut s = Selected::default(); - s.add(&Url::from("/a/b")); - assert_eq!(0, s.add_same(&[&Url::from("/a/b/c")])); + s.add(&url("/a/b")); + assert_eq!(0, s.add_same(&[&url("/a/b/c")])); } #[test] fn test_insert_many_with_remove() { let mut s = Selected::default(); - let child1 = Url::from("/parent/child1"); - let child2 = Url::from("/parent/child2"); - let child3 = Url::from("/parent/child3"); + let child1 = url("/parent/child1"); + let child2 = url("/parent/child2"); + let child3 = url("/parent/child3"); assert_eq!(3, s.add_same(&[&child1, &child2, &child3])); assert!(s.remove(&child1)); diff --git a/yazi-fm/src/cmp/cmp.rs b/yazi-fm/src/cmp/cmp.rs index 6a4e4a66..3bf37ab9 100644 --- a/yazi-fm/src/cmp/cmp.rs +++ b/yazi-fm/src/cmp/cmp.rs @@ -1,4 +1,4 @@ -use std::path::{MAIN_SEPARATOR_STR, Path}; +use std::path::MAIN_SEPARATOR_STR; use ratatui::{buffer::Buffer, layout::Rect, widgets::{Block, BorderType, List, ListItem, Widget}}; use yazi_adapter::Dimension; @@ -26,8 +26,7 @@ impl Widget for Cmp<'_> { let icon = if x.is_dir { &THEME.cmp.icon_folder } else { &THEME.cmp.icon_file }; let slash = if x.is_dir { MAIN_SEPARATOR_STR } else { "" }; - // FIXME: Bump Rust to 1.87.0 and use `OsStr::display()` instead - let mut item = ListItem::new(format!(" {icon} {}{slash}", Path::new(&x.name).display())); + let mut item = ListItem::new(format!(" {icon} {}{slash}", x.name.display())); if i == self.cx.cmp.rel_cursor() { item = item.style(THEME.cmp.active); } else { diff --git a/yazi-proxy/src/tab.rs b/yazi-proxy/src/tab.rs index 8bf94a2b..7d7ad104 100644 --- a/yazi-proxy/src/tab.rs +++ b/yazi-proxy/src/tab.rs @@ -13,7 +13,7 @@ impl TabProxy { } pub fn reveal(target: &Url) { - emit!(Call(Cmd::args("mgr:reveal", [target]).with("no-dummy", true))); + emit!(Call(Cmd::args("mgr:reveal", [target]).with("raw", true).with("no-dummy", true))); } pub fn arrow(step: impl Into>) { diff --git a/yazi-shared/src/bytes.rs b/yazi-shared/src/bytes.rs new file mode 100644 index 00000000..aedcb47f --- /dev/null +++ b/yazi-shared/src/bytes.rs @@ -0,0 +1,11 @@ +pub trait BytesExt { + fn split_by_seq(&self, sep: &[u8]) -> Option<(&[u8], &[u8])>; +} + +impl BytesExt for [u8] { + fn split_by_seq(&self, sep: &[u8]) -> Option<(&[u8], &[u8])> { + let idx = memchr::memmem::find(self, sep)?; + let (left, right) = self.split_at(idx); + Some((left, &right[sep.len()..])) + } +} diff --git a/yazi-shared/src/event/data.rs b/yazi-shared/src/event/data.rs index 2fc472df..089d19ec 100644 --- a/yazi-shared/src/event/data.rs +++ b/yazi-shared/src/event/data.rs @@ -56,7 +56,7 @@ impl Data { #[inline] pub fn into_url(self) -> Option { match self { - Data::String(s) => Some(Url::from(s.as_ref())), + Data::String(s) => Url::try_from(s.as_ref()).ok(), Data::Url(u) => Some(u), _ => None, } @@ -79,7 +79,7 @@ impl Data { #[inline] pub fn to_url(&self) -> Option { match self { - Self::String(s) => Some(Url::from(s.as_ref())), + Self::String(s) => Url::try_from(s.as_ref()).ok(), Self::Url(u) => Some(u.clone()), _ => None, } diff --git a/yazi-shared/src/lib.rs b/yazi-shared/src/lib.rs index c48f00b4..e05acf76 100644 --- a/yazi-shared/src/lib.rs +++ b/yazi-shared/src/lib.rs @@ -2,7 +2,7 @@ yazi_macro::mod_pub!(errors event shell translit url); -yazi_macro::mod_flat!(chars condition debounce either env id layer natsort number os rand ro_cell sync_cell terminal throttle time utf8); +yazi_macro::mod_flat!(bytes chars condition debounce either env id layer natsort number os osstr rand ro_cell sync_cell terminal throttle time utf8); pub fn init() { LOG_LEVEL.replace(<_>::from(std::env::var("YAZI_LOG").unwrap_or_default())); diff --git a/yazi-shared/src/osstr.rs b/yazi-shared/src/osstr.rs new file mode 100644 index 00000000..096e930b --- /dev/null +++ b/yazi-shared/src/osstr.rs @@ -0,0 +1,35 @@ +use std::{borrow::Cow, ffi::{OsStr, OsString}}; + +pub trait IntoOsStr<'a> { + type Error; + + fn into_os_str(self) -> Result, Self::Error>; +} + +impl<'a> IntoOsStr<'a> for Cow<'a, [u8]> { + type Error = anyhow::Error; + + fn into_os_str(self) -> Result, Self::Error> { + #[cfg(unix)] + { + use std::os::unix::ffi::{OsStrExt, OsStringExt}; + Ok(match self { + Cow::Borrowed(b) => Cow::Borrowed(OsStr::from_bytes(b)), + Cow::Owned(b) => Cow::Owned(OsString::from_vec(b)), + }) + } + #[cfg(windows)] + { + Ok(match self { + Cow::Borrowed(b) => Cow::Borrowed(OsStr::new(str::from_utf8(b)?)), + Cow::Owned(b) => Cow::Owned(OsString::from(String::from_utf8(b)?)), + }) + } + } +} + +impl<'a> IntoOsStr<'a> for &'a [u8] { + type Error = anyhow::Error; + + fn into_os_str(self) -> Result, Self::Error> { Cow::Borrowed(self).into_os_str() } +} diff --git a/yazi-shared/src/url/mod.rs b/yazi-shared/src/url/mod.rs index a2f1a4a6..8cac7ad0 100644 --- a/yazi-shared/src/url/mod.rs +++ b/yazi-shared/src/url/mod.rs @@ -1 +1 @@ -yazi_macro::mod_flat!(loc url urn); +yazi_macro::mod_flat!(loc scheme url urn); diff --git a/yazi-shared/src/url/scheme.rs b/yazi-shared/src/url/scheme.rs new file mode 100644 index 00000000..3a8bf357 --- /dev/null +++ b/yazi-shared/src/url/scheme.rs @@ -0,0 +1,36 @@ +use std::fmt::Display; + +use anyhow::bail; + +#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] +pub enum Scheme { + #[default] + Regular, + Search, + SearchItem, + Archive, +} + +impl TryFrom<&[u8]> for Scheme { + type Error = anyhow::Error; + + fn try_from(value: &[u8]) -> Result { + Ok(match value { + b"regular" => Scheme::Regular, + b"search" => Scheme::Search, + b"archive" => Scheme::Archive, + _ => bail!("Unknown URL scheme: {}", String::from_utf8_lossy(value)), + }) + } +} + +impl Display for Scheme { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + Scheme::Regular => write!(f, "regular"), + Scheme::Search => write!(f, "search"), + Scheme::SearchItem => write!(f, "search_item"), + Scheme::Archive => write!(f, "archive"), + } + } +} diff --git a/yazi-shared/src/url/url.rs b/yazi-shared/src/url/url.rs index 48bbd0ee..fcf1d115 100644 --- a/yazi-shared/src/url/url.rs +++ b/yazi-shared/src/url/url.rs @@ -1,27 +1,18 @@ -use std::{ffi::OsStr, fmt::{Debug, Display, Formatter}, hash::{BuildHasher, Hash, Hasher}, ops::Deref, path::{Path, PathBuf}}; +use std::{borrow::Cow, ffi::{OsStr, OsString}, fmt::{Debug, Display, Formatter}, hash::{BuildHasher, Hash, Hasher}, ops::Deref, path::{Path, PathBuf}}; -use percent_encoding::{AsciiSet, CONTROLS, percent_decode_str, percent_encode}; +use percent_encoding::{AsciiSet, CONTROLS, percent_decode, percent_encode}; use serde::{Deserialize, Serialize}; use super::UrnBuf; -use crate::url::Loc; +use crate::{BytesExt, IntoOsStr, url::{Loc, Scheme}}; const ENCODE_SET: &AsciiSet = &CONTROLS.add(b'#'); #[derive(Clone, Default, Eq, Ord, PartialOrd)] pub struct Url { loc: Loc, - scheme: UrlScheme, - frag: String, -} - -#[derive(Clone, Copy, Default, Eq, Hash, Ord, PartialEq, PartialOrd)] -pub enum UrlScheme { - #[default] - Regular, - Search, - SearchItem, - Archive, + scheme: Scheme, + frag: OsString, } impl Deref for Url { @@ -33,10 +24,10 @@ impl Deref for Url { impl Debug for Url { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { match self.scheme { - UrlScheme::Regular => write!(f, "Regular({:?})", self.loc), - UrlScheme::Search => write!(f, "Search({:?}, {})", self.loc, self.frag), - UrlScheme::SearchItem => write!(f, "SearchItem({:?})", self.loc), - UrlScheme::Archive => write!(f, "Archive({:?})", self.loc), + Scheme::Regular => write!(f, "Regular({:?})", self.loc), + Scheme::Search => write!(f, "Search({:?}, {})", self.loc, self.frag.display()), + Scheme::SearchItem => write!(f, "SearchItem({:?})", self.loc), + Scheme::Archive => write!(f, "Archive({:?})", self.loc), } } } @@ -57,44 +48,45 @@ impl From<&Path> for Url { fn from(path: &Path) -> Self { path.to_owned().into() } } -impl From<&str> for Url { - fn from(mut path: &str) -> Self { +impl TryFrom<&[u8]> for Url { + type Error = anyhow::Error; + + fn try_from(value: &[u8]) -> Result { let mut url = Url::default(); - match path.split_once("://").map(|(a, b)| (UrlScheme::from(a), b)) { - None => { - url.loc = Loc::new(PathBuf::from(path)); - return url; - } - Some((UrlScheme::Regular, b)) => { - url.loc = Loc::new(PathBuf::from(b)); - return url; - } - Some((a, b)) => { - url.scheme = a; - path = b; - } + let Some((scheme, rest)) = value.split_by_seq(b"://") else { + url.loc = Loc::new(value.into_os_str()?.into_owned().into()); + return Ok(url); + }; + + url.scheme = Scheme::try_from(scheme)?; + if url.scheme == Scheme::Regular { + url.loc = Loc::new(rest.into_os_str()?.into_owned().into()); + return Ok(url); } - match path.split_once('#') { - None => { - // FIXME: use `Loc::from(base, path)` instead - url.loc = Loc::new(percent_decode_str(path).decode_utf8_lossy().into_owned().into()); - } - Some((a, b)) => { - // FIXME: use `Loc::from(base, path)` instead - url.loc = Loc::new(percent_decode_str(a).decode_utf8_lossy().into_owned().into()); - url.frag = b.to_string(); - } - } - url + + let (loc, frag) = match rest.split_by_seq(b"#") { + None => (rest, OsString::new()), + Some((a, b)) => (a, b.into_os_str()?.into_owned()), + }; + + // FIXME: use `Loc::from(base, path)` instead + url.loc = Loc::new(Cow::from(percent_decode(loc)).into_os_str()?.into_owned().into()); + url.frag = frag; + + Ok(url) } } -impl From for Url { - fn from(path: String) -> Self { path.as_str().into() } +impl TryFrom<&str> for Url { + type Error = anyhow::Error; + + fn try_from(value: &str) -> Result { value.as_bytes().try_into() } } -impl From<&String> for Url { - fn from(path: &String) -> Self { path.as_str().into() } +impl TryFrom for Url { + type Error = anyhow::Error; + + fn try_from(value: String) -> Result { value.as_bytes().try_into() } } impl AsRef for Url { @@ -111,20 +103,15 @@ impl AsRef for Url { impl Display for Url { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { - if matches!(self.scheme, UrlScheme::Regular | UrlScheme::SearchItem) { + if matches!(self.scheme, Scheme::Regular | Scheme::SearchItem) { return write!(f, "{}", self.loc.display()); } - let scheme = match self.scheme { - UrlScheme::Regular | UrlScheme::SearchItem => unreachable!(), - UrlScheme::Search => "search://", - UrlScheme::Archive => "archive://", - }; - let path = percent_encode(self.loc.as_os_str().as_encoded_bytes(), ENCODE_SET); + let loc = percent_encode(self.loc.as_os_str().as_encoded_bytes(), ENCODE_SET); + write!(f, "{}://{loc}", self.scheme)?; - write!(f, "{scheme}{path}")?; if !self.frag.is_empty() { - write!(f, "#{}", self.frag)?; + write!(f, "#{}", percent_encode(self.frag.as_encoded_bytes(), ENCODE_SET))?; } Ok(()) @@ -139,16 +126,16 @@ impl Url { #[inline] pub fn join(&self, path: impl AsRef) -> Self { match self.scheme { - UrlScheme::Regular => Self::from(self.loc.join(path)), - UrlScheme::Search => { + Scheme::Regular => Self::from(self.loc.join(path)), + Scheme::Search => { let loc = Loc::from(&self.loc, self.loc.join(path)); Self::from(loc).into_search_item() } - UrlScheme::SearchItem => { + Scheme::SearchItem => { let loc = Loc::from(self.loc.base(), self.loc.join(path)); Self::from(loc).into_search_item() } - UrlScheme::Archive => Self::from(self.loc.join(path)).into_archive(), + Scheme::Archive => Self::from(self.loc.join(path)).into_archive(), } } @@ -156,15 +143,15 @@ impl Url { pub fn parent_url(&self) -> Option { let p = self.loc.parent()?; Some(match self.scheme { - UrlScheme::Regular | UrlScheme::Search => Self::from(p), - UrlScheme::SearchItem => { + Scheme::Regular | Scheme::Search => Self::from(p), + Scheme::SearchItem => { if p == self.loc.base() { Self::from(p).into_search("") } else { Self::from(p).into_search_item() } } - UrlScheme::Archive => Self::from(p), + Scheme::Archive => Self::from(p), }) } @@ -184,58 +171,56 @@ impl Url { impl Url { // --- Regular #[inline] - pub fn is_regular(&self) -> bool { self.scheme == UrlScheme::Regular } + pub fn is_regular(&self) -> bool { self.scheme == Scheme::Regular } #[inline] - pub fn to_regular(&self) -> Self { - Self { loc: self.loc.clone(), scheme: UrlScheme::Regular, frag: String::new() } - } + pub fn to_regular(&self) -> Self { Self { loc: self.loc.clone(), ..Default::default() } } #[inline] pub fn into_regular(mut self) -> Self { - self.scheme = UrlScheme::Regular; - self.frag = String::new(); + self.scheme = Scheme::Regular; + self.frag = OsString::new(); self } // --- Search #[inline] - pub fn is_search(&self) -> bool { self.scheme == UrlScheme::Search } + pub fn is_search(&self) -> bool { self.scheme == Scheme::Search } #[inline] - pub fn to_search(&self, frag: &str) -> Self { - Self { loc: self.loc.clone(), scheme: UrlScheme::Search, frag: frag.to_owned() } + pub fn to_search(&self, frag: impl AsRef) -> Self { + Self { loc: self.loc.clone(), scheme: Scheme::Search, frag: frag.as_ref().to_owned() } } #[inline] - pub fn into_search(mut self, frag: &str) -> Self { - self.scheme = UrlScheme::Search; - self.frag = frag.to_owned(); + pub fn into_search(mut self, frag: impl AsRef) -> Self { + self.scheme = Scheme::Search; + self.frag = frag.as_ref().to_owned(); self } #[inline] - pub fn is_search_item(&self) -> bool { self.scheme == UrlScheme::SearchItem } + pub fn is_search_item(&self) -> bool { self.scheme == Scheme::SearchItem } #[inline] pub fn into_search_item(mut self) -> Self { - self.scheme = UrlScheme::SearchItem; - self.frag = String::new(); + self.scheme = Scheme::SearchItem; + self.frag = OsString::new(); self } #[inline] - pub fn is_archive(&self) -> bool { self.scheme == UrlScheme::Archive } + pub fn is_archive(&self) -> bool { self.scheme == Scheme::Archive } #[inline] pub fn to_archive(&self) -> Self { - Self { loc: self.loc.clone(), scheme: UrlScheme::Archive, frag: String::new() } + Self { loc: self.loc.clone(), scheme: Scheme::Archive, ..Default::default() } } #[inline] pub fn into_archive(mut self) -> Self { - self.scheme = UrlScheme::Archive; - self.frag = String::new(); + self.scheme = Scheme::Archive; + self.frag = OsString::new(); self } @@ -251,21 +236,11 @@ impl Url { // --- Scheme #[inline] - pub fn scheme(&self) -> UrlScheme { self.scheme } + pub fn scheme(&self) -> Scheme { self.scheme } // --- Frag #[inline] - pub fn frag(&self) -> &str { &self.frag } -} - -impl From<&str> for UrlScheme { - fn from(value: &str) -> Self { - match value { - "search" => UrlScheme::Search, - "archive" => UrlScheme::Archive, - _ => UrlScheme::Regular, - } - } + pub fn frag(&self) -> &OsStr { &self.frag } } impl Hash for Url { @@ -273,8 +248,8 @@ impl Hash for Url { self.loc.hash(state); match self.scheme { - UrlScheme::Regular | UrlScheme::SearchItem => {} - UrlScheme::Search | UrlScheme::Archive => { + Scheme::Regular | Scheme::SearchItem => {} + Scheme::Search | Scheme::Archive => { self.scheme.hash(state); self.frag.hash(state); } @@ -285,7 +260,7 @@ impl Hash for Url { impl PartialEq for Url { fn eq(&self, other: &Self) -> bool { match (self.scheme, other.scheme) { - (UrlScheme::Regular | UrlScheme::SearchItem, UrlScheme::Regular | UrlScheme::SearchItem) => { + (Scheme::Regular | Scheme::SearchItem, Scheme::Regular | Scheme::SearchItem) => { self.loc == other.loc } _ => self.loc == other.loc && self.scheme == other.scheme, @@ -305,6 +280,6 @@ impl<'de> Deserialize<'de> for Url { D: serde::Deserializer<'de>, { let s = String::deserialize(deserializer)?; - Ok(Url::from(s)) + Url::try_from(s).map_err(serde::de::Error::custom) } }