From 060cd2832bb44090480049045162cd3cb89dc1c0 Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Mon, 26 Nov 2018 15:18:10 +0100 Subject: import some mailing lists --- src/data.rs | 17 +++++++++- src/main.rs | 13 +++++++- src/schema.rs | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++- src/validate.rs | 86 +++++++++++++++++++++++++++++--------------------- 4 files changed, 176 insertions(+), 38 deletions(-) (limited to 'src') diff --git a/src/data.rs b/src/data.rs index 2734f38..061badb 100644 --- a/src/data.rs +++ b/src/data.rs @@ -1,4 +1,4 @@ -use crate::schema::{Person, Team}; +use crate::schema::{Person, Team, List}; use failure::{Error, ResultExt}; use serde::Deserialize; use std::collections::HashMap; @@ -48,6 +48,21 @@ impl Data { Ok(()) } + pub(crate) fn lists(&self) -> Result, Error> { + let mut lists = HashMap::new(); + for team in self.teams.values() { + for list in team.lists(self)? { + lists.insert(list.address().to_string(), list); + } + } + Ok(lists) + } + + pub(crate) fn list(&self, name: &str) -> Result, Error> { + let mut lists = self.lists()?; + Ok(lists.remove(name)) + } + pub(crate) fn team(&self, name: &str) -> Option<&Team> { self.teams.get(name) } diff --git a/src/main.rs b/src/main.rs index 4cd04d6..7ae7817 100644 --- a/src/main.rs +++ b/src/main.rs @@ -17,7 +17,11 @@ enum Cli { #[structopt(name = "dump-team", help = "print the members of a team")] DumpTeam { name: String, - } + }, + #[structopt(name = "dump-list", help = "print all the emails in a list")] + DumpList { + name: String, + }, } fn main() { @@ -54,6 +58,13 @@ fn run() -> Result<(), Error> { }); } } + Cli::DumpList { ref name } => { + let data = Data::load()?; + let list = data.list(name)?.ok_or_else(|| err_msg("unknown list"))?; + for email in list.emails() { + println!("{}", email); + } + } } Ok(()) diff --git a/src/schema.rs b/src/schema.rs index bab5809..5e08a26 100644 --- a/src/schema.rs +++ b/src/schema.rs @@ -1,5 +1,5 @@ use crate::data::Data; -use failure::{Error, err_msg}; +use failure::{err_msg, Error}; use std::collections::HashSet; #[derive(serde_derive::Deserialize, Debug)] @@ -7,9 +7,11 @@ pub(crate) struct Person { name: String, github: String, irc: Option, + email: Option, } impl Person { + #[allow(unused)] pub(crate) fn name(&self) -> &str { &self.name } @@ -18,6 +20,7 @@ impl Person { &self.github } + #[allow(unused)] pub(crate) fn irc(&self) -> &str { if let Some(irc) = &self.irc { irc @@ -25,6 +28,10 @@ impl Person { &self.github } } + + pub(crate) fn email(&self) -> Option<&str> { + self.email.as_ref().map(|e| e.as_str()) + } } #[derive(serde_derive::Deserialize, Debug)] @@ -33,6 +40,8 @@ pub(crate) struct Team { #[serde(default)] children: Vec, people: TeamPeople, + #[serde(default)] + lists: Vec, } impl Team { @@ -56,6 +65,48 @@ impl Team { } Ok(members) } + + pub(crate) fn lists(&self, data: &Data) -> Result, Error> { + let mut lists = Vec::new(); + for raw_list in &self.lists { + let mut list = List { + address: raw_list.address.clone(), + access_level: raw_list.access_level, + emails: Vec::new(), + }; + + let mut members = if raw_list.include_team_members { + self.members(data)? + } else { + HashSet::new() + }; + for person in &raw_list.extra_people { + members.insert(person.as_str()); + } + for team in &raw_list.extra_teams { + let team = data + .team(team) + .ok_or_else(|| err_msg(format!("team {} is missing", team)))?; + for member in team.members(data)? { + members.insert(member); + } + } + + for member in members.iter() { + let member = data + .person(member) + .ok_or_else(|| err_msg(format!("member {} is missing", member)))?; + if let Some(email) = member.email() { + list.emails.push(email.to_string()); + } + } + for extra in &raw_list.extra_emails { + list.emails.push(extra.to_string()); + } + lists.push(list); + } + Ok(lists) + } } #[derive(serde_derive::Deserialize, Debug)] @@ -63,3 +114,48 @@ struct TeamPeople { leads: Vec, members: Vec, } + +#[derive(serde_derive::Deserialize, Debug)] +#[serde(rename_all = "kebab-case")] +pub(crate) struct TeamList { + address: String, + access_level: ListAccessLevel, + #[serde(default = "default_true")] + include_team_members: bool, + #[serde(default)] + extra_people: Vec, + #[serde(default)] + extra_emails: Vec, + #[serde(default)] + extra_teams: Vec, +} + +#[derive(serde_derive::Deserialize, Debug, Copy, Clone)] +#[serde(rename_all = "kebab-case")] +pub(crate) enum ListAccessLevel { + Everyone, + Members, + #[serde(rename = "read-only")] + Readonly, +} + +#[derive(Debug)] +pub(crate) struct List { + address: String, + access_level: ListAccessLevel, + emails: Vec, +} + +impl List { + pub(crate) fn address(&self) -> &str { + &self.address + } + + pub(crate) fn emails(&self) -> &[String] { + &self.emails + } +} + +fn default_true() -> bool { + true +} diff --git a/src/validate.rs b/src/validate.rs index 61e0778..250f172 100644 --- a/src/validate.rs +++ b/src/validate.rs @@ -8,6 +8,7 @@ pub(crate) fn validate(data: &Data) -> Result<(), Error> { validate_team_leads(data, &mut errors); validate_team_members(data, &mut errors); validate_inactive_members(data, &mut errors); + validate_list_email_addresses(data, &mut errors); if !errors.is_empty() { for err in &errors { @@ -22,58 +23,73 @@ pub(crate) fn validate(data: &Data) -> Result<(), Error> { /// Ensure team leaders are part of the teams they lead fn validate_team_leads(data: &Data, errors: &mut Vec) { - for team in data.teams() { - let members = match team.members(data) { - Ok(m) => m, - Err(err) => { - errors.push(err.to_string()); - continue; - } - }; - for lead in team.leads() { + wrapper(data.teams(), errors, |team, errors| { + let members = team.members(data)?; + wrapper(team.leads().iter(), errors, |lead, _| { if !members.contains(lead) { - errors.push(format!("`{}` leads team `{}`, but is not a member of it", lead, team.name())); + bail!("`{}` leads team `{}`, but is not a member of it", lead, team.name()); } - } - } + Ok(()) + }); + Ok(()) + }); } /// Ensure team members are people fn validate_team_members(data: &Data, errors: &mut Vec) { - for team in data.teams() { - let members = match team.members(data) { - Ok(m) => m, - Err(err) => { - errors.push(err.to_string()); - continue; - } - }; - for member in members { + wrapper(data.teams(), errors, |team, errors| { + wrapper(team.members(data)?.iter(), errors, |member, _| { if data.person(member).is_none() { - errors.push(format!("person `{}` is member of team `{}` but doesn't exist", member, team.name())); + bail!("person `{}` is member of team `{}` but doesn't exist", member, team.name()); } - } - } + Ok(()) + }); + Ok(()) + }); } /// Ensure every person is part of at least a team fn validate_inactive_members(data: &Data, errors: &mut Vec) { let mut active_members = HashSet::new(); - for team in data.teams() { - let members = match team.members(data) { - Ok(m) => m, - Err(err) => { - errors.push(err.to_string()); - continue; - } - }; + wrapper(data.teams(), errors, |team, _| { + let members = team.members(data)?; for member in members { active_members.insert(member); } - } + Ok(()) + }); let all_members = data.people().map(|p| p.github()).collect::>(); - for person in all_members.difference(&active_members) { - errors.push(format!("person `{}` is not a member of any team", person)); + wrapper(all_members.difference(&active_members), errors, |person, _| { + bail!("person `{}` is not a member of any team", person); + }); +} + +/// Ensure every member of a team with a mailing list has an email address +fn validate_list_email_addresses(data: &Data, errors: &mut Vec) { + wrapper(data.teams(), errors, |team, errors| { + if team.lists(data)?.is_empty() { + return Ok(()); + } + wrapper(team.members(data)?.iter(), errors, |member, _| { + let member = data.person(member).unwrap(); + if member.email().is_none() { + bail!("person `{}` is a member of a mailing list but has no email address", member.github()); + } + Ok(()) + }); + Ok(()) + }); +} + +fn wrapper(iter: I, errors: &mut Vec, mut func: F) +where + I: Iterator, + F: FnMut(T, &mut Vec) -> Result<(), Error>, +{ + for item in iter { + if let Err(err) = func(item, errors) { + errors.push(err.to_string()); + } } } -- cgit v1.2.3