use crate::error::{ExpandPathError, ExpandPathLexerError};
use camino::{Utf8Path, Utf8PathBuf};
use std::marker::PhantomData;
use std::path::MAIN_SEPARATOR_STR;
use std::{collections::VecDeque, convert::AsRef, string::ToString};
use unicode_segmentation::UnicodeSegmentation;
#[derive(Debug, Clone, PartialEq)]
enum Token {
IdentifierTilde,
Identifier { name: String, uses_curly: bool },
Separator,
Text(String),
}
#[derive(Clone, Debug)]
struct Parts<'a> {
index: usize,
started: bool,
wide_chars: Vec<&'a str>,
}
impl<'a> Iterator for Parts<'a> {
type Item = &'a str;
#[inline]
fn next(&mut self) -> Option<&'a str> {
if self.started {
self.index += 1;
} else {
self.started = true;
}
let out = self.nth(self.index);
if out.is_none() {
self.index = 0;
self.started = false;
}
out
}
#[inline]
fn nth(
&mut self,
index: usize,
) -> Option<&'a str> {
match self.wide_chars.get(index) {
Some(s) => Some(*s),
None => None,
}
}
}
impl<'a> Parts<'a> {
fn new(text: &'a str) -> Self {
let wide_chars = UnicodeSegmentation::graphemes(text, true)
.collect::<Vec<&str>>();
Self {
index: 0usize,
started: false,
wide_chars,
}
}
#[inline]
fn peek(&mut self) -> Option<&'a str> {
let mut index = self.index;
if self.started {
index += 1;
}
self.nth(index)
}
#[inline]
fn bump_index(&mut self) {
self.index += 1;
}
#[inline]
fn possible_token_count(&self) -> usize {
let mut count = 0usize;
for item in self.wide_chars.iter() {
if item == &MAIN_SEPARATOR_STR {
count += 1
}
}
count = count * 2 + 1;
count
}
fn extract_text(&mut self) -> String {
let mut out = String::new();
let mut escape = false;
while let Some(wide_char) = self.peek() {
if !escape {
match wide_char {
"\\" => {
escape = true;
self.bump_index();
continue;
},
"$" => break,
MAIN_SEPARATOR_STR => break,
_ => {},
}
}
out.push_str(wide_char);
escape = false;
self.bump_index();
}
out
}
fn extract_identifier(
&mut self
) -> Result<(String, bool), ExpandPathLexerError> {
let mut start_position = 0usize;
let mut curly = false;
let mut uses_curly = false;
let mut curly_start_position = 0usize;
let mut started = false;
let mut out = String::new();
let mut position = 0usize;
while let Some(wide_char) = self.peek() {
if position == 0 {
if wide_char != "$" {
return Err(
ExpandPathLexerError::MissingIdentifierSymbol(
self.index + 1,
),
);
}
position += 1;
start_position = self.index + 1;
self.bump_index();
continue;
}
if position == 1 && wide_char == "{" {
curly = true;
uses_curly = true;
curly_start_position = self.index + 1;
position += 1;
self.bump_index();
continue;
}
if !wide_char.is_ascii() {
break;
}
if !started {
if is_identifier_start(wide_char) {
out.push_str(wide_char);
self.bump_index();
position += 1;
started = true;
continue;
}
self.bump_index();
if uses_curly && wide_char == "}" {
return Err(
ExpandPathLexerError::MissingIdentifierName(
self.index - 1,
),
);
}
return Err(
ExpandPathLexerError::invalid_identifier_start_character(
wide_char,
self.index - 1,
),
);
}
if is_identifier(wide_char) {
out.push_str(wide_char);
self.bump_index();
position += 1;
continue;
}
if curly && wide_char == "}" {
self.bump_index();
curly = false;
}
break;
}
if curly {
return Err(ExpandPathLexerError::MissingClosingCurly(
curly_start_position,
));
}
if out.is_empty() {
return Err(
ExpandPathLexerError::MissingIdentifierName(
start_position,
),
);
}
Ok((out, uses_curly))
}
}
#[inline(always)]
fn is_identifier_start(wide_char: &str) -> bool {
if !wide_char.is_ascii() {
return false;
}
let c = wide_char.chars().next().unwrap_or_default();
if c.is_alphabetic() {
return true;
}
if c == '_' {
return true;
}
false
}
#[inline(always)]
fn is_identifier(wide_char: &str) -> bool {
if !wide_char.is_ascii() {
return false;
}
let c = wide_char.chars().next().unwrap_or_default();
if c.is_alphanumeric() {
return true;
}
if c == '_' {
return true;
}
false
}
fn tokenize<T>(
text: &T
) -> Result<VecDeque<Token>, ExpandPathLexerError>
where
T: AsRef<Utf8Path> + ?Sized,
{
let text = text.as_ref().to_string();
let mut parts = Parts::new(text.as_ref());
let mut out: VecDeque<Token> =
VecDeque::with_capacity(parts.possible_token_count());
let mut position = 0usize;
let mut started = false;
while let Some(wide_char) = parts.peek() {
if started {
position += 1;
} else {
started = true;
}
if position == 0 && wide_char == "~" {
if let Some(s) = parts.nth(1usize) {
if s != MAIN_SEPARATOR_STR {
return Err(
ExpandPathLexerError::InvalidTildeUse(
MAIN_SEPARATOR_STR.to_string(),
),
);
}
}
parts.bump_index();
out.push_back(Token::IdentifierTilde);
continue;
}
match wide_char {
"\\" => {
out.push_back(Token::Text(parts.extract_text()));
continue;
},
MAIN_SEPARATOR_STR => {
parts.bump_index();
out.push_back(Token::Separator);
position += 1;
continue;
},
"$" => {
let (name, uses_curly) =
parts.extract_identifier()?;
out.push_back(Token::Identifier {
name,
uses_curly,
});
},
_ => out.push_back(Token::Text(parts.extract_text())),
}
}
Ok(out)
}
#[inline(always)]
fn tokens_to_string(tokens: &VecDeque<Token>) -> String {
let mut out = String::new();
for token in tokens.iter() {
match token {
Token::IdentifierTilde => out.push('~'),
Token::Separator => out.push_str(MAIN_SEPARATOR_STR),
Token::Text(val) => out.push_str(val),
Token::Identifier { name, uses_curly } => {
out.push('$');
if *uses_curly {
out.push('{');
}
out.push_str(name);
if *uses_curly {
out.push('}');
}
},
}
}
out
}
pub struct Locked;
pub struct Unlocked;
pub struct ExpandPath<State = Locked> {
home: Option<Vec<Token>>,
cwd: Option<Vec<Token>>,
fetch: Option<fn(&str) -> Option<String>>,
state: PhantomData<State>,
}
impl Default for ExpandPath {
fn default() -> Self {
Self::new()
}
}
impl ExpandPath {
pub fn new() -> ExpandPath<Locked> {
ExpandPath {
home: None,
cwd: None,
fetch: None,
state: Default::default(),
}
}
}
impl ExpandPath<Unlocked> {
fn path_as_tokens(
&self,
path: &str,
) -> Result<VecDeque<Token>, ExpandPathError> {
tokenize(&path)
.map_err(|e| ExpandPathError::lexer_error(&path, e))
}
fn expand_tilde(
&self,
tokens: &mut VecDeque<Token>,
) {
let home = match &self.home {
Some(path) => path,
None => return,
};
if let Some(token) = tokens.front() {
match token {
Token::IdentifierTilde => {
let _ = tokens.pop_front();
},
_ => return,
}
}
for token in home.iter().rev() {
tokens.push_front(token.clone());
}
}
fn expand_home(
&self,
tokens: &mut VecDeque<Token>,
) {
let home = match &self.home {
Some(path) => path,
None => return,
};
for i in 0..tokens.len() {
let token = tokens.get(i).unwrap();
if let Token::Identifier { name, .. } = token {
if name == "HOME" {
let mut started = false;
for home_token in home.iter().rev() {
if !started {
tokens[i] = home_token.clone();
started = true;
} else {
tokens.insert(i, home_token.clone());
}
}
}
}
}
}
fn expand_cwd(
&self,
tokens: &mut VecDeque<Token>,
) {
let cwd = match &self.cwd {
Some(path) => path,
None => return,
};
if let Some(token) = tokens.front() {
match token {
Token::Separator => return,
Token::IdentifierTilde => return,
_ => {},
}
}
tokens.push_front(Token::Separator);
for token in cwd.iter().rev() {
tokens.push_front(token.clone());
}
}
fn expand_with(
&self,
tokens: &mut VecDeque<Token>,
) {
let fetch = match self.fetch {
Some(func) => func,
None => return,
};
for i in 0..tokens.len() {
let token = tokens.get(i).unwrap();
if let Token::Identifier { name, .. } = token {
if let Some(val) = fetch(name) {
tokens[i] = Token::Text(val)
}
}
}
}
fn expand_strict_with(
&self,
tokens: &mut VecDeque<Token>,
path: &str,
) -> Result<(), ExpandPathError> {
let fetch = match self.fetch {
Some(func) => func,
None => return Ok(()),
};
for i in 0..tokens.len() {
let token = tokens.get(i).unwrap();
if let Token::Identifier { name, .. } = token {
match fetch(name) {
Some(val) => tokens[i] = Token::Text(val),
None => {
return Err(
ExpandPathError::identifier_expand_error(
&path, name,
),
);
},
}
}
}
Ok(())
}
pub fn path<T>(
&self,
path: &T,
) -> Result<Utf8PathBuf, ExpandPathError>
where
T: AsRef<str> + ?Sized,
{
let path = path.as_ref().to_string();
if path.is_empty() {
return Err(ExpandPathError::EmptyPathError);
}
let mut tokens = self.path_as_tokens(&path)?;
self.expand_tilde(&mut tokens);
self.expand_home(&mut tokens);
self.expand_cwd(&mut tokens);
self.expand_with(&mut tokens);
let path = tokens_to_string(&tokens);
Ok(Utf8PathBuf::from(path))
}
pub fn path_strict<T>(
&self,
path: &T,
) -> Result<Utf8PathBuf, ExpandPathError>
where
T: AsRef<str> + ?Sized,
{
let path = path.as_ref().to_string();
if path.is_empty() {
return Err(ExpandPathError::EmptyPathError);
}
let mut tokens = self.path_as_tokens(&path)?;
self.expand_tilde(&mut tokens);
self.expand_home(&mut tokens);
self.expand_cwd(&mut tokens);
self.expand_strict_with(&mut tokens, &path)?;
let path = tokens_to_string(&tokens);
Ok(Utf8PathBuf::from(path))
}
}
impl<State> ExpandPath<State> {
fn str_as_tokens(
&self,
path: &str,
) -> Result<Vec<Token>, ExpandPathError> {
Ok(tokenize(&path)
.map_err(|e| ExpandPathError::lexer_error(&path, e))?
.into_iter()
.collect::<Vec<Token>>())
}
pub fn set_home<T>(
self,
path: &T,
) -> Result<ExpandPath<Unlocked>, ExpandPathError>
where
T: AsRef<str> + ?Sized,
{
let path = path.as_ref();
let home = Utf8PathBuf::from(&path);
if !home.is_absolute() {
return Err(ExpandPathError::NotAbsoluteHome(
path.to_string(),
));
}
let home = self.str_as_tokens(path)?;
Ok(ExpandPath {
home: Some(home),
cwd: self.cwd,
fetch: self.fetch,
state: PhantomData::<Unlocked>,
})
}
pub fn set_cwd<T>(
self,
path: &T,
) -> Result<ExpandPath<Unlocked>, ExpandPathError>
where
T: AsRef<str> + ?Sized,
{
let path = path.as_ref();
let cwd = Utf8PathBuf::from(&path);
if !cwd.is_absolute() {
return Err(ExpandPathError::NotAbsoluteCwd(
path.to_string(),
));
}
let cwd = self.str_as_tokens(path)?;
Ok(ExpandPath {
home: self.home,
cwd: Some(cwd),
fetch: self.fetch,
state: PhantomData::<Unlocked>,
})
}
pub fn set_fetch(
self,
fetch: fn(&str) -> Option<String>,
) -> ExpandPath<Unlocked> {
ExpandPath {
home: self.home,
cwd: self.cwd,
fetch: Some(fetch),
state: PhantomData::<Unlocked>,
}
}
}