use crate::kinds::SyntaxKind::{TOKEN_PATH_ABS, TOKEN_PATH_HOME, TOKEN_PATH_REL};
use rowan::{ast::AstNode as OtherAstNode, NodeOrToken};
pub use super::nodes::Path;
use super::{
nodes::{PathAbs, PathHome, PathRel, PathSearch},
AstToken, InterpolPart, PathContent,
};
use crate::ast;
fn extract_path_parts<T: ast::AstNode>(node: &T) -> Vec<InterpolPart<PathContent>> {
node.syntax()
.children_with_tokens()
.map(|child| match child {
NodeOrToken::Token(token) => {
debug_assert!(matches!(
token.kind(),
TOKEN_PATH_ABS | TOKEN_PATH_REL | TOKEN_PATH_HOME
));
InterpolPart::Literal(PathContent::cast(token).unwrap())
}
NodeOrToken::Node(node) => {
InterpolPart::Interpolation(ast::Interpol::cast(node).unwrap())
}
})
.collect()
}
impl PathAbs {
pub fn parts(&self) -> Vec<InterpolPart<PathContent>> {
extract_path_parts(self)
}
}
impl PathRel {
pub fn parts(&self) -> Vec<InterpolPart<PathContent>> {
extract_path_parts(self)
}
}
impl PathHome {
pub fn parts(&self) -> Vec<InterpolPart<PathContent>> {
extract_path_parts(self)
}
}
impl PathSearch {
pub fn content(&self) -> Option<PathContent> {
self.syntax()
.children_with_tokens()
.filter_map(|child| child.into_token().and_then(PathContent::cast))
.next()
}
}
impl Path {
pub fn parts(&self) -> Vec<InterpolPart<PathContent>> {
match self {
Path::PathAbs(p) => p.parts(),
Path::PathRel(p) => p.parts(),
Path::PathHome(p) => p.parts(),
Path::PathSearch(p) => {
if let Some(content) = p.content() {
vec![InterpolPart::Literal(content)]
} else {
vec![]
}
}
}
}
pub fn is_search(&self) -> bool {
matches!(self, Path::PathSearch(_))
}
pub fn is_interpolatable(&self) -> bool {
!self.is_search()
}
}
#[cfg(test)]
mod tests {
use rowan::ast::AstNode;
use super::InterpolPart;
use crate::{
ast::{self, Path},
Root,
};
#[test]
fn test_path_types() {
let inp = "/foo/bar";
let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
if let ast::Expr::PathAbs(p) = expr {
let path = Path::cast(p.syntax().clone()).unwrap();
assert!(path.is_interpolatable());
assert!(!path.is_search());
}
let inp = "<nixpkgs>";
let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
if let ast::Expr::PathSearch(p) = expr {
let path = Path::cast(p.syntax().clone()).unwrap();
assert!(!path.is_interpolatable());
assert!(path.is_search());
}
}
#[test]
fn test_parts() {
let inp = "/foo/bar";
let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
if let ast::Expr::PathAbs(p) = expr {
let path = Path::cast(p.syntax().clone()).unwrap();
let parts = path.parts();
assert_eq!(parts.len(), 1);
match &parts[0] {
InterpolPart::Literal(content) => {
assert_eq!(content.text(), "/foo/bar");
}
_ => panic!("Expected literal part"),
}
}
let inp = r#"./a/${"hello"}"#;
let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
if let ast::Expr::PathRel(p) = expr {
let path = Path::cast(p.syntax().clone()).unwrap();
let parts = path.parts();
assert_eq!(parts.len(), 2);
match &parts[0] {
InterpolPart::Literal(content) => {
assert_eq!(content.text(), "./a/");
}
_ => panic!("Expected literal part"),
}
match &parts[1] {
InterpolPart::Interpolation(_) => {} _ => panic!("Expected interpolation part"),
}
}
let inp = "<nixpkgs>";
let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
if let ast::Expr::PathSearch(p) = expr {
let path = Path::cast(p.syntax().clone()).unwrap();
let parts = path.parts();
assert_eq!(parts.len(), 1);
match &parts[0] {
InterpolPart::Literal(content) => {
assert_eq!(content.text(), "<nixpkgs>");
}
_ => panic!("Expected literal part"),
}
}
}
#[test]
fn direct_method_usage() {
let inp = "/foo/bar";
let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
if let ast::Expr::PathAbs(p) = expr {
let parts = p.parts();
assert_eq!(parts.len(), 1);
match &parts[0] {
InterpolPart::Literal(content) => {
assert_eq!(content.text(), "/foo/bar");
}
_ => panic!("Expected literal part"),
}
}
let inp = "<nixpkgs>";
let expr = Root::parse(inp).ok().unwrap().expr().unwrap();
if let ast::Expr::PathSearch(p) = expr {
let content = p.content().expect("Expected content");
assert_eq!(content.text(), "<nixpkgs>");
}
}
}