@@ -3,6 +3,7 @@ use anyhow::Context;
33use serde:: { Deserialize , Serialize } ;
44use std:: fmt;
55use std:: fs;
6+ use std:: io:: { self , IsTerminal , Write } ;
67use std:: net:: { IpAddr , SocketAddr } ;
78use std:: path:: Path ;
89use std:: str:: FromStr ;
@@ -372,6 +373,41 @@ impl Config {
372373 . with_context ( || format ! ( "Failed to write config file: {}" , path. display( ) ) ) ?;
373374 Ok ( ( ) )
374375 }
376+
377+ /// Load configuration from `path`, offering to create a default config
378+ /// there on first launch.
379+ ///
380+ /// If the file is missing and stdin is an interactive terminal, prompts
381+ /// the user to write out `Config::default()` at `path` (creating parent
382+ /// directories as needed) before loading. Non-interactive sessions and
383+ /// declined prompts fall through to the plain `load` error.
384+ pub fn load_or_prompt_default < P : AsRef < Path > > ( path : P ) -> Result < Self > {
385+ let path = path. as_ref ( ) ;
386+
387+ if !path. exists ( ) && io:: stdin ( ) . is_terminal ( ) {
388+ print ! (
389+ "No config file found at {}. Create a default one there now? [Y/n] " ,
390+ path. display( )
391+ ) ;
392+ io:: stdout ( ) . flush ( ) . ok ( ) ;
393+
394+ let mut answer = String :: new ( ) ;
395+ io:: stdin ( ) . read_line ( & mut answer) ?;
396+ let answer = answer. trim ( ) . to_lowercase ( ) ;
397+
398+ if answer. is_empty ( ) || answer == "y" || answer == "yes" {
399+ if let Some ( parent) = path. parent ( ) {
400+ fs:: create_dir_all ( parent) . with_context ( || {
401+ format ! ( "Failed to create config directory: {}" , parent. display( ) )
402+ } ) ?;
403+ }
404+ Config :: default ( ) . save ( path) ?;
405+ println ! ( "Wrote default configuration to {}" , path. display( ) ) ;
406+ }
407+ }
408+
409+ Self :: load ( path)
410+ }
375411}
376412
377413impl Default for Config {
0 commit comments