RandomId generator changed (now useful & used)
Now expects the config & dry run info, which means it can actually be used to generate pad IDs… which also happens now.
This commit is contained in:
		
							parent
							
								
									a34e4b7e1f
								
							
						
					
					
						commit
						b2b4f15747
					
				
					 2 changed files with 146 additions and 127 deletions
				
			
		|  | @ -1,3 +1,4 @@ | |||
| use crate::is_dry_run; | ||||
| use crate::key_value::KeyValueStore as KV; | ||||
| use std::error::Error; | ||||
| use std::io::{self, Write}; | ||||
|  | @ -13,6 +14,127 @@ const ANSI_FIELD: &str = "\x1b[1;33m"; | |||
| const ANSI_NOTICE: &str = "\x1b[33m"; | ||||
| const ANSI_RESET: &str = "\x1b[0m"; | ||||
| 
 | ||||
| /* ***** config structure definition ***** */ | ||||
| 
 | ||||
| /// Describes a field in the configuration.
 | ||||
| pub enum CfgField<'a> { | ||||
|     /// Silently writes a default to the DB if absent, never prompts for changes.
 | ||||
|     Silent { key: &'a str, default: &'a str }, | ||||
|     /// Writes a default, asks for changes.
 | ||||
|     Default { key: &'a str, default: &'a str, description: &'a str }, | ||||
|     /// No default, asks for value, but can stay empty.
 | ||||
|     Optional { key: &'a str, description: &'a str }, | ||||
|     /// Empty by default, required, will prompt without echo.
 | ||||
|     Password { key: &'a str, description: &'a str }, | ||||
|     /// Empty by default, can be user-provided, or will be generated randomly.
 | ||||
|     RandomId { | ||||
|         key: &'a str, | ||||
|         generator: fn(config: &KV, is_dry_run: bool) -> 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
 | ||||
| /// `{group.name}-{key}`.
 | ||||
| pub struct CfgGroup<'a> { | ||||
|     pub name: &'a str, | ||||
|     pub description: &'a str, | ||||
|     pub fields: &'a [CfgField<'a>], | ||||
| } | ||||
| 
 | ||||
| /// A description of the entire config, as a collection of named groups.
 | ||||
| ///
 | ||||
| /// Used both to populate defaults and to interactively adjust the configuration.
 | ||||
| pub struct CfgSpec<'a> { | ||||
|     pub groups: &'a [CfgGroup<'a>], | ||||
| } | ||||
| 
 | ||||
| impl<'a> CfgField<'a> { | ||||
|     /// Silent fields don't get prompted or shown.
 | ||||
|     fn is_silent(&self) -> bool { | ||||
|         matches!(self, CfgField::Silent { .. }) | ||||
|     } | ||||
| 
 | ||||
|     /// Password fields will be censored when displayed.
 | ||||
|     fn is_password(&self) -> bool { | ||||
|         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.
 | ||||
|     ///
 | ||||
|     /// (Because of the default population at boot, Silent and Default must
 | ||||
|     /// *necessarily* always have a value. Optional is optional.  Only
 | ||||
|     /// Password and RandomId might need actions if they are missing.)
 | ||||
|     fn is_action_required(&self, config: &KV, grpname: &str) -> bool { | ||||
|         if matches!( | ||||
|             self, | ||||
|             CfgField::Silent { .. } | CfgField::Default { .. } | CfgField::Optional { .. } | ||||
|         ) { | ||||
|             false | ||||
|         } else { | ||||
|             config.get(self.full_key(grpname).as_str()).ok().is_none() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Gets `key`, which is the local part of the field name. (`.full_key` is used for the DB.)
 | ||||
|     fn key(&self) -> String { | ||||
|         match self { | ||||
|             CfgField::Silent { key, .. } => key, | ||||
|             CfgField::Default { key, .. } => key, | ||||
|             CfgField::Optional { key, .. } => key, | ||||
|             CfgField::Password { key, .. } => key, | ||||
|             CfgField::RandomId { key, .. } => key, | ||||
|         } | ||||
|         .to_string() | ||||
|     } | ||||
| 
 | ||||
|     /// Full field name / key used in the DB (currently `{grpname}-{fieldname}`.)
 | ||||
|     fn full_key(&self, grpname: &str) -> String { | ||||
|         format!("{}-{}", grpname, self.key()) | ||||
|     } | ||||
| 
 | ||||
|     /// Gets `description` or "(none)".
 | ||||
|     fn description(&self) -> String { | ||||
|         match self { | ||||
|             CfgField::Silent { .. } => "(none)", | ||||
|             CfgField::Default { description, .. } => description, | ||||
|             CfgField::Optional { description, .. } => description, | ||||
|             CfgField::Password { description, .. } => description, | ||||
|             CfgField::RandomId { description, .. } => description, | ||||
|         } | ||||
|         .to_string() | ||||
|     } | ||||
| 
 | ||||
|     /// Gets a description of the default value if one exists.  
 | ||||
|     /// (Either the value itself, or a description of the generator making a default value.)
 | ||||
|     fn default_description(&self) -> Option<String> { | ||||
|         match self { | ||||
|             CfgField::Silent { default, .. } => Some(default.to_string()), | ||||
|             CfgField::Default { default, .. } => Some(default.to_string()), | ||||
|             CfgField::RandomId { generator_description, .. } => { | ||||
|                 Some(format!("{}{}{}", '(', generator_description, ')')) | ||||
|             }, | ||||
|             _ => None, | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Gets the actual default value, which may involve running a generator.
 | ||||
|     fn default_value(&self, config: &KV) -> 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(config, is_dry_run()).map(Some), | ||||
|             _ => Ok(None), | ||||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// Ensure all fields with known default values exist.
 | ||||
| pub fn populate_defaults(spec: &CfgSpec, config: &KV) { | ||||
|     for group in spec.groups { | ||||
|  | @ -119,7 +241,7 @@ fn check_field( | |||
|             } | ||||
|         }, | ||||
|         'R' => { | ||||
|             match field.default_value()? { | ||||
|             match field.default_value(&config)? { | ||||
|                 Some(value) => { | ||||
|                     config.set(&key, &value)?; | ||||
|                 }, | ||||
|  | @ -246,124 +368,3 @@ fn prompt_password() -> String { | |||
|     println!(""); | ||||
|     pass | ||||
| } | ||||
| 
 | ||||
| /* ***** config structure definition ***** */ | ||||
| 
 | ||||
| /// Describes a field in the configuration.
 | ||||
| pub enum CfgField<'a> { | ||||
|     /// Silently writes a default to the DB if absent, never prompts for changes.
 | ||||
|     Silent { key: &'a str, default: &'a str }, | ||||
|     /// Writes a default, asks for changes.
 | ||||
|     Default { key: &'a str, default: &'a str, description: &'a str }, | ||||
|     /// No default, asks for value, but can stay empty.
 | ||||
|     Optional { key: &'a str, description: &'a str }, | ||||
|     /// Empty by default, required, will prompt without echo.
 | ||||
|     Password { key: &'a str, description: &'a str }, | ||||
|     /// Empty by default, can be user-provided, or will be generated randomly.
 | ||||
|     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
 | ||||
| /// `{group.name}-{key}`.
 | ||||
| pub struct CfgGroup<'a> { | ||||
|     pub name: &'a str, | ||||
|     pub description: &'a str, | ||||
|     pub fields: &'a [CfgField<'a>], | ||||
| } | ||||
| 
 | ||||
| /// A description of the entire config, as a collection of named groups.
 | ||||
| ///
 | ||||
| /// Used both to populate defaults and to interactively adjust the configuration.
 | ||||
| pub struct CfgSpec<'a> { | ||||
|     pub groups: &'a [CfgGroup<'a>], | ||||
| } | ||||
| 
 | ||||
| impl<'a> CfgField<'a> { | ||||
|     /// Silent fields don't get prompted or shown.
 | ||||
|     fn is_silent(&self) -> bool { | ||||
|         matches!(self, CfgField::Silent { .. }) | ||||
|     } | ||||
| 
 | ||||
|     /// Password fields will be censored when displayed.
 | ||||
|     fn is_password(&self) -> bool { | ||||
|         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.
 | ||||
|     ///
 | ||||
|     /// (Because of the default population at boot, Silent and Default must
 | ||||
|     /// *necessarily* always have a value. Optional is optional.  Only
 | ||||
|     /// Password and RandomId might need actions if they are missing.)
 | ||||
|     fn is_action_required(&self, config: &KV, grpname: &str) -> bool { | ||||
|         if matches!( | ||||
|             self, | ||||
|             CfgField::Silent { .. } | CfgField::Default { .. } | CfgField::Optional { .. } | ||||
|         ) { | ||||
|             false | ||||
|         } else { | ||||
|             config.get(self.full_key(grpname).as_str()).ok().is_none() | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /// Gets `key`, which is the local part of the field name. (`.full_key` is used for the DB.)
 | ||||
|     fn key(&self) -> String { | ||||
|         match self { | ||||
|             CfgField::Silent { key, .. } => key, | ||||
|             CfgField::Default { key, .. } => key, | ||||
|             CfgField::Optional { key, .. } => key, | ||||
|             CfgField::Password { key, .. } => key, | ||||
|             CfgField::RandomId { key, .. } => key, | ||||
|         } | ||||
|         .to_string() | ||||
|     } | ||||
| 
 | ||||
|     /// Full field name / key used in the DB (currently `{grpname}-{fieldname}`.)
 | ||||
|     fn full_key(&self, grpname: &str) -> String { | ||||
|         format!("{}-{}", grpname, self.key()) | ||||
|     } | ||||
| 
 | ||||
|     /// Gets `description` or "(none)".
 | ||||
|     fn description(&self) -> String { | ||||
|         match self { | ||||
|             CfgField::Silent { .. } => "(none)", | ||||
|             CfgField::Default { description, .. } => description, | ||||
|             CfgField::Optional { description, .. } => description, | ||||
|             CfgField::Password { description, .. } => description, | ||||
|             CfgField::RandomId { description, .. } => description, | ||||
|         } | ||||
|         .to_string() | ||||
|     } | ||||
| 
 | ||||
|     /// Gets a description of the default value if one exists.  
 | ||||
|     /// (Either the value itself, or a description of the generator making a default value.)
 | ||||
|     fn default_description(&self) -> Option<String> { | ||||
|         match self { | ||||
|             CfgField::Silent { default, .. } => Some(default.to_string()), | ||||
|             CfgField::Default { default, .. } => Some(default.to_string()), | ||||
|             CfgField::RandomId { generator_description, .. } => { | ||||
|                 Some(format!("{}{}{}", '(', generator_description, ')')) | ||||
|             }, | ||||
|             _ => 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), | ||||
|         } | ||||
|     } | ||||
| } | ||||
|  |  | |||
|  | @ -17,9 +17,18 @@ pub const CONFIG: CfgGroup<'static> = CfgGroup { | |||
|             default: "plenum-template", | ||||
|             description: "Name of the pad containing the template to use.", | ||||
|         }, | ||||
|         // TODO: make these generators?
 | ||||
|         CfgField::Optional { key: "last-id", description: "ID of last plenum's pad." }, | ||||
|         CfgField::Optional { key: "next-id", description: "ID of next plenum's pad." }, | ||||
|         CfgField::RandomId { | ||||
|             key: "last-id", | ||||
|             generator: make_pad_id, | ||||
|             generator_description: "Makes a new pad that's completely empty.", | ||||
|             description: "ID of last plenum's pad.", | ||||
|         }, | ||||
|         CfgField::RandomId { | ||||
|             key: "next-id", | ||||
|             generator: make_pad_id, | ||||
|             generator_description: "Makes a new pad that's completely empty.", | ||||
|             description: "ID of next plenum's pad.", | ||||
|         }, | ||||
|     ], | ||||
| }; | ||||
| 
 | ||||
|  | @ -55,7 +64,9 @@ impl HedgeDoc { | |||
|     } | ||||
| 
 | ||||
|     pub fn create_pad(&self) -> Result<String, Box<dyn Error>> { | ||||
|         if self.is_dry_run { todo!("NYI: sane dry-run behavior") } | ||||
|         if self.is_dry_run { | ||||
|             todo!("NYI: sane dry-run behavior") | ||||
|         } | ||||
|         let res = self.do_request(&format!("{}/new", self.server_url)).unwrap(); | ||||
|         if res.status().is_success() { | ||||
|             Ok(self.get_id_from_response(res)) | ||||
|  | @ -65,7 +76,9 @@ impl HedgeDoc { | |||
|     } | ||||
| 
 | ||||
|     pub fn import_note(&self, id: Option<&str>, content: String) -> Result<String, Box<dyn Error>> { | ||||
|         if self.is_dry_run { todo!("NYI: sane dry-run behavior") } | ||||
|         if self.is_dry_run { | ||||
|             todo!("NYI: sane dry-run behavior") | ||||
|         } | ||||
|         let url = match id { | ||||
|             Some(id) => self.format_url(&format!("new/{id}")), | ||||
|             None => self.format_url("new"), | ||||
|  | @ -81,3 +94,8 @@ impl HedgeDoc { | |||
|         } | ||||
|     } | ||||
| } | ||||
| 
 | ||||
| /// For the config, make a new pad ID (by actually making a pad.)
 | ||||
| fn make_pad_id(config: &crate::KV, is_dry_run: bool) -> Result<String, Box<dyn Error>> { | ||||
|     HedgeDoc::new(&config.get("hedgedoc-server-url").unwrap(), is_dry_run).create_pad() | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue
	
	 nobody
						nobody