From 0d8c22c6d93cf77045515a6be2bc87ee562cbefc Mon Sep 17 00:00:00 2001 From: Kornel Date: Mon, 23 Mar 2020 00:20:35 +0000 Subject: Rustaceans info --- .gitmodules | 3 +++ data/rustaceans | 1 + front_end/src/author_page.rs | 38 +++++++++++++++++++++++++++++++++++- front_end/templates/author.rs.html | 13 ++++++++---- kitchen_sink/src/lib_kitchen_sink.rs | 32 ++++++++++++++++++++++++++++++ 5 files changed, 82 insertions(+), 5 deletions(-) create mode 160000 data/rustaceans diff --git a/.gitmodules b/.gitmodules index 8d3714d..aa120a9 100644 --- a/.gitmodules +++ b/.gitmodules @@ -13,3 +13,6 @@ [submodule "data/index"] path = data/index url = https://github.com/rust-lang/crates.io-index.git +[submodule "data/rustaceans"] + path = data/rustaceans + url = https://github.com/nrc/rustaceans.org diff --git a/data/rustaceans b/data/rustaceans new file mode 160000 index 0000000..36b6be9 --- /dev/null +++ b/data/rustaceans @@ -0,0 +1 @@ +Subproject commit 36b6be9d5cbfcaaed29e4531ecef9ec22bdcb598 diff --git a/front_end/src/author_page.rs b/front_end/src/author_page.rs index 2f06bab..31c0a5d 100644 --- a/front_end/src/author_page.rs +++ b/front_end/src/author_page.rs @@ -10,6 +10,7 @@ use kitchen_sink::Org; use kitchen_sink::OwnerKind; use kitchen_sink::RichAuthor; use kitchen_sink::RichCrateVersion; +use kitchen_sink::Rustacean; use kitchen_sink::User; use kitchen_sink::UserType; use render_readme::Renderer; @@ -38,10 +39,14 @@ pub struct AuthorPage<'a> { pub(crate) member_total: usize, pub(crate) keywords: Vec, pub(crate) collab: Vec, + pub(crate) rustacean: Option, } impl<'a> AuthorPage<'a> { pub async fn new(aut: &'a RichAuthor, kitchen_sink: &'a KitchenSink, markup: &'a Renderer) -> CResult> { + + let rustacean = kitchen_sink.rustacean_for_github_login(&aut.github.login); + let orgs = kitchen_sink.user_github_orgs(&aut.github.login).await?.unwrap_or_default(); let orgs = futures::stream::iter(orgs).filter_map(|org| async move { kitchen_sink.github_org(&org.login).await @@ -111,6 +116,7 @@ impl<'a> AuthorPage<'a> { joined, keywords, collab, + rustacean, }) } @@ -133,9 +139,39 @@ impl<'a> AuthorPage<'a> { .collect().await } + pub fn name(&self) -> Option<&str> { + let gh_name = self.aut.name(); + if !gh_name.is_empty() && gh_name != self.login() { + return Some(gh_name); + } + self.rustacean.as_ref().and_then(|r| r.name.as_deref()) + } + + pub fn twitter_link(&self) -> Option<(String, &str)> { + eprintln!("{:?}", self.rustacean); + self.rustacean.as_ref().and_then(|r| r.twitter.as_deref()) + .map(|t| t.trim_start_matches('@')) + .filter(|t| !t.is_empty()) + .map(|t| { + (format!("https://twitter.com/{}", t), t) + }) + } + + pub fn forum_link(&self) -> Option<(String, &str)> { + self.rustacean.as_ref().and_then(|r| r.discourse.as_deref()) + .map(|t| t.trim_start_matches('@')) + .filter(|t| !t.is_empty()) + .map(|t| { + (format!("https://users.rust-lang.org/u/{}", t), t) + }) + } + /// `(url, label)` pub fn homepage_link(&self) -> Option<(&str, Cow<'_, str>)> { - if let Some(url) = self.aut.github.blog.as_deref() { + let url = self.aut.github.blog.as_deref() + .or_else(|| self.rustacean.as_ref().and_then(|r| r.website.as_deref())) + .or_else(|| self.rustacean.as_ref().and_then(|r| r.blog.as_deref())); + if let Some(url) = url { if url.starts_with("https://") || url.starts_with("http://") { let label = url_domain(url) .map(|host| { diff --git a/front_end/templates/author.rs.html b/front_end/templates/author.rs.html index f84dca3..54ad323 100644 --- a/front_end/templates/author.rs.html +++ b/front_end/templates/author.rs.html @@ -34,8 +34,8 @@ @if p.orgs.iter().any(|o| o.login == "rust-lang") {Rust team} @p.login() - @if !p.aut.name().is_empty() && p.aut.name() != p.login() { -

@p.aut.name()

+ @if let Some(n) = p.name() { +

@n

}

Joined crates-io @HumanTime::from(p.joined).to_text_en(Accuracy::Rough, Tense::Past)@if let Some(d) = p.joined_github() {. @@ -50,6 +50,12 @@ @if let Some((url, label)) = p.homepage_link() {

  • @label
  • } + @if let Some((url, label)) = p.twitter_link() { +
  • Twitter (@label)
  • + } + @if let Some((url, label)) = p.forum_link() { +
  • Forum (@label)
  • + } @@ -93,9 +99,8 @@ }) diff --git a/kitchen_sink/src/lib_kitchen_sink.rs b/kitchen_sink/src/lib_kitchen_sink.rs index d10c16d..61a7357 100644 --- a/kitchen_sink/src/lib_kitchen_sink.rs +++ b/kitchen_sink/src/lib_kitchen_sink.rs @@ -170,6 +170,7 @@ pub struct KitchenSink { category_overrides: HashMap>>, crates_io_owners_cache: TempCache>, throttle: tokio::sync::Semaphore, + data_path: PathBuf, } impl KitchenSink { @@ -212,6 +213,7 @@ impl KitchenSink { category_overrides: Self::load_category_overrides(&data_path.join("category_overrides.txt"))?, crates_io_owners_cache: TempCache::new(&data_path.join("cio-owners.tmp"))?, throttle: tokio::sync::Semaphore::new(40), + data_path: data_path.into(), }) }) } @@ -2095,6 +2097,17 @@ impl KitchenSink { Ok(self.crate_db.recently_updated_crates_in_category(slug).await?) } + /// Case sensitive! + pub fn rustacean_for_github_login(&self, login: &str) -> Option { + if !is_alnum(login) { + return None; + } + + let path = self.data_path.join(format!("rustaceans/data/{}.json", login)); + let json = std::fs::read(path).ok()?; + serde_json::from_slice(&json).ok() + } + #[inline] pub async fn notable_recently_updated_crates(&self, limit: u32) -> CResult> { let mut crates = self.crate_db.recently_updated_crates(limit).await?; @@ -2160,6 +2173,25 @@ impl RichAuthor { } } +#[derive(Deserialize, Debug)] +pub struct Rustacean { + pub name: Option, + /// email address. Will appear in a mailto link. + pub email: Option, + /// homepage URL. + pub website: Option, + /// URL for your blog. + pub blog: Option, + /// username on Discourse. + pub discourse: Option, + /// username on Reddit + pub reddit: Option, + /// username on Twitter, including the @. + pub twitter: Option, + /// any notes you lik + pub notes: Option, +} + /// This is used to uniquely identify authors based on as little information as is available #[derive(Debug, Hash, Eq, PartialEq)] enum AuthorId { -- cgit v1.2.3