11//! A Harmont workspace and its resolved configuration.
22//!
3- //! A project is a directory tree rooted at a directory containing `.hm/`. A
4- //! [`ProjectContext`] wraps that root, exposes its `.hm/` paths, and holds the
5- //! [`ResolvedProjectConfig`] merged from the user and project layers at
6- //! construction.
3+ //! A project is a directory tree rooted at a directory containing `.hm/`.
74
85use std:: path:: { Path , PathBuf } ;
96
@@ -32,7 +29,7 @@ impl ProjectContext {
3229 start : & Path ,
3330 user : Option < & UserConfig > ,
3431 ) -> Result < Option < Self > , ConfigLoadingError > {
35- match find_project_root ( start) {
32+ match Self :: find_root ( start) {
3633 Some ( root) => Ok ( Some ( Self :: at ( root, user) . await ?) ) ,
3734 None => Ok ( None ) ,
3835 }
@@ -45,7 +42,7 @@ impl ProjectContext {
4542 /// [`ConfigLoadingError`] if the project config file is present but
4643 /// unreadable or malformed.
4744 pub async fn at ( path : PathBuf , user : Option < & UserConfig > ) -> Result < Self , ConfigLoadingError > {
48- let project = load_project_config ( & config_path_for ( & path) ) . await ?;
45+ let project = Self :: load_config ( & Self :: config_file ( & path) ) . await ?;
4946 let user = user. cloned ( ) . unwrap_or_default ( ) ;
5047 let config = ResolvedProjectConfig :: from_user_project ( & user, & project) ;
5148 Ok ( Self { path, config } )
@@ -70,44 +67,41 @@ impl ProjectContext {
7067 /// The project config file path (`.hm/config.toml`).
7168 #[ must_use]
7269 pub fn config_path ( & self ) -> PathBuf {
73- config_path_for ( & self . path )
70+ Self :: config_file ( & self . path )
7471 }
7572
7673 /// The resolved configuration for this workspace.
7774 #[ must_use]
7875 pub const fn config ( & self ) -> & ResolvedProjectConfig {
7976 & self . config
8077 }
81- }
8278
83- /// The project config file path for a workspace root.
84- fn config_path_for ( root : & Path ) -> PathBuf {
85- root. join ( ".hm" ) . join ( "config.toml" )
86- }
79+ /// The project config file path for a workspace root.
80+ fn config_file ( root : & Path ) -> PathBuf {
81+ root. join ( ".hm" ) . join ( "config.toml" )
82+ }
8783
88- /// Walk up from `start` to the first directory containing `.hm/`.
89- ///
90- /// Returns the directory *containing* `.hm/`, or `None` at the filesystem root.
91- #[ must_use]
92- pub fn find_project_root ( start : & Path ) -> Option < PathBuf > {
93- let mut current = start;
94- loop {
95- if current. join ( ".hm" ) . is_dir ( ) {
96- return Some ( current. to_path_buf ( ) ) ;
84+ /// Walk up from `start` to the first directory containing `.hm/`.
85+ ///
86+ /// Returns the directory *containing* `.hm/`, or `None` at the filesystem
87+ /// root.
88+ fn find_root ( start : & Path ) -> Option < PathBuf > {
89+ let mut current = start;
90+ loop {
91+ if current. join ( ".hm" ) . is_dir ( ) {
92+ return Some ( current. to_path_buf ( ) ) ;
93+ }
94+ current = current. parent ( ) ?;
9795 }
98- current = current. parent ( ) ?;
9996 }
100- }
10197
102- /// Read a project config, treating a missing file as defaults.
103- ///
104- /// # Errors
105- /// [`ConfigLoadingError`] if the file is present but unreadable or malformed.
106- async fn load_project_config ( path : & Path ) -> Result < ProjectConfig , ConfigLoadingError > {
107- match tokio:: fs:: read_to_string ( path) . await {
108- Ok ( contents) => Ok ( toml:: from_str ( & contents) ?) ,
109- Err ( e) if e. kind ( ) == std:: io:: ErrorKind :: NotFound => Ok ( ProjectConfig :: default ( ) ) ,
110- Err ( e) => Err ( e. into ( ) ) ,
98+ /// Read a project config, treating a missing file as defaults.
99+ async fn load_config ( path : & Path ) -> Result < ProjectConfig , ConfigLoadingError > {
100+ match tokio:: fs:: read_to_string ( path) . await {
101+ Ok ( contents) => Ok ( toml:: from_str ( & contents) ?) ,
102+ Err ( e) if e. kind ( ) == std:: io:: ErrorKind :: NotFound => Ok ( ProjectConfig :: default ( ) ) ,
103+ Err ( e) => Err ( e. into ( ) ) ,
104+ }
111105 }
112106}
113107
@@ -123,13 +117,16 @@ mod tests {
123117 std:: fs:: create_dir ( tmp. path ( ) . join ( ".hm" ) ) . unwrap ( ) ;
124118 let nested = tmp. path ( ) . join ( "src" ) . join ( "deep" ) ;
125119 std:: fs:: create_dir_all ( & nested) . unwrap ( ) ;
126- assert_eq ! ( find_project_root( & nested) , Some ( tmp. path( ) . to_path_buf( ) ) ) ;
120+ assert_eq ! (
121+ ProjectContext :: find_root( & nested) ,
122+ Some ( tmp. path( ) . to_path_buf( ) )
123+ ) ;
127124 }
128125
129126 #[ rstest]
130127 fn find_project_root_none_when_absent ( ) {
131128 let tmp = tempfile:: tempdir ( ) . unwrap ( ) ;
132- assert_eq ! ( find_project_root ( tmp. path( ) ) , None ) ;
129+ assert_eq ! ( ProjectContext :: find_root ( tmp. path( ) ) , None ) ;
133130 }
134131
135132 #[ rstest]
0 commit comments