use super::field::{Field, FieldKind, ValueKind};
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct Form {
pub text: &'static str,
pub examples: &'static [&'static str],
}
pub const DURATION_FORMS: &[Form] = &[
Form {
text: "500ms, 2s, 5m, 1h",
examples: &["500ms", "2s", "5m", "1h"],
},
Form {
text: "a bare number is milliseconds",
examples: &["250"],
},
];
pub const DURATION_REFUSALS: &[Form] = &[
Form {
text: "a negative",
examples: &["-1s"],
},
Form {
text: "a unit shep does not know",
examples: &["3 fortnights"],
},
];
pub const MEMORY_FORMS: &[Form] = &[
Form {
text: "512M, 2G",
examples: &["512M", "2G"],
},
Form {
text: "a bare number is bytes",
examples: &["1048576"],
},
];
pub const BOOL_FORMS: &[Form] = &[Form {
text: "true or false",
examples: &["true", "false"],
}];
pub const INTEGER_FORMS: &[Form] = &[Form {
text: "a whole number",
examples: &["3"],
}];
#[derive(Debug, Clone, Default, PartialEq, Eq)]
pub struct Bullets {
pub accepts: Vec<String>,
pub refuses: Vec<String>,
}
impl Bullets {
#[must_use]
pub fn is_empty(&self) -> bool {
self.accepts.is_empty() && self.refuses.is_empty()
}
}
fn table_for(field: &Field) -> (&'static [Form], &'static [Form]) {
match field.value_kind {
Some(ValueKind::UpDuration) => return (DURATION_FORMS, DURATION_REFUSALS),
Some(ValueKind::MemSize) => return (MEMORY_FORMS, &[]),
None => {}
}
match field.kind {
FieldKind::Bool => (BOOL_FORMS, &[]),
FieldKind::Integer => (INTEGER_FORMS, &[]),
_ => (&[], &[]),
}
}
#[must_use]
pub fn bullets(field: &Field) -> Bullets {
if !field.accepts.is_empty() || !field.refuses.is_empty() {
return Bullets {
accepts: field.accepts.clone(),
refuses: field.refuses.clone(),
};
}
let (accepts, refuses) = table_for(field);
Bullets {
accepts: accepts.iter().map(|f| f.text.to_owned()).collect(),
refuses: refuses.iter().map(|f| f.text.to_owned()).collect(),
}
}
#[cfg(test)]
mod tests {
use super::*;
use shep_core::values::{MemSize, UpDuration};
#[test]
fn every_accepted_duration_form_parses() {
for form in DURATION_FORMS {
for example in form.examples {
assert!(
example.parse::<UpDuration>().is_ok(),
"{} is listed as accepted but does not parse",
example
);
}
}
}
#[test]
fn every_refused_duration_form_fails_to_parse() {
for form in DURATION_REFUSALS {
for example in form.examples {
assert!(
example.parse::<UpDuration>().is_err(),
"{} is listed as refused but parses",
example
);
}
}
}
#[test]
fn every_accepted_memory_form_parses() {
for form in MEMORY_FORMS {
for example in form.examples {
assert!(
example.parse::<MemSize>().is_ok(),
"{} is listed as accepted but does not parse",
example
);
}
}
}
fn forms_named(text: &str) -> usize {
text.split(',').flat_map(|part| part.split(" or ")).count()
}
#[test]
fn every_form_the_text_names_has_an_example() {
for (name, forms) in [
("DURATION_FORMS", DURATION_FORMS),
("DURATION_REFUSALS", DURATION_REFUSALS),
("MEMORY_FORMS", MEMORY_FORMS),
("BOOL_FORMS", BOOL_FORMS),
("INTEGER_FORMS", INTEGER_FORMS),
] {
for form in forms {
assert!(
forms_named(form.text) <= form.examples.len(),
"{name}: text \"{}\" names {} forms but examples has only {}",
form.text,
forms_named(form.text),
form.examples.len()
);
}
}
}
#[test]
fn a_field_with_its_own_accepts_replaces_the_type_table() {
let mut field = text_field("cwd");
field.value_kind = Some(ValueKind::UpDuration);
field.accepts = vec!["only this".to_owned()];
let bullets = bullets(&field);
assert_eq!(bullets.accepts, vec!["only this".to_owned()]);
}
#[test]
fn a_field_with_no_accepts_takes_the_type_table() {
let mut field = text_field("min_uptime");
field.value_kind = Some(ValueKind::UpDuration);
let bullets = bullets(&field);
assert!(bullets.accepts.len() > 1);
}
#[test]
fn a_plain_text_field_with_no_annotation_has_no_bullets() {
let bullets = bullets(&text_field("fold"));
assert!(bullets.is_empty());
}
fn text_field(key: &str) -> Field {
Field {
key: key.to_owned(),
help: key.to_owned(),
group: None,
kind: FieldKind::Text,
value_kind: None,
default: None,
default_value: None,
secret: false,
editable: true,
example: None,
accepts: Vec::new(),
refuses: Vec::new(),
neighbours: Vec::new(),
}
}
}