@@ -12,8 +12,10 @@ use toml::value::Table;
12
12
use toml:: Value ;
13
13
14
14
use super :: id:: { NameTransform , OptionId } ;
15
- use super :: parse:: parse_string_list;
16
- use super :: { ListEdit , ListEditAction , OptionsSource } ;
15
+ use super :: parse:: {
16
+ parse_bool_list, parse_dict, parse_float_list, parse_int_list, parse_string_list, ParseError ,
17
+ } ;
18
+ use super :: { DictEdit , DictEditAction , ListEdit , ListEditAction , OptionsSource , Val } ;
17
19
18
20
type InterpolationMap = HashMap < String , String > ;
19
21
@@ -186,6 +188,34 @@ impl FromValue for f64 {
186
188
}
187
189
}
188
190
191
+ fn toml_value_to_val ( value : & Value ) -> Val {
192
+ match value {
193
+ Value :: String ( s) => Val :: String ( s. to_owned ( ) ) ,
194
+ Value :: Integer ( i) => Val :: Int ( * i) ,
195
+ Value :: Float ( f) => Val :: Float ( * f) ,
196
+ Value :: Boolean ( b) => Val :: Bool ( * b) ,
197
+ Value :: Datetime ( d) => Val :: String ( d. to_string ( ) ) ,
198
+ Value :: Array ( a) => Val :: List ( a. iter ( ) . map ( toml_value_to_val) . collect ( ) ) ,
199
+ Value :: Table ( t) => Val :: Dict (
200
+ t. iter ( )
201
+ . map ( |( k, v) | ( k. to_string ( ) , toml_value_to_val ( v) ) )
202
+ . collect ( ) ,
203
+ ) ,
204
+ }
205
+ }
206
+
207
+ // Helper function. Only call if you know that the arg is a Value::Table.
208
+ fn toml_table_to_dict ( table : & Value ) -> HashMap < String , Val > {
209
+ if !table. is_table ( ) {
210
+ panic ! ( "Expected a TOML table but received: {table}" ) ;
211
+ }
212
+ if let Val :: Dict ( hm) = toml_value_to_val ( table) {
213
+ hm
214
+ } else {
215
+ panic ! ( "toml_value_to_val() on a Value::Table must return a Val::Dict" ) ;
216
+ }
217
+ }
218
+
189
219
#[ derive( Clone ) ]
190
220
pub ( crate ) struct Config {
191
221
config : Value ,
@@ -305,6 +335,57 @@ impl Config {
305
335
. and_then ( |table| table. get ( Self :: option_name ( id) ) )
306
336
}
307
337
338
+ fn get_list < T : FromValue > (
339
+ & self ,
340
+ id : & OptionId ,
341
+ parse_list : fn ( & str ) -> Result < Vec < ListEdit < T > > , ParseError > ,
342
+ ) -> Result < Option < Vec < ListEdit < T > > > , String > {
343
+ if let Some ( table) = self . config . get ( id. scope ( ) ) {
344
+ let option_name = Self :: option_name ( id) ;
345
+ let mut list_edits = vec ! [ ] ;
346
+ if let Some ( value) = table. get ( & option_name) {
347
+ match value {
348
+ Value :: Table ( sub_table) => {
349
+ if sub_table. is_empty ( )
350
+ || !sub_table. keys ( ) . collect :: < HashSet < _ > > ( ) . is_subset (
351
+ & [ "add" . to_owned ( ) , "remove" . to_owned ( ) ]
352
+ . iter ( )
353
+ . collect :: < HashSet < _ > > ( ) ,
354
+ )
355
+ {
356
+ return Err ( format ! (
357
+ "Expected {option_name} to contain an 'add' element, a 'remove' element or both but found: {sub_table:?}"
358
+ ) ) ;
359
+ }
360
+ if let Some ( add) = sub_table. get ( "add" ) {
361
+ list_edits. push ( ListEdit {
362
+ action : ListEditAction :: Add ,
363
+ items : T :: extract_list ( & format ! ( "{option_name}.add" ) , add) ?,
364
+ } )
365
+ }
366
+ if let Some ( remove) = sub_table. get ( "remove" ) {
367
+ list_edits. push ( ListEdit {
368
+ action : ListEditAction :: Remove ,
369
+ items : T :: extract_list ( & format ! ( "{option_name}.remove" ) , remove) ?,
370
+ } )
371
+ }
372
+ }
373
+ Value :: String ( v) => {
374
+ list_edits. extend ( parse_list ( v) . map_err ( |e| e. render ( option_name) ) ?) ;
375
+ }
376
+ value => list_edits. push ( ListEdit {
377
+ action : ListEditAction :: Replace ,
378
+ items : T :: extract_list ( & option_name, value) ?,
379
+ } ) ,
380
+ }
381
+ }
382
+ if !list_edits. is_empty ( ) {
383
+ return Ok ( Some ( list_edits) ) ;
384
+ }
385
+ }
386
+ Ok ( None )
387
+ }
388
+
308
389
pub ( crate ) fn merge ( mut self , mut other : Config ) -> Config {
309
390
let mut map = mem:: take ( self . config . as_table_mut ( ) . unwrap ( ) ) ;
310
391
let mut other = mem:: take ( other. config . as_table_mut ( ) . unwrap ( ) ) ;
@@ -346,52 +427,51 @@ impl OptionsSource for Config {
346
427
f64:: from_config ( self , id)
347
428
}
348
429
430
+ fn get_bool_list ( & self , id : & OptionId ) -> Result < Option < Vec < ListEdit < bool > > > , String > {
431
+ self . get_list ( id, parse_bool_list)
432
+ }
433
+
434
+ fn get_int_list ( & self , id : & OptionId ) -> Result < Option < Vec < ListEdit < i64 > > > , String > {
435
+ self . get_list ( id, parse_int_list)
436
+ }
437
+
438
+ fn get_float_list ( & self , id : & OptionId ) -> Result < Option < Vec < ListEdit < f64 > > > , String > {
439
+ self . get_list ( id, parse_float_list)
440
+ }
441
+
349
442
fn get_string_list ( & self , id : & OptionId ) -> Result < Option < Vec < ListEdit < String > > > , String > {
443
+ self . get_list ( id, parse_string_list)
444
+ }
445
+
446
+ fn get_dict ( & self , id : & OptionId ) -> Result < Option < DictEdit > , String > {
350
447
if let Some ( table) = self . config . get ( id. scope ( ) ) {
351
448
let option_name = Self :: option_name ( id) ;
352
- let mut list_edits = vec ! [ ] ;
353
449
if let Some ( value) = table. get ( & option_name) {
354
450
match value {
355
451
Value :: Table ( sub_table) => {
356
- if sub_table. is_empty ( )
357
- || !sub_table. keys ( ) . collect :: < HashSet < _ > > ( ) . is_subset (
358
- & [ "add" . to_owned ( ) , "remove" . to_owned ( ) ]
359
- . iter ( )
360
- . collect :: < HashSet < _ > > ( ) ,
361
- )
362
- {
363
- return Err ( format ! (
364
- "Expected {option_name} to contain an 'add' element, a 'remove' element or both but found: {sub_table:?}"
365
- ) ) ;
366
- }
367
452
if let Some ( add) = sub_table. get ( "add" ) {
368
- list_edits. push ( ListEdit {
369
- action : ListEditAction :: Add ,
370
- items : String :: extract_list ( & format ! ( "{option_name}.add" ) , add) ?,
371
- } )
372
- }
373
- if let Some ( remove) = sub_table. get ( "remove" ) {
374
- list_edits. push ( ListEdit {
375
- action : ListEditAction :: Remove ,
376
- items : String :: extract_list (
377
- & format ! ( "{option_name}.remove" ) ,
378
- remove,
379
- ) ?,
380
- } )
453
+ if sub_table. len ( ) == 1 && add. is_table ( ) {
454
+ return Ok ( Some ( DictEdit {
455
+ action : DictEditAction :: Add ,
456
+ items : toml_table_to_dict ( add) ,
457
+ } ) ) ;
458
+ }
381
459
}
460
+ return Ok ( Some ( DictEdit {
461
+ action : DictEditAction :: Replace ,
462
+ items : toml_table_to_dict ( value) ,
463
+ } ) ) ;
382
464
}
383
465
Value :: String ( v) => {
384
- list_edits. extend ( parse_string_list ( v) . map_err ( |e| e. render ( option_name) ) ?) ;
466
+ return Ok ( Some ( parse_dict ( v) . map_err ( |e| e. render ( option_name) ) ?) ) ;
467
+ }
468
+ _ => {
469
+ return Err ( format ! (
470
+ "Expected {option_name} to be a toml table or Python dict, but given {value}."
471
+ ) ) ;
385
472
}
386
- value => list_edits. push ( ListEdit {
387
- action : ListEditAction :: Replace ,
388
- items : String :: extract_list ( & option_name, value) ?,
389
- } ) ,
390
473
}
391
474
}
392
- if !list_edits. is_empty ( ) {
393
- return Ok ( Some ( list_edits) ) ;
394
- }
395
475
}
396
476
Ok ( None )
397
477
}
0 commit comments