@@ -4,6 +4,7 @@ use std::{collections::HashMap, env, path::PathBuf};
44
55use color_eyre:: Result ;
66use directories:: ProjectDirs ;
7+ use eyre:: eyre;
78use lazy_static:: lazy_static;
89use serde:: { Deserialize , Serialize } ;
910
@@ -27,10 +28,12 @@ pub struct CscsConfig {
2728 #[ serde( default ) ]
2829 pub current_system : String ,
2930 #[ serde( default ) ]
30- pub name : Option < String > ,
31- #[ serde( default ) ]
3231 pub sbatch_script_template : String ,
3332 #[ serde( default ) ]
33+ pub workdir : Option < String > ,
34+ #[ serde( default ) ]
35+ pub env : HashMap < String , String > ,
36+ #[ serde( default ) ]
3437 pub image : String ,
3538 #[ serde( default ) ]
3639 pub edf_file_template : String ,
@@ -43,6 +46,8 @@ pub struct CscsConfig {
4346
4447#[ derive( Clone , Debug , Default , Serialize , Deserialize ) ]
4548pub struct Config {
49+ #[ serde( default ) ]
50+ pub name : Option < String > ,
4651 #[ serde( default , flatten) ]
4752 pub config : AppConfig ,
4853 #[ serde( default ) ]
@@ -59,71 +64,123 @@ lazy_static! {
5964 env:: var( format!( "{}_CONFIG" , PROJECT_NAME . clone( ) ) )
6065 . ok( )
6166 . map( PathBuf :: from) ;
67+ pub static ref CONFIG_FILE_NAME : String = format!( "{}.toml" , PROJECT_NAME . clone( ) ) ;
68+ pub static ref CONFIG_FORMAT : config:: FileFormat = config:: FileFormat :: Toml ;
6269}
6370
6471impl Config {
65- pub fn new ( ) -> Result < Self , config:: ConfigError > {
66- let data_dir = get_data_dir ( ) ;
67- let config_dir = get_config_dir ( ) ;
68- let mut builder = config:: Config :: builder ( )
69- . add_source ( config:: File :: from_str (
70- DEFAULT_CONFIG_TOML ,
71- config:: FileFormat :: Toml ,
72- ) )
73- . set_default ( "data_dir" , data_dir. to_str ( ) . unwrap ( ) ) ?
74- . set_default ( "config_dir" , config_dir. to_str ( ) . unwrap ( ) ) ?;
75-
76- let config_files = [
77- (
78- format ! ( "{}.toml" , PROJECT_NAME . to_lowercase( ) ) ,
79- config:: FileFormat :: Toml ,
80- ) ,
81- (
82- format ! ( "{}.json5" , PROJECT_NAME . to_lowercase( ) ) ,
83- config:: FileFormat :: Json5 ,
84- ) ,
85- (
86- format ! ( "{}.json" , PROJECT_NAME . to_lowercase( ) ) ,
87- config:: FileFormat :: Json ,
88- ) ,
89- (
90- format ! ( "{}.yaml" , PROJECT_NAME . to_lowercase( ) ) ,
91- config:: FileFormat :: Yaml ,
92- ) ,
93- (
94- format ! ( "{}.ini" , PROJECT_NAME . to_lowercase( ) ) ,
95- config:: FileFormat :: Ini ,
96- ) ,
97- ] ;
98- for ( file, format) in & config_files {
99- let source = config:: File :: from ( config_dir. join ( file) )
100- . format ( * format)
101- . required ( false ) ;
102- builder = builder. add_source ( source) ;
103- }
72+ pub fn new ( ) -> Result < Self > {
73+ let builder = default_config_builder ( ) ?;
74+ let builder = global_config_builder ( builder) ?;
75+ let builder = project_local_config_builder ( builder) ?;
10476
105- // find config override in current directory
106- let mut search_path = std:: env:: current_dir ( ) . expect ( "current directory does not exist" ) ;
107- loop {
108- for ( file, format) in & config_files {
109- if search_path. join ( file) . exists ( ) {
110- let source = config:: File :: from ( search_path. join ( file) )
111- . format ( * format)
112- . required ( false ) ;
113- builder = builder. add_source ( source) ;
114- break ;
115- }
116- }
117- if let Some ( p) = search_path. parent ( ) {
118- search_path = p. to_path_buf ( ) ;
119- } else {
120- break ;
121- }
122- }
77+ let cfg: Self = builder. build ( ) ?. try_deserialize ( ) ?;
78+ Ok ( cfg)
79+ }
80+ pub fn new_global ( ) -> Result < Self > {
81+ let builder = default_config_builder ( ) ?;
82+ let builder = global_config_builder ( builder) ?;
12383
12484 let cfg: Self = builder. build ( ) ?. try_deserialize ( ) ?;
12585 Ok ( cfg)
12686 }
87+
88+ pub fn write_local ( & self ) -> Result < ( ) > {
89+ match get_project_local_config_file ( ) {
90+ Some ( path) => {
91+ let content = toml:: to_string_pretty ( self ) ?;
92+ std:: fs:: write ( path, content) ?;
93+ Ok ( ( ) )
94+ }
95+ None => Err ( eyre ! (
96+ "No config file exists in current project. Consider creating one using '{} init" ,
97+ PROJECT_NAME . to_lowercase( ) . clone( )
98+ ) ) ,
99+ }
100+ }
101+
102+ pub fn write_global ( & self ) -> Result < ( ) > {
103+ let config_dir = get_config_dir ( ) ;
104+ let path = config_dir. join ( CONFIG_FILE_NAME . clone ( ) ) ;
105+ let content = toml:: to_string_pretty ( self ) ?;
106+ std:: fs:: write ( path, content) ?;
107+ Ok ( ( ) )
108+ }
109+
110+ pub fn create_config ( destination : Option < PathBuf > ) -> Result < ( ) > {
111+ let mut config = Config :: new ( ) ?;
112+ let project_dir = destination
113+ . unwrap_or ( std:: env:: current_dir ( ) . expect ( "current directory does not exist" ) ) ;
114+ if !project_dir. exists ( ) || !project_dir. is_dir ( ) {
115+ return Err ( eyre ! (
116+ "destination must exist and be a directory, got {}" ,
117+ project_dir. to_string_lossy( )
118+ ) ) ;
119+ }
120+
121+ let name = project_dir
122+ . file_name ( )
123+ . expect ( "could not get base name from destination" ) ;
124+ config. name = Some ( name. to_string_lossy ( ) . to_string ( ) ) ;
125+
126+ let config_path = project_dir. join ( CONFIG_FILE_NAME . clone ( ) ) ;
127+ std:: fs:: write ( config_path. clone ( ) , "" ) ?;
128+ let content = toml:: to_string_pretty ( & config) ?;
129+ std:: fs:: write ( config_path, content) ?;
130+ Ok ( ( ) )
131+ }
132+ }
133+
134+ pub fn default_config_builder ( ) -> Result < config:: ConfigBuilder < config:: builder:: DefaultState > > {
135+ let data_dir = get_data_dir ( ) ;
136+ let config_dir = get_config_dir ( ) ;
137+ let builder = config:: Config :: builder ( )
138+ . add_source ( config:: File :: from_str (
139+ DEFAULT_CONFIG_TOML ,
140+ config:: FileFormat :: Toml ,
141+ ) )
142+ . set_default ( "data_dir" , data_dir. to_str ( ) . unwrap ( ) ) ?
143+ . set_default ( "config_dir" , config_dir. to_str ( ) . unwrap ( ) ) ?;
144+ Ok ( builder)
145+ }
146+
147+ pub fn global_config_builder (
148+ builder : config:: ConfigBuilder < config:: builder:: DefaultState > ,
149+ ) -> Result < config:: ConfigBuilder < config:: builder:: DefaultState > > {
150+ let config_dir = get_config_dir ( ) ;
151+ let source = config:: File :: from ( config_dir. join ( CONFIG_FILE_NAME . clone ( ) ) )
152+ . format ( * CONFIG_FORMAT )
153+ . required ( false ) ;
154+ let builder = builder. add_source ( source) ;
155+ Ok ( builder)
156+ }
157+
158+ pub fn project_local_config_builder (
159+ builder : config:: ConfigBuilder < config:: builder:: DefaultState > ,
160+ ) -> Result < config:: ConfigBuilder < config:: builder:: DefaultState > > {
161+ if let Some ( config_path) = get_project_local_config_file ( ) {
162+ let source = config:: File :: from ( config_path)
163+ . format ( * CONFIG_FORMAT )
164+ . required ( false ) ;
165+ let builder = builder. add_source ( source) ;
166+ return Ok ( builder) ;
167+ }
168+ Ok ( builder)
169+ }
170+
171+ pub fn get_project_local_config_file ( ) -> Option < PathBuf > {
172+ let mut search_path = std:: env:: current_dir ( ) . expect ( "current directory does not exist" ) ;
173+ loop {
174+ if search_path. join ( CONFIG_FILE_NAME . clone ( ) ) . exists ( ) {
175+ return Some ( search_path. join ( CONFIG_FILE_NAME . clone ( ) ) ) ;
176+ }
177+ if let Some ( p) = search_path. parent ( ) {
178+ search_path = p. to_path_buf ( ) ;
179+ } else {
180+ break ;
181+ }
182+ }
183+ None
127184}
128185
129186pub fn get_data_dir ( ) -> PathBuf {
0 commit comments