From 6c69a5ee75f18ecaa8b5cb7a320ed2cde94fb711 Mon Sep 17 00:00:00 2001 From: Pietro Albini Date: Thu, 11 Jul 2019 15:06:39 +0200 Subject: add the add-person subcommand --- src/github.rs | 50 ++++++++++++++++++++++++++++++++++++++++++++++++++ src/main.rs | 59 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++--- 2 files changed, 106 insertions(+), 3 deletions(-) create mode 100644 src/github.rs (limited to 'src') diff --git a/src/github.rs b/src/github.rs new file mode 100644 index 0000000..9a62994 --- /dev/null +++ b/src/github.rs @@ -0,0 +1,50 @@ +use failure::{Error, ResultExt}; +use reqwest::header::{self, HeaderValue}; +use reqwest::{Client, Method, RequestBuilder}; +use std::borrow::Cow; + +static API_BASE: &str = "https://api.github.com/"; +static TOKEN_VAR: &str = "GITHUB_TOKEN"; + +#[derive(serde::Deserialize)] +pub(crate) struct User { + pub(crate) login: String, + pub(crate) name: Option, + pub(crate) email: Option, +} + +pub(crate) struct GitHubApi { + http: Client, + token: String, +} + +impl GitHubApi { + pub(crate) fn new() -> Result { + let token = std::env::var(TOKEN_VAR) + .with_context(|_| format!("missing environment variable {}", TOKEN_VAR))?; + Ok(GitHubApi { + http: Client::new(), + token: token.to_string(), + }) + } + + fn prepare(&self, method: Method, url: &str) -> Result { + let url = if url.starts_with("https://") { + Cow::Borrowed(url) + } else { + Cow::Owned(format!("{}{}", API_BASE, url)) + }; + Ok(self.http.request(method, url.as_ref()).header( + header::AUTHORIZATION, + HeaderValue::from_str(&format!("token {}", self.token))?, + )) + } + + pub(crate) fn user(&self, login: &str) -> Result { + Ok(self + .prepare(Method::GET, &format!("users/{}", login))? + .send()? + .error_for_status()? + .json()?) + } +} diff --git a/src/main.rs b/src/main.rs index 1b45257..c47f270 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,12 +3,14 @@ mod data; #[macro_use] mod permissions; +mod github; mod schema; mod static_api; mod validate; use crate::data::Data; use failure::{err_msg, Error}; +use log::{error, info, warn}; use std::path::PathBuf; use structopt::StructOpt; @@ -17,6 +19,11 @@ use structopt::StructOpt; enum Cli { #[structopt(name = "check", help = "check if the configuration is correct")] Check, + #[structopt( + name = "add-person", + help = "add a new person from their GitHub profile" + )] + AddPerson { github_name: String }, #[structopt(name = "static-api", help = "generate the static API")] StaticApi { dest: String }, #[structopt(name = "dump-team", help = "print the members of a team")] @@ -36,11 +43,19 @@ enum Cli { } fn main() { - env_logger::init(); + let mut env = env_logger::Builder::new(); + env.default_format_timestamp(false); + env.default_format_module_path(false); + env.filter_module("rust_team", log::LevelFilter::Info); + if let Ok(content) = std::env::var("RUST_LOG") { + env.parse(&content); + } + env.init(); + if let Err(e) = run() { - eprintln!("error: {}", e); + error!("{}", e); for e in e.iter_causes() { - eprintln!(" cause: {}", e); + error!("cause: {}", e); } std::process::exit(1); } @@ -53,6 +68,44 @@ fn run() -> Result<(), Error> { Cli::Check => { crate::validate::validate(&data)?; } + Cli::AddPerson { ref github_name } => { + #[derive(serde::Serialize)] + struct PersonToAdd<'a> { + name: &'a str, + github: &'a str, + #[serde(skip_serializing_if = "Option::is_none")] + email: Option<&'a str>, + } + + let github = github::GitHubApi::new()?; + let user = github.user(github_name)?; + let github_name = user.login; + + if data.person(&github_name).is_some() { + failure::bail!("person already in the repo: {}", github_name); + } + + let file = format!("people/{}.toml", github_name); + std::fs::write( + &file, + toml::to_string_pretty(&PersonToAdd { + name: user.name.as_ref().map(|n| n.as_str()).unwrap_or_else(|| { + warn!( + "the person is missing the name on GitHub, defaulting to the username" + ); + github_name.as_str() + }), + github: &github_name, + email: user.email.as_ref().map(|e| e.as_str()).or_else(|| { + warn!("the person is missing the email on GitHub, leaving the field empty"); + None + }), + })? + .as_bytes(), + )?; + + info!("written data to {}", file); + } Cli::StaticApi { ref dest } => { let dest = PathBuf::from(dest); let generator = crate::static_api::Generator::new(&dest, &data)?; -- cgit v1.2.3