Skip to content

Commit

Permalink
Add tokio full feature flag and cleanup
Browse files Browse the repository at this point in the history
As of tokio 0.2, features are hidden behind flags.
For example, macros require the `macro` feature.
See tokio-rs/tokio#1833
for a more detailed description.

The easiest way to stay up-to-date is by enabling
the `full` feature flag, which is analog to the
previous behavior. This fixes `cargo install`.

Also, the `async_await` feature can be removed
as it is stable since rustc 1.39.
  • Loading branch information
mre committed Nov 28, 2019
1 parent 04a53bc commit 86b40ca
Show file tree
Hide file tree
Showing 12 changed files with 892 additions and 716 deletions.
1,236 changes: 673 additions & 563 deletions Cargo.lock

Large diffs are not rendered by default.

11 changes: 6 additions & 5 deletions Cargo.toml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
[package]
name = "github-star-counter"
version = "1.0.7"
version = "1.1.0"
authors = ["Sebastian Thiel <sthiel@thoughtworks.com>"]
edition = "2018"
repository = "https://github.com/Byron/github-star-counter"
Expand All @@ -21,17 +21,18 @@ path = "src/main/mod.rs"
serde = { version = "1.0", features = ["derive"] }
serde_json = "1.0.40"
itertools = "0.8.0"
futures-preview = { version = "=0.3.0-alpha.18", features = ["async-await", "nightly"] }
tokio = "0.2.0-alpha.1"
base64 = "0.10.1"
futures-preview = { version = "=0.3.0-alpha.19", features = ["async-await"] }
tokio = { version = "0.2.1", features = ["full"] }
base64 = "0.11"
lazy_static = "1.3.0"
log = "0.4.8"
surf = "1.0.2"
surf = "1.0"

# main
structopt = "0.3.0"
bytesize = "1.0.0"
simple_logger = "1.3.0"
tera = "0.11.20"

[dev-dependencies]
pretty_assertions = "0.6.1"
2 changes: 1 addition & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ help:
$(info fixtures | re-generate fixtures for tests)

tests:
cargo test --lib
cargo +nightly test --lib

fixtures:
curl "https://api.github.com/users/Byron" > test/fixtures/github.com-byron.json
Expand Down
7 changes: 3 additions & 4 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -99,11 +99,10 @@ Search the code for `TODO` to learn about workarounds/issues still present.

For the parallelism diagrams, a data point prefixed with `*` signals that multiple data is handled at the same time.

#### v1.0.7 - Remove code by using `surf`
#### v1.1.0 - Support for 'tera' templates

It was easy to use, felt well thought out, was perfectly documented, and... worked!

https://docs.rs/surf
Thanks to the generous contribution of @mre there now is support for rendering to custom tera
templates. [Look here](https://endler.dev/about/) for an example.

#### v1.0.6 - Assurance of correctness

Expand Down
1 change: 0 additions & 1 deletion rust-toolchain

This file was deleted.

53 changes: 53 additions & 0 deletions src/api.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
use crate::request::BasicAuth;
use serde::{Deserialize, Serialize};

#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
pub struct Repo {
pub stargazers_count: usize,
pub name: String,
pub owner: RepoOwner,
}

#[derive(Serialize, Deserialize, Debug, Clone, Eq, PartialEq)]
pub struct RepoStats {
pub total: usize,
pub total_by_user_only: Vec<usize>,
pub total_by_orgs_only: Vec<usize>,
}

#[derive(Clone, Debug, Serialize, Deserialize, Eq, PartialEq)]
pub struct RepoOwner {
pub login: String,
}

#[derive(Debug, Deserialize, Clone)]
pub struct User {
pub login: String,
pub public_repos: usize,
}

pub struct Options {
pub no_orgs: bool,
pub auth: Option<BasicAuth>,
pub page_size: usize,
pub repo_limit: usize,
pub stargazer_threshold: usize,
}

impl Default for Options {
fn default() -> Self {
Self {
auth: None,
no_orgs: false,
page_size: 100,
repo_limit: 10,
stargazer_threshold: 0,
}
}
}

#[derive(Debug)]
pub struct Response {
pub user: User,
pub repos: Vec<Repo>,
}
188 changes: 94 additions & 94 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
#![feature(async_closure)]
#![feature(async_await)]
#![cfg_attr(test, feature(async_closure))]

#[macro_use]
extern crate lazy_static;
Expand All @@ -9,64 +8,42 @@ use futures::future::join_all as join_all_futures;
use futures::{FutureExt, TryFutureExt};
use itertools::Itertools;
use log::{error, info};
use serde::Deserialize;
use std::{future::Future, io, sync::atomic::Ordering, time::Instant};
use std::fmt::Write;
use std::fs;
use std::path::PathBuf;
use std::{future::Future, sync::atomic::Ordering, time::Instant};
use tera::{Context, Tera};

mod api;
mod request;

pub type Error = Box<dyn std::error::Error>;

#[derive(Deserialize)]
#[cfg_attr(test, derive(Debug, Clone, Eq, PartialEq))]
struct Repo {
stargazers_count: usize,
name: String,
owner: RepoOwner,
}

#[derive(Deserialize)]
#[cfg_attr(test, derive(Debug, Clone, Eq, PartialEq))]
struct RepoOwner {
login: String,
}

#[derive(Deserialize, Clone)]
struct User {
login: String,
public_repos: usize,
}
pub use crate::api::*;

pub struct Options {
pub no_orgs: bool,
pub auth: Option<BasicAuth>,
pub page_size: usize,
pub repo_limit: usize,
pub stargazer_threshold: usize,
}
pub type Error = Box<dyn std::error::Error>;

impl Default for Options {
fn default() -> Self {
Self {
auth: None,
no_orgs: false,
page_size: 100,
repo_limit: 10,
stargazer_threshold: 0,
fn filter_repos(repos: &Vec<Repo>, user_login: &str, is_user: bool) -> Vec<usize> {
let compare_username_matches = |want: bool, user: String| {
move |r: &Repo| {
if r.owner.login.eq(&user) == want {
Some(r.stargazers_count)
} else {
None
}
}
}
};

repos
.iter()
.filter_map(compare_username_matches(is_user, user_login.to_owned()))
.collect()
}

pub async fn count_stars(
username: &str,
out: impl io::Write,
Options {
no_orgs,
auth,
page_size,
repo_limit,
stargazer_threshold,
}: Options,
) -> Result<(), Error> {
no_orgs: bool,
auth: Option<BasicAuth>,
page_size: usize,
) -> Result<Response, Error> {
let fetch_repos_for_user = |user| {
fetch_repos(user, page_size, |user, page_number| {
let repos_paged_url = format!(
Expand All @@ -87,7 +64,7 @@ pub async fn count_stars(
let user_url = format!("users/{}", username);
let user: User = request::json(user_url.clone(), auth.clone()).await?;
let orgs_url = format!("{}/orgs", user_url);
let mut user_repos_futures = vec![fetch_repos_for_user(user).boxed_local()];
let mut user_repos_futures = vec![fetch_repos_for_user(user.clone()).boxed_local()];

if !no_orgs {
let auth = auth.clone();
Expand Down Expand Up @@ -131,7 +108,7 @@ pub async fn count_stars(
duration_in_network_requests / elapsed.as_secs_f32()
);

output(username, repos, repo_limit, stargazer_threshold, out)
Ok(Response { user, repos })
}

async fn fetch_repos<F>(
Expand Down Expand Up @@ -185,75 +162,98 @@ fn sanity_check(page_size: usize, pages_with_results: &Vec<Vec<Repo>>) {
}
}

fn output(
username: &str,
fn get_stats(repos: &Vec<Repo>, login: &str) -> RepoStats {
let total: usize = repos.iter().map(|r| r.stargazers_count).sum();
let total_by_user_only = filter_repos(&repos, login, true);
let total_by_orgs_only = filter_repos(&repos, login, false);

RepoStats {
total,
total_by_user_only,
total_by_orgs_only,
}
}

pub fn render_output(
template: Option<PathBuf>,
mut repos: Vec<Repo>,
login: String,
repo_limit: usize,
stargazer_threshold: usize,
mut out: impl io::Write,
) -> Result<(), Error> {
let total: usize = repos.iter().map(|r| r.stargazers_count).sum();
let compare_username_matches = |want: bool| {
move |r: &Repo| {
if r.owner.login.eq(username) == want {
Some(r.stargazers_count)
} else {
None
}
}
};
let total_by_user_only: Vec<_> = repos
.iter()
.filter_map(compare_username_matches(true))
.collect();
let total_by_orgs_only: Vec<_> = repos
.iter()
.filter_map(compare_username_matches(false))
) -> Result<String, Error> {
let stats = get_stats(&repos, &login);

repos.sort_by(|a, b| b.stargazers_count.cmp(&a.stargazers_count));
let mut repos: Vec<_> = repos
.into_iter()
.filter(|r| r.stargazers_count >= stargazer_threshold)
.take(repo_limit)
.collect();

writeln!(out, "Total: {}", total)?;
if !total_by_user_only.is_empty() && !total_by_orgs_only.is_empty() {
if !stats.total_by_orgs_only.is_empty() {
for mut repo in repos.iter_mut() {
repo.name = format!("{}/{}", repo.owner.login, repo.name);
}
}

match template {
Some(template) => template_output(repos, stats, login, template),
None => default_output(repos, stats, login),
}
}

pub fn template_output(
repos: Vec<Repo>,
stats: RepoStats,
login: String,
template: PathBuf,
) -> Result<String, Error> {
let mut context = Context::new();
context.insert("repos", &repos);
context.insert("total", &stats.total);
context.insert("total_by_user_only", &stats.total_by_user_only);
context.insert("total_by_orgs_only", &stats.total_by_orgs_only);
context.insert("login", &login);

let template: String = fs::read_to_string(template)?;
let rendered = Tera::one_off(&template, &context, true)?;
Ok(rendered)
}

pub fn default_output(repos: Vec<Repo>, stats: RepoStats, login: String) -> Result<String, Error> {
let mut out = String::new();
writeln!(out, "Total: {}", stats.total)?;
if !stats.total_by_user_only.is_empty() && !stats.total_by_orgs_only.is_empty() {
writeln!(
out,
"Total for {}: {}",
username,
total_by_user_only.iter().sum::<usize>()
login,
stats.total_by_user_only.iter().sum::<usize>()
)?;
}
if !total_by_orgs_only.is_empty() {
if !stats.total_by_orgs_only.is_empty() {
writeln!(
out,
"Total for orgs: {}",
total_by_orgs_only.iter().sum::<usize>()
stats.total_by_orgs_only.iter().sum::<usize>()
)?;
}

repos.sort_by(|a, b| b.stargazers_count.cmp(&a.stargazers_count));
let mut repos: Vec<_> = repos
.into_iter()
.filter(|r| r.stargazers_count >= stargazer_threshold)
.take(repo_limit)
.collect();
if !total_by_orgs_only.is_empty() {
for mut repo in repos.iter_mut() {
repo.name = format!("{}/{}", repo.owner.login, repo.name);
}
}
let longest_name_len = repos.iter().map(|r| r.name.len()).max().unwrap_or(0);

if repos.len() > 0 {
writeln!(out)?;
}

let max_width = repos.iter().map(|r| r.name.len()).max().unwrap_or(0);
for repo in repos {
writeln!(
out,
"{:width$} ★ {}",
repo.name,
repo.stargazers_count,
width = longest_name_len
width = max_width
)?;
}
Ok(())
Ok(out)
}

#[cfg(test)]
Expand Down
Loading

0 comments on commit 86b40ca

Please sign in to comment.