205 lines
6.0 KiB
Rust
205 lines
6.0 KiB
Rust
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()
|
|
}
|
|
|
|
// Direct methods for interpolatable path types
|
|
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)
|
|
}
|
|
}
|
|
|
|
// Direct methods for search path
|
|
impl PathSearch {
|
|
/// Get the content of a search path
|
|
pub fn content(&self) -> Option<PathContent> {
|
|
self.syntax()
|
|
.children_with_tokens()
|
|
.filter_map(|child| child.into_token().and_then(PathContent::cast))
|
|
.next()
|
|
}
|
|
}
|
|
|
|
/// Extension methods for the Path enum
|
|
impl Path {
|
|
/// Get parts from any path type in a unified way
|
|
pub fn parts(&self) -> Vec<InterpolPart<PathContent>> {
|
|
match self {
|
|
// For interpolatable paths, get their parts
|
|
Path::PathAbs(p) => p.parts(),
|
|
Path::PathRel(p) => p.parts(),
|
|
Path::PathHome(p) => p.parts(),
|
|
// For search paths, return a single literal component if content exists
|
|
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() {
|
|
// Absolute path
|
|
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());
|
|
}
|
|
|
|
// Search path
|
|
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() {
|
|
// Test parts with absolute path
|
|
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"),
|
|
}
|
|
}
|
|
|
|
// Test parts with interpolated path
|
|
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(_) => {} // Success
|
|
_ => panic!("Expected interpolation part"),
|
|
}
|
|
}
|
|
|
|
// Test parts with search path
|
|
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() {
|
|
// Test direct parts() method on PathAbs
|
|
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"),
|
|
}
|
|
}
|
|
|
|
// Test direct content() method on PathSearch
|
|
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>");
|
|
}
|
|
}
|
|
}
|