github-trending-rs 0.0.5

A simple Rust crate to fetch trending repositories from GitHub.
Documentation
use github_trending_rs::{Language, Param, SpokenLanguage, TrendExt};
use soupy::{Node, Queryable};
use std::{
    collections::HashSet,
    time::{Duration, SystemTime, UNIX_EPOCH},
};
use strum::IntoEnumIterator as _;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let date = std::env::args().nth(1);

    let content = match date {
        Some(date) => std::fs::read_to_string(format!("./tests/github-trends-{}.html", date))?,
        None => {
            let client = reqwest::ClientBuilder::new()
                .connect_timeout(Duration::from_secs(5))
                .timeout(Duration::from_secs(10))
                .build()?;

            let trending = client.github_trending().repositories().await?;

            let secs = SystemTime::now()
                .duration_since(UNIX_EPOCH)
                .unwrap_or_default()
                .as_secs();

            std::fs::write(
                format!("./tests/github-trends-{}.html", secs),
                trending.raw(),
            )?;

            trending.raw().to_owned()
        }
    };

    let soup = soupy::Soup::html(content);
    let items = soup
        .attr("id", "languages-menuitems")
        .first()
        .expect("cant find languages menuitems");

    let defined_langs: std::collections::HashSet<String> = Language::iter()
        .map(|item| item.value().to_string())
        .collect();

    let parsed_langs: Option<std::collections::HashSet<String>> = items
        .query()
        .tag("a")
        .into_iter()
        .map(|item| {
            item.get("href")
                .and_then(|href| {
                    href.strip_prefix("/trending/")
                        .and_then(|href| href.strip_suffix("?since=daily"))
                })
                .map(|item| item.to_string())
        })
        .collect();

    let parsed_langs = parsed_langs.expect("some parse failed");

    let new_langs: HashSet<String> = parsed_langs
        .difference(&defined_langs)
        .map(ToOwned::to_owned)
        .collect();

    let removed_langs: HashSet<String> = defined_langs
        .difference(&parsed_langs)
        .map(ToOwned::to_owned)
        .collect();

    if !new_langs.is_empty() {
        println!("New Programming Languages: {:?}", new_langs);
    }

    if !removed_langs.is_empty() {
        println!("Removed Programming Languages: {:?}", removed_langs);
    }

    let defined_sls: std::collections::HashSet<String> = SpokenLanguage::iter()
        .map(|item| item.value().to_string())
        .collect();

    let parsed_sls: Option<std::collections::HashSet<String>> = soup
        .attr("data-filterable-for", "text-filter-field-spoken-language")
        .first()
        .expect("cant find spoken language list")
        .query()
        .tag("a")
        .into_iter()
        .map(|item| {
            item.get("href")
                .and_then(|href| href.strip_prefix("/trending?spoken_language_code="))
                .map(ToOwned::to_owned)
        })
        .collect();

    let parsed_sls = parsed_sls.expect("some spoken language parse failed");

    let new_sls: HashSet<String> = parsed_sls
        .difference(&defined_sls)
        .map(ToOwned::to_owned)
        .collect();
    let removed_sls: HashSet<String> = defined_sls
        .difference(&parsed_sls)
        .map(ToOwned::to_owned)
        .collect();

    if !new_sls.is_empty() {
        println!("New Spoken Languages: {:?}", new_sls);
    }

    if !removed_sls.is_empty() {
        println!("Removed Spoken Languages: {:?}", removed_sls);
    }

    Ok(())
}