group.rsannotatedgroup.rssource110 lines · 4.8 KB · raw

The groups a memory's picture is looked for in first, when there are too many pictures to put in one question.

A group is a category of the allowlist (the pack's own README names them), holding the admitted icons that depict a thing. Nothing here is invented: a category appears when the allowlist has an admitted picture of a thing under it, and its members are exactly those pictures.

7use std::fmt;
9use crate::{IconId, Verdict, allowlist};

What each category holds, in words Jev can choose between. A category the allowlist gains later has no entry until somebody writes one; it is still a group, described by its own name (a test lists the ones without).

13const DESCRIPTIONS: &[(&str, &str)] = &[
14    ("Animals", "animals, pets and other creatures"),
15    ("Dinosaurs", "dinosaurs"),
16    ("Fruits, Vegetables and other Ingredients", "fruit, vegetables and other things to cook with or snack on"),
17    ("Food and Drinks", "meals, sweets, snacks, desserts and drinks"),
18    ("Objects", "everyday things: vehicles, buildings, toys, instruments, devices, weather, holidays and gifts"),
19    ("Logos", "the logos of apps, websites and companies"),
20    ("Games", "characters from video games"),
21    ("Pokemons", "Pokemon characters"),
22    ("Memes", "internet meme characters"),
23];

How many of a group's pictures its description names.

26const EXAMPLES: usize = 6;

A group of pictures: one category of the allowlist that has an admitted picture of a thing.

29#[derive(Clone, Copy, Debug, PartialEq, Eq, PartialOrd, Ord, Hash)]
30pub struct IconGroup(&'static str);
32impl IconGroup {

The group called name (a category's name), if it has a picture to offer.

34    pub fn new(name: &str) -> Option<IconGroup> {
35        IconGroup::all().into_iter().find(|g| g.0 == name)
36    }

Every group, in the order of the allowlist.

39    pub fn all() -> Vec<IconGroup> {
40        let mut out: Vec<IconGroup> = Vec::new();
41        for e in allowlist().entries() {
42            if e.verdict == Verdict::Admitted && e.category != "UI" && !out.iter().any(|g| g.0 == e.category) {
43                out.push(IconGroup(e.category));
44            }
45        }
46        out
47    }

The group an icon belongs to, if it depicts a thing.

50    pub fn of(icon: &IconId) -> Option<IconGroup> {
51        icon.depicts_a_thing().then(|| IconGroup(icon.category()))
52    }

The category's name: what Jev is offered as the option.

55    pub fn name(&self) -> &'static str {
56        self.0
57    }

The group's pictures, in the order of the allowlist.

60    pub fn members(&self) -> Vec<IconId> {
61        IconId::all().filter(|i| IconGroup::of(i) == Some(*self)).collect()
62    }

What Jev is told the group holds: the description, then a few of its pictures by name.

65    pub fn describe(&self) -> String {
66        let what = DESCRIPTIONS.iter().find(|(n, _)| *n == self.0).map_or_else(|| self.0.to_lowercase(), |(_, d)| (*d).to_owned());
67        let members = self.members();
68        let examples: Vec<String> = members.iter().take(EXAMPLES).map(|m| m.as_str().replace('-', " ")).collect();
69        format!("pictures of {what}, {} in all, such as {}", members.len(), examples.join(", "))
70    }

The groups that have no description of their own yet (empty when every category is described).

73    pub fn undescribed() -> Vec<IconGroup> {
74        IconGroup::all().into_iter().filter(|g| !DESCRIPTIONS.iter().any(|(n, _)| *n == g.0)).collect()
75    }
76}
78impl fmt::Display for IconGroup {
79    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
80        f.write_str(self.0)
81    }
82}
83
84#[cfg(test)]
85mod tests {
86    use super::*;
87
88    #[test]
89    fn the_groups_are_the_categories_with_a_picture_of_a_thing_and_hold_exactly_those_pictures() {
90        let groups = IconGroup::all();
91        assert!(!groups.iter().any(|g| g.name() == "UI"), "interface icons are no group");
92        let total: usize = groups.iter().map(|g| g.members().len()).sum();
93        assert_eq!(total, IconId::all().filter(IconId::depicts_a_thing).count(), "every picture of a thing is in exactly one group");
94        for g in &groups {
95            assert!(!g.members().is_empty(), "{g}");
96            assert!(g.members().iter().all(|m| IconGroup::of(m) == Some(*g)));
97            assert_eq!(IconGroup::new(g.name()), Some(*g));
98        }
99        assert_eq!(IconGroup::new("UI"), None);
100        assert_eq!(IconGroup::of(&IconId::new("cat").unwrap()).unwrap().name(), "Animals");
101        assert_eq!(IconGroup::of(&IconId::new("arrow-up").unwrap()), None);
102    }
103
104    #[test]
105    fn every_group_is_described() {
106        assert!(IconGroup::undescribed().is_empty(), "describe: {:?}", IconGroup::undescribed());
107        let d = IconGroup::new("Dinosaurs").unwrap().describe();
108        assert!(d.contains("dinosaurs") && d.contains("4 in all") && d.contains("t rex"), "{d}");
109    }
110}