11//! Handles the CLI's configuration files and credentials.
22
33use anyhow:: { Context , Error } ;
4+ use serde:: { Deserialize , Serialize } ;
45use std:: env;
56use std:: fs:: { self , File } ;
67use std:: io:: Write ;
78use std:: path:: { Path , PathBuf } ;
89use toml:: { value:: Table , Value } ;
910
11+ #[ derive( Debug , Serialize , Deserialize ) ]
12+ #[ serde( rename_all = "kebab-case" ) ]
13+ pub struct Config {
14+ pub projects_dir : PathBuf ,
15+ #[ serde( flatten) ]
16+ pub table : Table ,
17+ }
18+
19+ impl Config {
20+ pub fn get ( & self , key : & str ) -> ConfigValue {
21+ match key {
22+ "projects-dir" => ConfigValue :: Path ( & self . projects_dir ) ,
23+ _ => ConfigValue :: Value ( self . table . get ( key) ) ,
24+ }
25+ }
26+
27+ pub fn insert ( & mut self , key : String , value : Value ) -> Result < ( ) , anyhow:: Error > {
28+ match key. as_str ( ) {
29+ "projects-dir" => {
30+ if let Value :: String ( value) = value {
31+ let path = PathBuf :: from ( value) ;
32+ self . set_projects_dir ( path) ?;
33+ } else {
34+ anyhow:: bail!( "The value for projects-dir must be a string." )
35+ }
36+ }
37+ _ => {
38+ self . table . insert ( key, value) ;
39+ }
40+ }
41+ Ok ( ( ) )
42+ }
43+
44+ pub fn remove ( & mut self , key : & str ) -> Result < Option < Value > , anyhow:: Error > {
45+ match key {
46+ "projects-dir" => anyhow:: bail!( "projects-dir must always be defined" ) ,
47+ _ => Ok ( self . table . remove ( key) ) ,
48+ }
49+ }
50+
51+ pub fn set_projects_dir ( & mut self , mut target : PathBuf ) -> Result < PathBuf , anyhow:: Error > {
52+ // check if the directory is empty or not
53+ if fs:: read_dir ( & target)
54+ . with_context ( || format ! ( "Failed to read directory at {}" , target. display( ) ) ) ?
55+ . next ( )
56+ . is_some ( )
57+ {
58+ anyhow:: bail!( "Cannot set projects-dir to a non-empty directory." ) ;
59+ }
60+ std:: mem:: swap ( & mut self . projects_dir , & mut target) ;
61+ Ok ( target)
62+ }
63+ }
64+
65+ #[ derive( Debug , Serialize ) ]
66+ #[ serde( untagged) ]
67+ pub enum ConfigValue < ' a > {
68+ Value ( Option < & ' a Value > ) ,
69+ Path ( & ' a Path ) ,
70+ }
71+
1072// base directory for a given plugin's settings files
1173fn get_tmc_dir ( client_name : & str ) -> Result < PathBuf , Error > {
1274 let config_dir = match env:: var ( "TMC_LANGS_CONFIG_DIR" ) {
@@ -35,7 +97,7 @@ fn get_client_stub(client: &str) -> &str {
3597}
3698
3799// initializes the default configuration file at the given path
38- fn init_config_at ( client_name : & str , path : & Path ) -> Result < Table , Error > {
100+ fn init_config_at ( client_name : & str , path : & Path ) -> Result < Config , Error > {
39101 let mut file = File :: create ( & path)
40102 . with_context ( || format ! ( "Failed to create new config file at {}" , path. display( ) ) ) ?;
41103
@@ -50,21 +112,20 @@ fn init_config_at(client_name: &str, path: &Path) -> Result<Table, Error> {
50112 )
51113 } ) ?;
52114
53- let mut config = Table :: new ( ) ;
54- config. insert (
55- "projects-folder" . to_string ( ) ,
56- Value :: String ( default_project_dir. to_string_lossy ( ) . into_owned ( ) ) ,
57- ) ;
115+ let config = Config {
116+ projects_dir : default_project_dir,
117+ table : Table :: new ( ) ,
118+ } ;
58119
59120 let toml = toml:: to_string_pretty ( & config) . context ( "Failed to serialize config" ) ?;
60121 file. write_all ( toml. as_bytes ( ) )
61122 . with_context ( || format ! ( "Failed to write default config to {}" , path. display( ) ) ) ?;
62123 Ok ( config)
63124}
64125
65- pub fn load_config ( client_name : & str ) -> Result < Table , Error > {
126+ pub fn load_config ( client_name : & str ) -> Result < Config , Error > {
66127 let path = get_config_path ( client_name) ?;
67- match fs:: read ( & path) {
128+ let config = match fs:: read ( & path) {
68129 Ok ( bytes) => match toml:: from_slice ( & bytes) {
69130 Ok ( config) => Ok ( config) ,
70131 Err ( _) => {
@@ -76,10 +137,19 @@ pub fn load_config(client_name: &str) -> Result<Table, Error> {
76137 }
77138 } ,
78139 Err ( _) => init_config_at ( client_name, & path) ,
140+ } ?;
141+ if !config. projects_dir . exists ( ) {
142+ fs:: create_dir_all ( & config. projects_dir ) . with_context ( || {
143+ format ! (
144+ "Failed to create projects-dir at {}" ,
145+ config. projects_dir. display( )
146+ )
147+ } ) ?;
79148 }
149+ Ok ( config)
80150}
81151
82- pub fn save_config ( client_name : & str , config : Table ) -> Result < ( ) , Error > {
152+ pub fn save_config ( client_name : & str , config : Config ) -> Result < ( ) , Error > {
83153 let path = get_config_path ( client_name) ?;
84154 let toml = toml:: to_string_pretty ( & config) . context ( "Failed to serialize HashMap" ) ?;
85155 fs:: write ( & path, toml. as_bytes ( ) )
0 commit comments