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
|
@ -1,3 +1,4 @@
|
||||||
|
use crate::is_dry_run;
|
||||||
use crate::key_value::KeyValueStore as KV;
|
use crate::key_value::KeyValueStore as KV;
|
||||||
use std::error::Error;
|
use std::error::Error;
|
||||||
use std::io::{self, Write};
|
use std::io::{self, Write};
|
||||||
|
@ -13,6 +14,127 @@ const ANSI_FIELD: &str = "\x1b[1;33m";
|
||||||
const ANSI_NOTICE: &str = "\x1b[33m";
|
const ANSI_NOTICE: &str = "\x1b[33m";
|
||||||
const ANSI_RESET: &str = "\x1b[0m";
|
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.
|
/// Ensure all fields with known default values exist.
|
||||||
pub fn populate_defaults(spec: &CfgSpec, config: &KV) {
|
pub fn populate_defaults(spec: &CfgSpec, config: &KV) {
|
||||||
for group in spec.groups {
|
for group in spec.groups {
|
||||||
|
@ -119,7 +241,7 @@ fn check_field(
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
'R' => {
|
'R' => {
|
||||||
match field.default_value()? {
|
match field.default_value(&config)? {
|
||||||
Some(value) => {
|
Some(value) => {
|
||||||
config.set(&key, &value)?;
|
config.set(&key, &value)?;
|
||||||
},
|
},
|
||||||
|
@ -246,124 +368,3 @@ fn prompt_password() -> String {
|
||||||
println!("");
|
println!("");
|
||||||
pass
|
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",
|
default: "plenum-template",
|
||||||
description: "Name of the pad containing the template to use.",
|
description: "Name of the pad containing the template to use.",
|
||||||
},
|
},
|
||||||
// TODO: make these generators?
|
CfgField::RandomId {
|
||||||
CfgField::Optional { key: "last-id", description: "ID of last plenum's pad." },
|
key: "last-id",
|
||||||
CfgField::Optional { key: "next-id", description: "ID of next plenum's pad." },
|
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>> {
|
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();
|
let res = self.do_request(&format!("{}/new", self.server_url)).unwrap();
|
||||||
if res.status().is_success() {
|
if res.status().is_success() {
|
||||||
Ok(self.get_id_from_response(res))
|
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>> {
|
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 {
|
let url = match id {
|
||||||
Some(id) => self.format_url(&format!("new/{id}")),
|
Some(id) => self.format_url(&format!("new/{id}")),
|
||||||
None => self.format_url("new"),
|
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…
Reference in a new issue