config check: mostly functional now
This commit is contained in:
parent
264cd364ed
commit
45f5eeee57
|
@ -80,6 +80,7 @@ pub fn interactive_check(config: KV) -> Result<(), Box<dyn Error>> {
|
||||||
for group in CONFIG_SPEC.groups {
|
for group in CONFIG_SPEC.groups {
|
||||||
group_header( group.name, group.description );
|
group_header( group.name, group.description );
|
||||||
let todo = needs_info(&config, group);
|
let todo = needs_info(&config, group);
|
||||||
|
// TODO: add distinction between edit all / edit necessary only
|
||||||
let choices = if !todo.is_empty() {
|
let choices = if !todo.is_empty() {
|
||||||
println!( "{}The following fields need adjustment: {}{}", ANSI_NOTICE, todo.join(", "), ANSI_RESET );
|
println!( "{}The following fields need adjustment: {}{}", ANSI_NOTICE, todo.join(", "), ANSI_RESET );
|
||||||
&["[E]dit (default)", "[L]ist all", "[S]kip group"]
|
&["[E]dit (default)", "[L]ist all", "[S]kip group"]
|
||||||
|
@ -118,24 +119,36 @@ fn check_field( config: &KV, grpname: &str, field: &CfgField, ok: &mut bool ) ->
|
||||||
let key = field.full_key(grpname);
|
let key = field.full_key(grpname);
|
||||||
let value = config.get(&key).ok();
|
let value = config.get(&key).ok();
|
||||||
let mut actions: Vec<&'static str> = Vec::new();
|
let mut actions: Vec<&'static str> = Vec::new();
|
||||||
if value.is_some() { actions.push("[K]eep as-is"); }
|
// TODO: adjust order: empty password should offer input first
|
||||||
if field.is_password() {
|
// TODO: RandomId should offer generating as [R]egenerate
|
||||||
match value {
|
if value.is_some() | field.is_password() | field.is_optional() { actions.push("[K]eep as-is"); }
|
||||||
Some(_) => {
|
if field.default_description().is_some() { actions.push("[R]eset to default"); }
|
||||||
println!(" current: {}", HIDDEN_PASSWORD);
|
if field.is_password() { actions.push( "[R]emove" ); }
|
||||||
|
actions.push( "[I]nput new value" );
|
||||||
|
if !field.is_password() { actions.push( "[L]ong (multiline) input" ) };
|
||||||
|
|
||||||
|
match prompt_action(&actions) {
|
||||||
|
'K' => {
|
||||||
|
// we allow leaving a password empty, but that means config is incomplete
|
||||||
|
if value.is_none() & !field.is_optional() { *ok &= false; }
|
||||||
},
|
},
|
||||||
None => {
|
'R' => {
|
||||||
println!(" current: {}", EMPTY_VALUE);
|
match field.default_value()? {
|
||||||
actions.push("[K]eep as-is"); // we allow leaving the password empty if you don't want to set it
|
Some(value) => { config.set(&key, &value)?; },
|
||||||
},
|
// password again
|
||||||
};
|
None => { config.delete(&key)?; *ok &= false; },
|
||||||
//todo!()
|
}
|
||||||
// TODO: [K]eep as-is (if exists), [R]emove, [I]nput
|
},
|
||||||
} else {
|
'I' => {
|
||||||
println!( " current: {}", value.unwrap_or(EMPTY_VALUE.to_string()) );
|
let value = if field.is_password() { prompt_password() } else { prompt_single_line() };
|
||||||
// TODO: [K]eep as-is, [R]eset to default, [I]nput, [L]ong (multiline) input
|
config.set(&key, &value)?;
|
||||||
|
},
|
||||||
|
'L' => {
|
||||||
|
let value = prompt_multiline();
|
||||||
|
config.set(&key, &value)?;
|
||||||
|
},
|
||||||
|
_ => { return Err("Wat.".into()); }
|
||||||
}
|
}
|
||||||
// Action -
|
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -157,7 +170,7 @@ fn show_field( config: &KV, grpname: &str, field: &CfgField ) {
|
||||||
if field.is_silent() { return }
|
if field.is_silent() { return }
|
||||||
let key = field.full_key(grpname);
|
let key = field.full_key(grpname);
|
||||||
println!( "{}{}{} - {}", ANSI_FIELD, &key, ANSI_RESET, field.description() );
|
println!( "{}{}{} - {}", ANSI_FIELD, &key, ANSI_RESET, field.description() );
|
||||||
println!( " default: {}", field.default().unwrap_or(EMPTY_VALUE.to_string()));
|
println!( " default: {}", field.default_description().unwrap_or(EMPTY_VALUE.to_string()));
|
||||||
let value = config.get(&key).ok()
|
let value = config.get(&key).ok()
|
||||||
.map(|s| if field.is_password() { HIDDEN_PASSWORD.to_string() } else { s })
|
.map(|s| if field.is_password() { HIDDEN_PASSWORD.to_string() } else { s })
|
||||||
.unwrap_or(EMPTY_VALUE.to_string());
|
.unwrap_or(EMPTY_VALUE.to_string());
|
||||||
|
@ -228,7 +241,10 @@ fn prompt_multiline( ) -> String {
|
||||||
|
|
||||||
/// Read a password without echoing it.
|
/// Read a password without echoing it.
|
||||||
fn prompt_password( ) -> String {
|
fn prompt_password( ) -> String {
|
||||||
rpassword::prompt_password("New password (not shown) : ").unwrap()
|
let pass = rpassword::prompt_password("New password (not shown) : ").unwrap();
|
||||||
|
// disabled echo means the newline also isn't shown
|
||||||
|
println!("");
|
||||||
|
pass
|
||||||
}
|
}
|
||||||
|
|
||||||
/* ***** config structure definition ***** */
|
/* ***** config structure definition ***** */
|
||||||
|
@ -244,7 +260,7 @@ enum CfgField<'a> {
|
||||||
/// Empty by default, required, will prompt without echo.
|
/// Empty by default, required, will prompt without echo.
|
||||||
Password { key: &'a str, description: &'a str },
|
Password { key: &'a str, description: &'a str },
|
||||||
/// Empty by default, can be user-provided, or will be generated randomly.
|
/// Empty by default, can be user-provided, or will be generated randomly.
|
||||||
RandomId { key: &'a str, generator: fn() -> String, generator_description: &'a str, description: &'a str },
|
RandomId { key: &'a str, generator: fn() -> Result<String,Box<dyn Error>>, generator_description: &'a str, description: &'a str },
|
||||||
}
|
}
|
||||||
|
|
||||||
/// A group of related config fields. The final key of an inner value will be
|
/// A group of related config fields. The final key of an inner value will be
|
||||||
|
@ -273,6 +289,11 @@ impl<'a> CfgField<'a> {
|
||||||
matches!( self, CfgField::Password { .. } )
|
matches!( self, CfgField::Password { .. } )
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Optional fields are allowed to be (and stay) empty.
|
||||||
|
fn is_optional(&self) -> bool {
|
||||||
|
matches!( self, CfgField::Optional { .. } )
|
||||||
|
}
|
||||||
|
|
||||||
/// Reports if the field needs changing or is ok as-is.
|
/// Reports if the field needs changing or is ok as-is.
|
||||||
///
|
///
|
||||||
/// (Because of the default population at boot, Silent and Default must
|
/// (Because of the default population at boot, Silent and Default must
|
||||||
|
@ -315,7 +336,7 @@ impl<'a> CfgField<'a> {
|
||||||
|
|
||||||
/// Gets a description of the default value if one exists.
|
/// Gets a description of the default value if one exists.
|
||||||
/// (Either the value itself, or a description of the generator making a default value.)
|
/// (Either the value itself, or a description of the generator making a default value.)
|
||||||
fn default(&self) -> Option<String> {
|
fn default_description(&self) -> Option<String> {
|
||||||
match self {
|
match self {
|
||||||
CfgField::Silent { default, .. } => Some(default.to_string()),
|
CfgField::Silent { default, .. } => Some(default.to_string()),
|
||||||
CfgField::Default { default, .. } => Some(default.to_string()),
|
CfgField::Default { default, .. } => Some(default.to_string()),
|
||||||
|
@ -323,4 +344,14 @@ impl<'a> CfgField<'a> {
|
||||||
_ => None,
|
_ => None,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the actual default value, which may involve running a generator.
|
||||||
|
fn default_value(&self) -> Result<Option<String>, Box<dyn Error>> {
|
||||||
|
match self {
|
||||||
|
CfgField::Silent { default, .. } => Ok(Some(default.to_string())),
|
||||||
|
CfgField::Default { default, .. } => Ok(Some(default.to_string())),
|
||||||
|
CfgField::RandomId { generator, .. } => generator().map(Some),
|
||||||
|
_ => Ok(None),
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
Loading…
Reference in a new issue