@@ -70,8 +70,21 @@ pub mod log;
70
70
pub mod tracing;
71
71
72
72
/// Logging flags to `#[command(flatten)]` into your CLI
73
- #[ derive( clap:: Args , Debug , Clone , Default ) ]
73
+ #[ derive( clap:: Args , Debug , Clone , Default , PartialEq , Eq ) ]
74
74
#[ command( about = None , long_about = None ) ]
75
+ #[ cfg_attr( feature = "serde" , derive( serde:: Serialize , serde:: Deserialize ) ) ]
76
+ #[ cfg_attr(
77
+ feature = "serde" ,
78
+ serde(
79
+ from = "VerbosityFilter" ,
80
+ into = "VerbosityFilter" ,
81
+ bound( serialize = "L: Clone" )
82
+ )
83
+ ) ]
84
+ #[ cfg_attr(
85
+ feature = "serde" ,
86
+ doc = r#"This type serializes to a string representation of the log level, e.g. `"Debug"`"#
87
+ ) ]
75
88
pub struct Verbosity < L : LogLevel = ErrorLevel > {
76
89
#[ arg(
77
90
long,
@@ -162,6 +175,21 @@ impl<L: LogLevel> fmt::Display for Verbosity<L> {
162
175
}
163
176
}
164
177
178
+ impl < L : LogLevel > From < Verbosity < L > > for VerbosityFilter {
179
+ fn from ( verbosity : Verbosity < L > ) -> Self {
180
+ verbosity. filter ( )
181
+ }
182
+ }
183
+
184
+ impl < L : LogLevel > From < VerbosityFilter > for Verbosity < L > {
185
+ fn from ( filter : VerbosityFilter ) -> Self {
186
+ let default = L :: default_filter ( ) ;
187
+ let verbose = filter. value ( ) . saturating_sub ( default. value ( ) ) ;
188
+ let quiet = default. value ( ) . saturating_sub ( filter. value ( ) ) ;
189
+ Verbosity :: new ( verbose, quiet)
190
+ }
191
+ }
192
+
165
193
/// Customize the default log-level and associated help
166
194
pub trait LogLevel {
167
195
/// Baseline level before applying `--verbose` and `--quiet`
@@ -192,6 +220,7 @@ pub trait LogLevel {
192
220
///
193
221
/// Used to calculate the log level and filter.
194
222
#[ derive( Debug , Clone , Copy , PartialEq , Eq ) ]
223
+ #[ cfg_attr( feature = "serde" , derive( serde:: Serialize , serde:: Deserialize ) ) ]
195
224
pub enum VerbosityFilter {
196
225
Off ,
197
226
Error ,
@@ -206,15 +235,7 @@ impl VerbosityFilter {
206
235
///
207
236
/// Negative values will decrease the verbosity, while positive values will increase it.
208
237
fn with_offset ( & self , offset : i16 ) -> VerbosityFilter {
209
- let value = match self {
210
- Self :: Off => 0_i16 ,
211
- Self :: Error => 1 ,
212
- Self :: Warn => 2 ,
213
- Self :: Info => 3 ,
214
- Self :: Debug => 4 ,
215
- Self :: Trace => 5 ,
216
- } ;
217
- match value. saturating_add ( offset) {
238
+ match i16:: from ( self . value ( ) ) . saturating_add ( offset) {
218
239
i16:: MIN ..=0 => Self :: Off ,
219
240
1 => Self :: Error ,
220
241
2 => Self :: Warn ,
@@ -223,6 +244,20 @@ impl VerbosityFilter {
223
244
5 ..=i16:: MAX => Self :: Trace ,
224
245
}
225
246
}
247
+
248
+ /// Get the numeric value of the filter.
249
+ ///
250
+ /// This is an internal representation of the filter level used only for conversion / offset.
251
+ fn value ( & self ) -> u8 {
252
+ match self {
253
+ Self :: Off => 0 ,
254
+ Self :: Error => 1 ,
255
+ Self :: Warn => 2 ,
256
+ Self :: Info => 3 ,
257
+ Self :: Debug => 4 ,
258
+ Self :: Trace => 5 ,
259
+ }
260
+ }
226
261
}
227
262
228
263
impl fmt:: Display for VerbosityFilter {
@@ -239,7 +274,7 @@ impl fmt::Display for VerbosityFilter {
239
274
}
240
275
241
276
/// Default to [`VerbosityFilter::Error`]
242
- #[ derive( Copy , Clone , Debug , Default ) ]
277
+ #[ derive( Copy , Clone , Debug , Default , PartialEq , Eq ) ]
243
278
pub struct ErrorLevel ;
244
279
245
280
impl LogLevel for ErrorLevel {
@@ -249,7 +284,7 @@ impl LogLevel for ErrorLevel {
249
284
}
250
285
251
286
/// Default to [`VerbosityFilter::Warn`]
252
- #[ derive( Copy , Clone , Debug , Default ) ]
287
+ #[ derive( Copy , Clone , Debug , Default , PartialEq , Eq ) ]
253
288
pub struct WarnLevel ;
254
289
255
290
impl LogLevel for WarnLevel {
@@ -259,7 +294,7 @@ impl LogLevel for WarnLevel {
259
294
}
260
295
261
296
/// Default to [`VerbosityFilter::Info`]
262
- #[ derive( Copy , Clone , Debug , Default ) ]
297
+ #[ derive( Copy , Clone , Debug , Default , PartialEq , Eq ) ]
263
298
pub struct InfoLevel ;
264
299
265
300
impl LogLevel for InfoLevel {
@@ -269,7 +304,7 @@ impl LogLevel for InfoLevel {
269
304
}
270
305
271
306
/// Default to [`VerbosityFilter::Debug`]
272
- #[ derive( Copy , Clone , Debug , Default ) ]
307
+ #[ derive( Copy , Clone , Debug , Default , PartialEq , Eq ) ]
273
308
pub struct DebugLevel ;
274
309
275
310
impl LogLevel for DebugLevel {
@@ -279,7 +314,7 @@ impl LogLevel for DebugLevel {
279
314
}
280
315
281
316
/// Default to [`VerbosityFilter::Trace`]
282
- #[ derive( Copy , Clone , Debug , Default ) ]
317
+ #[ derive( Copy , Clone , Debug , Default , PartialEq , Eq ) ]
283
318
pub struct TraceLevel ;
284
319
285
320
impl LogLevel for TraceLevel {
@@ -289,7 +324,7 @@ impl LogLevel for TraceLevel {
289
324
}
290
325
291
326
/// Default to [`VerbosityFilter::Off`] (no logging)
292
- #[ derive( Copy , Clone , Debug , Default ) ]
327
+ #[ derive( Copy , Clone , Debug , Default , PartialEq , Eq ) ]
293
328
pub struct OffLevel ;
294
329
295
330
impl LogLevel for OffLevel {
@@ -453,4 +488,85 @@ mod test {
453
488
assert_filter :: < TraceLevel > ( verbose, quiet, expected_filter) ;
454
489
}
455
490
}
491
+
492
+ #[ test]
493
+ fn from_verbosity_filter ( ) {
494
+ for & filter in & [
495
+ VerbosityFilter :: Off ,
496
+ VerbosityFilter :: Error ,
497
+ VerbosityFilter :: Warn ,
498
+ VerbosityFilter :: Info ,
499
+ VerbosityFilter :: Debug ,
500
+ VerbosityFilter :: Trace ,
501
+ ] {
502
+ assert_eq ! ( Verbosity :: <OffLevel >:: from( filter) . filter( ) , filter) ;
503
+ assert_eq ! ( Verbosity :: <ErrorLevel >:: from( filter) . filter( ) , filter) ;
504
+ assert_eq ! ( Verbosity :: <WarnLevel >:: from( filter) . filter( ) , filter) ;
505
+ assert_eq ! ( Verbosity :: <InfoLevel >:: from( filter) . filter( ) , filter) ;
506
+ assert_eq ! ( Verbosity :: <DebugLevel >:: from( filter) . filter( ) , filter) ;
507
+ assert_eq ! ( Verbosity :: <TraceLevel >:: from( filter) . filter( ) , filter) ;
508
+ }
509
+ }
510
+ }
511
+
512
+ #[ cfg( feature = "serde" ) ]
513
+ #[ cfg( test) ]
514
+ mod serde_tests {
515
+ use super :: * ;
516
+
517
+ use clap:: Parser ;
518
+ use serde:: { Deserialize , Serialize } ;
519
+
520
+ #[ derive( Debug , Parser , Serialize , Deserialize ) ]
521
+ struct Cli {
522
+ meaning_of_life : u8 ,
523
+ #[ command( flatten) ]
524
+ verbosity : Verbosity < InfoLevel > ,
525
+ }
526
+
527
+ #[ test]
528
+ fn serialize_toml ( ) {
529
+ let cli = Cli {
530
+ meaning_of_life : 42 ,
531
+ verbosity : Verbosity :: new ( 2 , 1 ) ,
532
+ } ;
533
+ let toml = toml:: to_string ( & cli) . unwrap ( ) ;
534
+ assert_eq ! ( toml, "meaning_of_life = 42\n verbosity = \" Debug\" \n " ) ;
535
+ }
536
+
537
+ #[ test]
538
+ fn deserialize_toml ( ) {
539
+ let toml = "meaning_of_life = 42\n verbosity = \" Debug\" \n " ;
540
+ let cli: Cli = toml:: from_str ( toml) . unwrap ( ) ;
541
+ assert_eq ! ( cli. meaning_of_life, 42 ) ;
542
+ assert_eq ! ( cli. verbosity. filter( ) , VerbosityFilter :: Debug ) ;
543
+ }
544
+
545
+ /// Tests that the `Verbosity` can be serialized and deserialized correctly from an a token.
546
+ #[ test]
547
+ fn serde_round_trips ( ) {
548
+ use serde_test:: { assert_tokens, Token } ;
549
+
550
+ for ( filter, variant) in [
551
+ ( VerbosityFilter :: Off , "Off" ) ,
552
+ ( VerbosityFilter :: Error , "Error" ) ,
553
+ ( VerbosityFilter :: Warn , "Warn" ) ,
554
+ ( VerbosityFilter :: Info , "Info" ) ,
555
+ ( VerbosityFilter :: Debug , "Debug" ) ,
556
+ ( VerbosityFilter :: Trace , "Trace" ) ,
557
+ ] {
558
+ let tokens = & [ Token :: UnitVariant {
559
+ name : "VerbosityFilter" ,
560
+ variant,
561
+ } ] ;
562
+
563
+ // `assert_tokens` checks both serialization and deserialization.
564
+ assert_tokens ( & Verbosity :: < OffLevel > :: from ( filter) , tokens) ;
565
+ assert_tokens ( & Verbosity :: < ErrorLevel > :: from ( filter) , tokens) ;
566
+ assert_tokens ( & Verbosity :: < WarnLevel > :: from ( filter) , tokens) ;
567
+ assert_tokens ( & Verbosity :: < InfoLevel > :: from ( filter) , tokens) ;
568
+ assert_tokens ( & Verbosity :: < DebugLevel > :: from ( filter) , tokens) ;
569
+ assert_tokens ( & Verbosity :: < TraceLevel > :: from ( filter) , tokens) ;
570
+ }
571
+ }
456
572
}
0 commit comments