From 6306ccc16725d02c81d0f2b8d442e315d564c2ab Mon Sep 17 00:00:00 2001 From: Daniel Freytag Date: Wed, 13 Nov 2024 10:03:39 +0100 Subject: [PATCH] feat: add WASM build --- CHANGELOG.md | 58 +++++--------------------------------- Cargo.lock | 45 +++++++++++++++++++++-------- Cargo.toml | 25 ++++++++++++++-- README.md | 19 ++++++++++++- src/lib.rs | 35 +++++++++++++++-------- src/main.rs | 8 +++--- tests/integration_tests.rs | 12 +++----- web/example.html | 21 ++++++++++++++ web/index.js | 15 ++++++++++ 9 files changed, 149 insertions(+), 89 deletions(-) create mode 100644 web/example.html create mode 100644 web/index.js diff --git a/CHANGELOG.md b/CHANGELOG.md index 1fd9ee2..5117150 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,57 +1,13 @@ -# 4.0.0 / 2023-07-18 +# Changelog -* [BUGFIX] Update GithubStats dep to use new Github.com format for table +## 5.1.0 / 2024-11-13 -# 3.4.0 / 2023-01-08 +- feat: add WASM build -* [ENHANCEMENT] Update GithubStats dep +## 5.0.0 / 2024-11-12 -# 3.3.1 / 2021-02-06 +- feat: migrate to Rust -* [BUGFIX] Actually bump githubstats dep - -# 3.3.0 / 2021-02-06 - -* [ENHANCEMENT] Update GithubStats dep - -# 3.2.0 / 2020-10-06 - -* [ENHANCEMENT] Update GithubStats dep - -# 3.1.1 / 2020-08-13 - -* [ENHANCEMENT] Update GithubStats dep - -# 3.1.0 / 2017-10-03 - -* [ENHANCEMENT] GitHub updated their styling; this updated matches that change - -# 3.0.0 / 2017-09-29 - -* [ENHANCEMENT] Upgrade to latest githubstats with better error messages - -# 2.1.0 / 2017-08-25 - -* [ENHANCEMENT] Upgrade to latest githubstats - -# 2.0.1 / 2016-11-08 - -* [BUGFIX] Actually update changelog in release - -# 2.0.0 / 2016-11-07 - -* [FEATURE] Add support for svg_square as a rendering type, thanks @blerchin - -# 1.1.0 / 2016-05-19 - -* [BUGFIX] Upgrade to newer GithubStats that fixes streak parsing - -# 1.0.1 / 2015-10-20 - -* [BUGFIX] Fix month labels for parity with GitHub -* [ENHANCEMENT] Link to @2016rshah's awesome service for hosted SVGs - -# 1.0.0 / 2015-01-25 - -* [ENHANCEMENT] Stabilized API +## 4.0.0 / 2023-07-18 +See [upstream repo](https://github.com/akerl/githubchart) for previouschangelog. diff --git a/Cargo.lock b/Cargo.lock index dce1c9c..070b48b 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -279,12 +279,6 @@ version = "0.3.31" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "05f29059c0c2090612e8d742178b0580d2dc940c837851ad723096f87af6663e" -[[package]] -name = "futures-io" -version = "0.3.31" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9e5c1b78ca4aae1ac06c48a526a655760685149f0d465d21f37abfe57ce075c6" - [[package]] name = "futures-sink" version = "0.3.31" @@ -304,12 +298,9 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "9fa08315bb612088cc391249efdc3bc77536f16c91f6cf495e6fbe85b20a4a81" dependencies = [ "futures-core", - "futures-io", "futures-task", - "memchr", "pin-project-lite", "pin-utils", - "slab", ] [[package]] @@ -337,8 +328,10 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c4567c8db10ae91089c99af84c68c38da3ec2f087c3f82960bcdbf3656b6f4d7" dependencies = [ "cfg-if", + "js-sys", "libc", "wasi", + "wasm-bindgen", ] [[package]] @@ -348,12 +341,19 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "07e28edb80900c19c28f1072f2e8aeca7fa06b23cd4169cefe1af5aa3260783f" [[package]] -name = "githubchart" -version = "5.0.0" +name = "githubchart-rust" +version = "5.1.0" dependencies = [ + "getrandom", + "js-sys", "regex", "reqwest", "scraper", + "serde", + "serde-wasm-bindgen", + "tokio", + "wasm-bindgen", + "wasm-bindgen-futures", ] [[package]] @@ -1196,6 +1196,17 @@ dependencies = [ "serde_derive", ] +[[package]] +name = "serde-wasm-bindgen" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f3b143e2833c57ab9ad3ea280d21fd34e285a42837aeb0ee301f4f41890fa00e" +dependencies = [ + "js-sys", + "serde", + "wasm-bindgen", +] + [[package]] name = "serde_derive" version = "1.0.215" @@ -1415,9 +1426,21 @@ dependencies = [ "mio", "pin-project-lite", "socket2", + "tokio-macros", "windows-sys 0.52.0", ] +[[package]] +name = "tokio-macros" +version = "2.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "693d596312e88961bc67d7f1f97af8a70227d9f90c31bba5806eec004978d752" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.87", +] + [[package]] name = "tokio-native-tls" version = "0.3.1" diff --git a/Cargo.toml b/Cargo.toml index ac7bfd4..fc043bc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,13 +1,32 @@ [package] -name = "githubchart" -version = "5.0.0" +name = "githubchart-rust" +version = "5.1.0" authors = ["frytg"] edition = "2021" +license = "MIT" +repository = "https://github.com/frytg/githubchart-rust" +description = "GitHub contribution graph generator in Rust/WASM" + +[lib] +crate-type = ["cdylib", "rlib"] [dependencies] regex = "1.11.1" -reqwest = { version = "0.11", features = ["blocking", "json"] } +reqwest = { version = "0.11", features = ["json"] } scraper = "0.17" +wasm-bindgen = "0.2" +wasm-bindgen-futures = "0.4" +serde = { version = "1.0", features = ["derive"] } +serde-wasm-bindgen = "0.5" +getrandom = { version = "0.2", features = ["js"] } + +[target.'cfg(not(target_arch = "wasm32"))'.dependencies] +tokio = { version = "1.0", features = ["rt-multi-thread", "macros"] } + +[target.'cfg(target_arch = "wasm32")'.dependencies] +js-sys = "0.3" +tokio = { version = "1.0", features = ["sync", "macros"] } +getrandom = { version = "0.2", features = ["js"] } [profile.release] # optimizations from https://github.com/johnthagen/min-sized-rust diff --git a/README.md b/README.md index 0bcf9aa..dc9c4b0 100644 --- a/README.md +++ b/README.md @@ -46,6 +46,23 @@ Test the binary with: ./target/release/githubchart release.svg -u frytg ``` +## Build for Web + +See [_Compiling from Rust to WebAssembly_](https://developer.mozilla.org/en-US/docs/WebAssembly/Rust_to_Wasm) for a full guide on compiling Rust to WebAssembly (WASM). + +This project is already configured to build for Web with `wasm-pack`. Run this command to build: + +```sh +wasm-pack build --target web +``` + +There's also an example in [`web/example.html`](./web/example.html) that you can run locally. + +More docs about this: + +- [WebAssembly in Deno](https://docs.deno.com/runtime/reference/wasm/) +- [`wasm-pack` docs](https://rustwasm.github.io/docs/wasm-pack/) + ## License -This `githubchart-rust` fork (like the upstream repo) is released under the MIT License. See the bundled [LICENSE](./LICENSE) file for details. +This `githubchart-rust` fork (like the upstream repository) is released under the MIT License. See the bundled [LICENSE](./LICENSE) file for details. diff --git a/src/lib.rs b/src/lib.rs index 6e57ccd..1ab8e81 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -1,5 +1,6 @@ use reqwest; use scraper::{Html, Selector}; +use wasm_bindgen::prelude::*; pub mod svg; @@ -37,20 +38,18 @@ pub const COLOR_SCHEMES: &[(&str, &[&str])] = &[ ), ]; -pub fn fetch_github_stats( +pub async fn fetch_github_stats( username: &str, ) -> Result, Box> { - // GitHub's contribution graph data endpoint let url = format!("https://github.com/users/{}/contributions", username); - // Fetch the HTML content - let client = reqwest::blocking::Client::new(); + let client = reqwest::Client::new(); let response = client .get(&url) .header("User-Agent", "githubchart-rust") - .send()?; + .send() + .await?; - // Check status code if !response.status().is_success() { return Err(format!( "Failed loading data from GitHub: {} {}", @@ -60,10 +59,8 @@ pub fn fetch_github_stats( .into()); } - let html_content = response.text()?; + let html_content = response.text().await?; let document = Html::parse_document(&html_content); - - // Select contribution calendar cells let cell_selector = Selector::parse("td.ContributionCalendar-day").unwrap(); let mut stats = Vec::new(); @@ -74,13 +71,11 @@ pub fn fetch_github_stats( element.value().attr("data-level"), ) { if let Ok(count) = count_str.parse::() { - // println!("fetch_github_stats > date: {}, count: {}", date, count); stats.push((date.to_string(), count)); } } } - // Sort by date stats.sort_by(|a, b| a.0.cmp(&b.0)); if stats.is_empty() { @@ -92,3 +87,21 @@ pub fn fetch_github_stats( #[cfg(test)] mod tests; + +#[wasm_bindgen] +pub async fn generate_github_chart(username: &str, color_scheme: Option) -> Result { + let stats = fetch_github_stats(username) + .await + .map_err(|e| JsValue::from_str(&e.to_string()))?; + + let colors = match color_scheme.as_deref() { + Some(scheme) => COLOR_SCHEMES + .iter() + .find(|&&(name, _)| name == scheme) + .map(|&(_, colors)| colors.to_vec()), + None => None, + }; + + let chart = Chart::new(stats, colors); + chart.render().map_err(|e| JsValue::from_str(&e)) +} diff --git a/src/main.rs b/src/main.rs index d3aea0d..2c79f12 100644 --- a/src/main.rs +++ b/src/main.rs @@ -3,9 +3,10 @@ use std::fs::File; use std::io::{self, Write}; use std::path::Path; -use githubchart::{fetch_github_stats, Chart, COLOR_SCHEMES}; +use githubchart_rust::{fetch_github_stats, Chart, COLOR_SCHEMES}; -fn main() { +#[tokio::main] +async fn main() { let args: Vec = env::args().collect(); if args.len() < 2 { eprintln!( @@ -49,8 +50,7 @@ fn main() { } let stats = if let Some(username) = username { - // Fetch stats from GitHub API - match fetch_github_stats(&username) { + match fetch_github_stats(&username).await { Ok(stats) => stats, Err(e) => { eprintln!("Error fetching GitHub stats: {}", e); diff --git a/tests/integration_tests.rs b/tests/integration_tests.rs index 7090ece..33a9f4d 100644 --- a/tests/integration_tests.rs +++ b/tests/integration_tests.rs @@ -1,18 +1,14 @@ use regex::Regex; +use githubchart_rust::{fetch_github_stats, Chart, COLOR_SCHEMES}; -use githubchart::{fetch_github_stats, Chart, COLOR_SCHEMES}; - -#[test] -fn test_github_stats_fetching() { - // Note: This test requires internet connection - match fetch_github_stats("frytg") { +#[tokio::test] +async fn test_github_stats_fetching() { + match fetch_github_stats("frytg").await { Ok(stats) => { assert!(!stats.is_empty()); - // Check date format assert!(Regex::new(r"^\d{4}-\d{2}-\d{2}$") .unwrap() .is_match(&stats[0].0)); - // Check contribution count is non-negative assert!(stats[0].1 >= 0); } Err(e) => panic!("Failed to fetch stats: {}", e), diff --git a/web/example.html b/web/example.html new file mode 100644 index 0000000..a674bb1 --- /dev/null +++ b/web/example.html @@ -0,0 +1,21 @@ + + + + + GitHub Chart Demo + + +
+ + + diff --git a/web/index.js b/web/index.js new file mode 100644 index 0000000..176958f --- /dev/null +++ b/web/index.js @@ -0,0 +1,15 @@ +import init, { generate_github_chart } from '../pkg/githubchart.js' + +export async function initWasm() { + await init() +} + +export async function generateGitHubChart(username, colorScheme) { + return generate_github_chart(username, colorScheme) +} + +export const COLOR_SCHEMES = { + default: ['#eeeeee', '#c6e48b', '#7bc96f', '#239a3b', '#196127'], + old: ['#eeeeee', '#d6e685', '#8cc665', '#44a340', '#1e6823'], + halloween: ['#EEEEEE', '#FFEE4A', '#FFC501', '#FE9600', '#03001C'], +}