use std::fmt; /// Identifier in `.proto` file #[derive(Eq, PartialEq, Debug, Clone)] pub struct ProtobufIdent(String); impl ProtobufIdent { /// New ident from a string. #[allow(dead_code)] pub fn new(s: &str) -> ProtobufIdent { assert!(!s.is_empty()); assert!(!s.contains("/")); assert!(!s.contains(".")); assert!(!s.contains(":")); ProtobufIdent(s.to_owned()) } /// Get as a string. pub fn get(&self) -> &str { &self.0 } } impl From<&'_ str> for ProtobufIdent { fn from(s: &str) -> Self { ProtobufIdent::new(s) } } impl From for ProtobufIdent { fn from(s: String) -> Self { ProtobufIdent::new(&s) } } impl fmt::Display for ProtobufIdent { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.get(), f) } } /// Relative protobuf identifier path. #[derive(Debug, Eq, PartialEq, Clone)] pub struct ProtobufRelativePath { /// The path pub path: String, } #[allow(dead_code)] impl ProtobufRelativePath { /// Empty relative path. pub fn empty() -> ProtobufRelativePath { ProtobufRelativePath::new(String::new()) } /// New path from a string. pub fn new(path: String) -> ProtobufRelativePath { assert!(!path.starts_with(".")); ProtobufRelativePath { path } } /// From path components. pub fn from_components>(i: I) -> ProtobufRelativePath { let v: Vec = i.into_iter().map(|c| c.get().to_owned()).collect(); ProtobufRelativePath::from(v.join(".")) } /// Get the string. pub fn get(&self) -> &str { &self.path } /// The path is empty. pub fn is_empty(&self) -> bool { self.path.is_empty() } /// As absolute path from root namespace. pub fn into_absolute(self) -> ProtobufAbsolutePath { if self.is_empty() { ProtobufAbsolutePath::root() } else { ProtobufAbsolutePath::from(format!(".{}", self)) } } fn _last_part(&self) -> Option<&str> { match self.path.rfind('.') { Some(pos) => Some(&self.path[pos + 1..]), None => { if self.path.is_empty() { None } else { Some(&self.path) } } } } fn parent(&self) -> Option { match self.path.rfind('.') { Some(pos) => Some(ProtobufRelativePath::new(self.path[..pos].to_owned())), None => { if self.path.is_empty() { None } else { Some(ProtobufRelativePath::empty()) } } } } /// Self path and parent paths. pub fn self_and_parents(&self) -> Vec { let mut tmp = self.clone(); let mut r = Vec::new(); r.push(self.clone()); while let Some(parent) = tmp.parent() { r.push(parent.clone()); tmp = parent; } r } /// Append path component. pub fn append(&self, simple: &ProtobufRelativePath) -> ProtobufRelativePath { if self.path.is_empty() { ProtobufRelativePath::from(simple.get()) } else { ProtobufRelativePath::new(format!("{}.{}", self.path, simple)) } } /// Append identifier to the path. pub fn append_ident(&self, simple: &ProtobufIdent) -> ProtobufRelativePath { self.append(&ProtobufRelativePath::from(simple.clone())) } /// Get first component path and remaining. pub fn split_first_rem(&self) -> Option<(ProtobufIdent, ProtobufRelativePath)> { if self.is_empty() { None } else { Some(match self.path.find('.') { Some(dot) => ( ProtobufIdent::from(&self.path[..dot]), ProtobufRelativePath::new(self.path[dot + 1..].to_owned()), ), None => ( ProtobufIdent::from(self.path.clone()), ProtobufRelativePath::empty(), ), }) } } } impl From<&'_ str> for ProtobufRelativePath { fn from(s: &str) -> ProtobufRelativePath { ProtobufRelativePath::from(s.to_owned()) } } impl From for ProtobufRelativePath { fn from(s: String) -> ProtobufRelativePath { ProtobufRelativePath::new(s) } } impl From for ProtobufRelativePath { fn from(s: ProtobufIdent) -> ProtobufRelativePath { ProtobufRelativePath::from(s.get()) } } impl From> for ProtobufRelativePath { fn from(s: Vec) -> ProtobufRelativePath { ProtobufRelativePath::from_components(s.into_iter()) } } impl fmt::Display for ProtobufRelativePath { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.path, f) } } #[cfg(test)] mod relative_path_test { use super::*; #[test] fn parent() { assert_eq!(None, ProtobufRelativePath::empty().parent()); assert_eq!( Some(ProtobufRelativePath::empty()), ProtobufRelativePath::new("aaa".to_owned()).parent() ); assert_eq!( Some(ProtobufRelativePath::new("abc".to_owned())), ProtobufRelativePath::new("abc.def".to_owned()).parent() ); assert_eq!( Some(ProtobufRelativePath::new("abc.def".to_owned())), ProtobufRelativePath::new("abc.def.gh".to_owned()).parent() ); } #[test] fn last_part() { assert_eq!(None, ProtobufRelativePath::empty()._last_part()); assert_eq!( Some("aaa"), ProtobufRelativePath::new("aaa".to_owned())._last_part() ); assert_eq!( Some("def"), ProtobufRelativePath::new("abc.def".to_owned())._last_part() ); assert_eq!( Some("gh"), ProtobufRelativePath::new("abc.def.gh".to_owned())._last_part() ); } } /// Absolute protobuf path (e. g. package). /// /// This is not filesystem path. #[derive(Clone, Eq, PartialEq, Debug, Hash)] pub struct ProtobufAbsolutePath { /// The path. pub path: String, } impl ProtobufAbsolutePath { fn root() -> ProtobufAbsolutePath { ProtobufAbsolutePath::new(String::new()) } /// From string. pub fn new(path: String) -> ProtobufAbsolutePath { assert!(path.is_empty() || path.starts_with("."), path); assert!(!path.ends_with("."), path); ProtobufAbsolutePath { path } } /// The path is empty. pub fn is_empty(&self) -> bool { self.path.is_empty() } /// From a path without leading dot. /// /// (Protobuf paths start with dot). pub fn from_path_without_dot(path: &str) -> ProtobufAbsolutePath { if path.is_empty() { ProtobufAbsolutePath::root() } else { assert!(!path.starts_with(".")); assert!(!path.ends_with(".")); ProtobufAbsolutePath::new(format!(".{}", path)) } } /// Parse absolute path. #[allow(dead_code)] pub fn from_package_path(path: Option<&str>) -> ProtobufAbsolutePath { match path { None => ProtobufAbsolutePath::root(), Some(path) => ProtobufAbsolutePath::from_path_without_dot(path), } } /// Construct abs path from a string which may start with a dot. pub fn from_path_maybe_dot(path: &str) -> ProtobufAbsolutePath { if path.starts_with(".") { ProtobufAbsolutePath::new(path.to_owned()) } else { ProtobufAbsolutePath::from_path_without_dot(path) } } /// Push identifier to the path. pub fn push_simple(&mut self, simple: ProtobufIdent) { self.path.push('.'); self.path.push_str(simple.get()); } /// Push relative path. pub fn push_relative(&mut self, relative: &ProtobufRelativePath) { if !relative.is_empty() { self.path.push('.'); self.path.push_str(&relative.path); } } /// Try remove a prefix. pub fn remove_prefix(&self, prefix: &ProtobufAbsolutePath) -> Option { if self.path.starts_with(&prefix.path) { let rem = &self.path[prefix.path.len()..]; if rem.is_empty() { return Some(ProtobufRelativePath::empty()); } if rem.starts_with('.') { return Some(ProtobufRelativePath::new(rem[1..].to_owned())); } } None } } impl From<&'_ str> for ProtobufAbsolutePath { fn from(s: &str) -> Self { ProtobufAbsolutePath::new(s.to_owned()) } } impl From for ProtobufAbsolutePath { fn from(s: String) -> Self { ProtobufAbsolutePath::new(s) } } impl fmt::Display for ProtobufAbsolutePath { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { fmt::Display::fmt(&self.path, f) } } #[cfg(test)] mod absolute_path_test { use super::*; #[test] fn absolute_path_push_simple() { let mut foo = ProtobufAbsolutePath::new(".foo".to_owned()); foo.push_simple(ProtobufIdent::from("bar")); assert_eq!(ProtobufAbsolutePath::new(".foo.bar".to_owned()), foo); let mut foo = ProtobufAbsolutePath::root(); foo.push_simple(ProtobufIdent::from("bar")); assert_eq!(ProtobufAbsolutePath::new(".bar".to_owned()), foo); } #[test] fn absolute_path_remove_prefix() { assert_eq!( Some(ProtobufRelativePath::empty()), ProtobufAbsolutePath::new(".foo".to_owned()) .remove_prefix(&ProtobufAbsolutePath::new(".foo".to_owned())) ); assert_eq!( Some(ProtobufRelativePath::new("bar".to_owned())), ProtobufAbsolutePath::new(".foo.bar".to_owned()) .remove_prefix(&ProtobufAbsolutePath::new(".foo".to_owned())) ); assert_eq!( Some(ProtobufRelativePath::new("baz.qux".to_owned())), ProtobufAbsolutePath::new(".foo.bar.baz.qux".to_owned()) .remove_prefix(&ProtobufAbsolutePath::new(".foo.bar".to_owned())) ); assert_eq!( None, ProtobufAbsolutePath::new(".foo.barbaz".to_owned()) .remove_prefix(&ProtobufAbsolutePath::new(".foo.bar".to_owned())) ); } }