use std::{
fmt::{self, Display},
str::FromStr,
};
use crate::{error::ParseError, STD_CRATES};
pub struct SimplePath(String, usize);
impl SimplePath {
#[allow(clippy::missing_const_for_fn)]
#[must_use]
pub fn into_inner(self) -> String {
self.0
}
#[must_use]
pub fn crate_name(&self) -> &str {
&self.0[..self.1]
}
#[must_use]
pub fn is_std(&self) -> bool {
STD_CRATES.contains(&self.crate_name())
}
pub(crate) fn is_crate_only(&self) -> bool {
self.0.len() == self.1
}
}
impl FromStr for SimplePath {
type Err = ParseError;
fn from_str(s: &str) -> Result<Self, Self::Err> {
if s.is_empty() {
return Err(Self::Err::TooShort);
}
if !s.split("::").all(is_identifier) {
return Err(Self::Err::InvalidIdentifier);
}
let index = s.find("::").unwrap_or(s.len());
Ok(Self(s.to_owned(), index))
}
}
impl AsRef<str> for SimplePath {
fn as_ref(&self) -> &str {
&self.0
}
}
impl Display for SimplePath {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
self.0.fmt(f)
}
}
fn is_identifier_or_keyword(value: &str) -> bool {
fn variant_one(first_char: char, value: &str) -> bool {
unicode_ident::is_xid_start(first_char)
&& value.chars().skip(1).all(unicode_ident::is_xid_continue)
}
fn variant_two(first_char: char, value: &str) -> bool {
first_char == '_'
&& value.chars().skip(1).count() > 0
&& value.chars().skip(1).all(unicode_ident::is_xid_continue)
}
let first_char = match value.chars().next() {
Some(ch) => ch,
None => return false,
};
variant_one(first_char, value) || variant_two(first_char, value)
}
fn is_raw_identifier(value: &str) -> bool {
const KEYWORDS: &[&str] = &["crate", "self", "super", "Self"];
value
.strip_prefix("r#")
.map(|value| is_identifier_or_keyword(value) && !KEYWORDS.contains(&value))
.unwrap_or_default()
}
fn is_non_keyword_identifier(value: &str) -> bool {
const STRICT_KEYWORDS: &[&str] = &[
"as", "break", "const", "continue", "crate", "else", "enum", "extern", "false", "fn",
"for", "if", "impl", "in", "let", "loop", "match", "mod", "move", "mut", "pub", "ref",
"return", "self", "Self", "static", "struct", "super", "trait", "true", "type", "unsafe",
"use", "where", "while", "async", "await", "dyn",
];
const RESERVED_KEYWORDS: &[&str] = &[
"abstract", "become", "box", "do", "final", "macro", "override", "priv", "typeof",
"unsized", "virtual", "yield",
];
is_identifier_or_keyword(value)
&& !STRICT_KEYWORDS.contains(&value)
&& !RESERVED_KEYWORDS.contains(&value)
}
fn is_identifier(value: &str) -> bool {
is_non_keyword_identifier(value) || is_raw_identifier(value)
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn parse_valid() {
let inputs = &["anyhow", "anyhow::Result", "special::__", "__", "r#unsafe"];
for input in inputs {
assert!(input.parse::<SimplePath>().is_ok());
}
}
#[test]
fn parse_invalid() {
let inputs = &["", "a::::b", "::", "_", "unsafe", "Self", "r#Self"];
for input in inputs {
assert!(input.parse::<SimplePath>().is_err());
}
}
}