11//! Resolution of one project-relative path to a real, readable project file.
22
3- use std:: ffi:: OsStr ;
4- use std:: os:: unix:: ffi:: OsStrExt ;
53use std:: path:: { Path , PathBuf } ;
64
7- use crate :: project_tree:: constants:: MISSING_FILE_ERRNOS ;
5+ use crate :: project_tree:: _helpers:: relative_paths:: { pure_parts, pure_relative_to} ;
6+ use crate :: project_tree:: constants:: MISSING_FILE_ERRORS ;
87use crate :: project_tree:: errors:: ProjectTreeFileError ;
98use crate :: project_tree:: main:: tracked_text_path:: tracked_text_path;
9+ use crate :: project_tree:: models:: PureParts ;
1010
11- /// `root` joined with the raw path components.
12- pub ( crate ) fn joined ( root : & Path , parts : & [ & [ u8 ] ] ) -> PathBuf {
11+ /// `root / path` for a relative POSIX path: the root joined with each component.
12+ #[ cfg( unix) ]
13+ pub ( crate ) fn joined ( root : & Path , parts : & PureParts ) -> PathBuf {
1314 let mut path: PathBuf = root. to_path_buf ( ) ;
14- path. extend ( parts. iter ( ) . map ( |part| OsStr :: from_bytes ( part ) ) ) ;
15+ path. extend ( & parts. tail ) ;
1516 path
1617}
1718
19+ /// `root / path` as `ntpath.join` builds it, keeping or replacing the root's drive.
20+ #[ cfg( not( unix) ) ]
21+ pub ( crate ) fn joined ( root : & Path , parts : & PureParts ) -> PathBuf {
22+ use std:: ffi:: OsString ;
23+ use std:: path:: MAIN_SEPARATOR_STR ;
24+
25+ use crate :: project_tree:: _helpers:: relative_paths:: same_part;
26+
27+ let anchor: & str = & parts. anchor . to_string_lossy ( ) ;
28+ let drive: & str = anchor. trim_end_matches ( MAIN_SEPARATOR_STR ) ;
29+ let root_anchor: OsString = pure_parts ( root) . anchor ;
30+ let root_anchor: & str = & root_anchor. to_string_lossy ( ) ;
31+ let root_drive: & str = root_anchor. trim_end_matches ( MAIN_SEPARATOR_STR ) ;
32+ let mut path: PathBuf = if drive. len ( ) < anchor. len ( ) {
33+ let drive: & str = if drive. is_empty ( ) { root_drive } else { drive } ;
34+ PathBuf :: from ( format ! ( "{drive}{MAIN_SEPARATOR_STR}" ) )
35+ } else if drive. is_empty ( ) || same_part ( drive. as_ref ( ) , root_drive. as_ref ( ) ) {
36+ root. components ( ) . collect ( )
37+ } else {
38+ PathBuf :: from ( drive)
39+ } ;
40+ path. extend ( & parts. tail ) ;
41+ path
42+ }
43+
44+ /// `path.resolve()` of an existing path, spelled without Windows' verbatim `\\?\` prefix.
45+ #[ cfg( unix) ]
46+ pub ( crate ) fn real_path ( path : & Path ) -> std:: io:: Result < PathBuf > {
47+ std:: fs:: canonicalize ( path)
48+ }
49+
50+ /// `path.resolve()` of an existing path, spelled without Windows' verbatim `\\?\` prefix.
51+ #[ cfg( not( unix) ) ]
52+ pub ( crate ) fn real_path ( path : & Path ) -> std:: io:: Result < PathBuf > {
53+ use std:: ffi:: OsString ;
54+ use std:: path:: { Component , Prefix } ;
55+
56+ let real: PathBuf = std:: fs:: canonicalize ( path) ?;
57+ let mut components = real. components ( ) ;
58+ let Some ( Component :: Prefix ( prefix) ) = components. next ( ) else {
59+ return Ok ( real) ;
60+ } ;
61+ let anchor: OsString = match prefix. kind ( ) {
62+ Prefix :: VerbatimDisk ( drive) => OsString :: from ( format ! ( "{}:" , char :: from( drive) ) ) ,
63+ Prefix :: VerbatimUNC ( server, share) => {
64+ let mut anchor: OsString = OsString :: from ( r"\\" ) ;
65+ anchor. push ( server) ;
66+ anchor. push ( r"\" ) ;
67+ anchor. push ( share) ;
68+ anchor
69+ }
70+ _ => return Ok ( real) ,
71+ } ;
72+ let mut plain: PathBuf = PathBuf :: from ( anchor) ;
73+ plain. extend ( components) ;
74+ Ok ( plain)
75+ }
76+
77+ /// `path.is_relative_to(root)`, case-folded on Windows as `pathlib` compares there.
78+ pub ( crate ) fn inside ( path : & Path , root : & Path ) -> bool {
79+ if cfg ! ( unix) {
80+ path. starts_with ( root)
81+ } else {
82+ pure_relative_to ( & pure_parts ( path) , & pure_parts ( root) ) . is_some ( )
83+ }
84+ }
85+
1886/// `Path.is_file()`: false for the failures `pathlib` ignores, the failure otherwise.
1987pub ( crate ) fn points_to_file ( path : & Path ) -> Result < bool , ProjectTreeFileError > {
2088 match std:: fs:: metadata ( path) {
2189 Ok ( metadata) => Ok ( metadata. is_file ( ) ) ,
2290 Err ( error) => match error. raw_os_error ( ) {
23- Some ( errno) if !MISSING_FILE_ERRNOS . contains ( & errno) => {
91+ Some ( errno) if !MISSING_FILE_ERRORS . contains ( & errno) => {
2492 Err ( ProjectTreeFileError :: Unreadable {
2593 path : path. to_path_buf ( ) ,
2694 errno,
@@ -32,16 +100,20 @@ pub(crate) fn points_to_file(path: &Path) -> Result<bool, ProjectTreeFileError>
32100}
33101
34102/// A regular, non-link file under the project's resolved parent directory of the path.
35- pub ( crate ) fn regular_file ( root : & Path , parts : & [ & [ u8 ] ] ) -> Option < PathBuf > {
36- let ( name, parent) = parts. split_last ( ) ?;
37- let directory: PathBuf = match std:: fs:: canonicalize ( joined ( root, parent) ) {
103+ pub ( crate ) fn regular_file ( root : & Path , parts : & PureParts ) -> Option < PathBuf > {
104+ let ( name, parent) = parts. tail . split_last ( ) ?;
105+ let parent: PureParts = PureParts {
106+ anchor : parts. anchor . clone ( ) ,
107+ tail : parent. to_vec ( ) ,
108+ } ;
109+ let directory: PathBuf = match real_path ( & joined ( root, & parent) ) {
38110 Ok ( directory) => directory,
39111 Err ( _unresolved) => return None ,
40112 } ;
41- if !directory . starts_with ( root) {
113+ if !inside ( & directory , root) {
42114 return None ;
43115 }
44- let candidate: PathBuf = directory. join ( OsStr :: from_bytes ( name) ) ;
116+ let candidate: PathBuf = directory. join ( name) ;
45117 match std:: fs:: symlink_metadata ( & candidate) {
46118 Ok ( metadata) => metadata. file_type ( ) . is_file ( ) . then_some ( candidate) ,
47119 Err ( _unreadable) => None ,
0 commit comments