11use std:: collections:: BTreeMap ;
22use std:: error:: Error as StdError ;
3+ use std:: env;
34use std:: fmt;
4- use std:: fs:: File ;
5+ use std:: fs:: { self , File } ;
56use std:: io:: Read ;
67use std:: path:: Path ;
78use std:: u16;
@@ -37,6 +38,7 @@ pub enum Error {
3738 InvalidEnvironmentTable ,
3839 FileOpenError ,
3940 FileReadError ,
41+ DirectoryCreateError ,
4042}
4143
4244impl fmt:: Display for Error {
@@ -63,10 +65,43 @@ impl StdError for Error {
6365 Error :: InvalidEnvironmentTable => "'env' table is invalid, check configuration" ,
6466 Error :: FileOpenError => "could not open config file" ,
6567 Error :: FileReadError => "could not read config file into string" ,
68+ Error :: DirectoryCreateError => "could not create default directory" ,
6669 }
6770 }
6871}
6972
73+ // See http://standards.freedesktop.org/basedir-spec/basedir-spec-latest.html
74+ fn get_xdg_data_home ( ) -> Option < String > {
75+ let empty_string = String :: from ( "" ) ;
76+
77+ let home_dir = match env:: var ( "HOME" ) {
78+ Ok ( dir) => dir,
79+ Err ( _) => return None ,
80+ } ;
81+
82+ Some ( match env:: var ( "XDG_DATA_HOME" ) {
83+ Ok ( ref dir) if * dir == empty_string => home_dir,
84+ Err ( _) => format ! ( "{}/.local/share" , home_dir) ,
85+ Ok ( dir) => dir,
86+ } )
87+ }
88+
89+ fn get_default_log_dir ( ) -> Option < String > {
90+ let xdg_data_home = match get_xdg_data_home ( ) {
91+ None => return None ,
92+ Some ( dir) => dir,
93+ } ;
94+ Some ( format ! ( "{}/hookshot/logs" , xdg_data_home) )
95+ }
96+
97+ fn get_default_checkout_dir ( ) -> Option < String > {
98+ let xdg_data_home = match get_xdg_data_home ( ) {
99+ None => return None ,
100+ Some ( dir) => dir,
101+ } ;
102+ Some ( format ! ( "{}/hookshot/checkouts" , xdg_data_home) )
103+ }
104+
70105impl ServerConfig {
71106 pub fn from_file ( config_path : & Path ) -> Result < ServerConfig , Error > {
72107 let mut file = match File :: open ( & config_path) {
@@ -81,6 +116,10 @@ impl ServerConfig {
81116 }
82117
83118 pub fn from ( string : & str ) -> Result < ServerConfig , Error > {
119+ let default_port = 1469 ;
120+ let default_checkout_dir = get_default_checkout_dir ( ) ;
121+ let default_log_dir = get_default_log_dir ( ) ;
122+
84123 let root = match toml:: Parser :: new ( string) . parse ( ) {
85124 Some ( value) => value,
86125 None => return Err ( Error :: ParseError ) ,
@@ -96,20 +135,48 @@ impl ServerConfig {
96135 } ;
97136 let u16_max = u16:: max_value ( ) as i64 ;
98137 let port = match config. lookup ( "port" ) {
99- None => return Err ( Error :: MissingPort ) ,
138+ None => default_port ,
100139 Some ( & Value :: Integer ( port) ) if port < u16_max => port as u16 ,
101140 _ => return Err ( Error :: InvalidPort ) ,
102141 } ;
142+
103143 let checkout_root = match lookup_as_string ( config, "checkout_root" ) {
104- LookupResult :: Missing => return Err ( Error :: MissingCheckoutRoot ) ,
144+ LookupResult :: Missing => {
145+ let checkout_root_string = match default_checkout_dir {
146+ None => return Err ( Error :: MissingCheckoutRoot ) ,
147+ Some ( dir) => dir,
148+ } ;
149+ let checkout_root = Path :: new ( & checkout_root_string) ;
150+ if let Err ( _) = fs:: create_dir_all ( & checkout_root) {
151+ return Err ( Error :: DirectoryCreateError ) ;
152+ }
153+ match VerifiedPath :: directory ( None , checkout_root) {
154+ Ok ( v) => v,
155+ Err ( _) => return Err ( Error :: InvalidCheckoutRoot ) ,
156+ }
157+ }
105158 LookupResult :: WrongType => return Err ( Error :: InvalidCheckoutRoot ) ,
106159 LookupResult :: Value ( v) => match VerifiedPath :: directory ( None , Path :: new ( v) ) {
107160 Ok ( v) => v,
108161 Err ( _) => return Err ( Error :: InvalidCheckoutRoot ) ,
109162 } ,
110163 } ;
164+
111165 let log_root = match lookup_as_string ( config, "log_root" ) {
112- LookupResult :: Missing => return Err ( Error :: MissingLogRoot ) ,
166+ LookupResult :: Missing => {
167+ let log_root_string = match default_log_dir {
168+ None => return Err ( Error :: MissingLogRoot ) ,
169+ Some ( dir) => dir,
170+ } ;
171+ let log_root = Path :: new ( & log_root_string) ;
172+ if let Err ( _) = fs:: create_dir_all ( & log_root) {
173+ return Err ( Error :: DirectoryCreateError ) ;
174+ }
175+ match VerifiedPath :: directory ( None , log_root) {
176+ Ok ( v) => v,
177+ Err ( _) => return Err ( Error :: InvalidLogRoot ) ,
178+ }
179+ }
113180 LookupResult :: WrongType => return Err ( Error :: InvalidLogRoot ) ,
114181 LookupResult :: Value ( v) => match VerifiedPath :: directory ( None , Path :: new ( v) ) {
115182 Ok ( v) => v,
@@ -204,6 +271,7 @@ fn lookup_as_string<'a>(obj: &'a toml::Value, key: &'static str) -> LookupResult
204271mod tests {
205272 use super :: * ;
206273 use std:: path:: Path ;
274+ use std:: env;
207275
208276 macro_rules! expect_error {
209277 ( $i: ident, $error: path ) => { {
@@ -269,14 +337,16 @@ mod tests {
269337
270338 #[ test]
271339 fn test_invalid_config_missing_checkout_root ( ) {
340+ env:: set_var ( "XDG_DATA_HOME" , "/tmp" ) ;
272341 let toml = r#"
273342 [config]
274343 secret = "it's a secret to everyone"
275344 port = 5712
276345 hostname = "127.0.0.1"
277346 log_root = "/tmp"
278347 "# ;
279- expect_error ! ( toml, Error :: MissingCheckoutRoot ) ;
348+ let config = ServerConfig :: from ( & toml) . unwrap ( ) ;
349+ assert_eq ! ( config. checkout_root. path( ) , Path :: new( "/tmp/hookshot/checkouts" ) ) ;
280350 }
281351
282352 #[ test]
@@ -305,15 +375,17 @@ mod tests {
305375 }
306376
307377 #[ test]
308- fn test_invalid_config_missing_log_root ( ) {
378+ fn test_config_default_log_root ( ) {
379+ env:: set_var ( "XDG_DATA_HOME" , "/tmp" ) ;
309380 let toml = r#"
310381 [config]
311382 port = 5712
312383 hostname = "127.0.0.1"
313384 secret = "shh"
314385 checkout_root = "/tmp"
315386 "# ;
316- expect_error ! ( toml, Error :: MissingLogRoot ) ;
387+ let config = ServerConfig :: from ( & toml) . unwrap ( ) ;
388+ assert_eq ! ( config. log_root. path( ) , Path :: new( "/tmp/hookshot/logs" ) ) ;
317389 }
318390
319391
@@ -343,6 +415,19 @@ mod tests {
343415 expect_error ! ( toml, Error :: InvalidPort ) ;
344416 }
345417
418+ #[ test]
419+ fn test_config_default_port ( ) {
420+ let toml = r#"
421+ [config]
422+ secret = "it's a secret to everyone"
423+ hostname = "127.0.0.1"
424+ checkout_root = "/tmp"
425+ log_root = "/tmp"
426+ "# ;
427+ let config = ServerConfig :: from ( & toml) . unwrap ( ) ;
428+ assert_eq ! ( config. port, 1469 ) ;
429+ }
430+
346431 #[ test]
347432 fn test_environments ( ) {
348433 let toml = r#"
0 commit comments