From a1c10938c527cf85d3f5c006901505d2b2f6e7a5 Mon Sep 17 00:00:00 2001 From: Valentin Brandl Date: Mon, 29 Apr 2019 20:37:20 +0200 Subject: [PATCH 1/6] Use Cow instead of String --- src/cache.rs | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/src/cache.rs b/src/cache.rs index 5ae46d3..8318b98 100644 --- a/src/cache.rs +++ b/src/cache.rs @@ -1,21 +1,22 @@ use crate::Error; use std::{ + borrow::Cow, fs::{create_dir_all, File, OpenOptions}, io::BufReader, path::Path, }; /// Enum to indicate the state of the cache -pub(crate) enum CacheState { +pub(crate) enum CacheState<'a> { /// Current head and cached head are the same Current(u64), /// Cached head is older than current head - Old(Cache), + Old(Cache<'a>), /// No cache was found No, } -impl CacheState { +impl<'a> CacheState<'a> { pub(crate) fn read_from_file(path: impl AsRef, head: &str) -> Result { if path.as_ref().exists() { let cache: Cache = serde_json::from_reader(BufReader::new(File::open(path)?))?; @@ -29,7 +30,7 @@ impl CacheState { } } - pub(crate) fn calculate_new_cache(self, count: u64, head: String) -> Cache { + pub(crate) fn calculate_new_cache(self, count: u64, head: Cow<'a, str>) -> Cache { match self { CacheState::Old(mut cache) => { cache.head = head; @@ -42,12 +43,12 @@ impl CacheState { } #[derive(Serialize, Deserialize)] -pub(crate) struct Cache { - pub head: String, +pub(crate) struct Cache<'a> { + pub head: Cow<'a, str>, pub count: u64, } -impl Cache { +impl<'a> Cache<'a> { pub(crate) fn write_to_file(&self, path: impl AsRef) -> Result<(), Error> { create_dir_all(path.as_ref().parent().ok_or(Error::Internal)?)?; serde_json::to_writer( From fa2e06fccfd0a3e5bbb3c6bfe8f4078f01e348f3 Mon Sep 17 00:00:00 2001 From: Valentin Brandl Date: Mon, 29 Apr 2019 20:37:53 +0200 Subject: [PATCH 2/6] Add Service trait to remove code duplication --- src/service.rs | 47 +++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 src/service.rs diff --git a/src/service.rs b/src/service.rs new file mode 100644 index 0000000..f234ff3 --- /dev/null +++ b/src/service.rs @@ -0,0 +1,47 @@ +pub(crate) trait Service { + fn domain() -> &'static str; + fn url_path() -> &'static str; + fn commit_url(repo: &str, commit_ref: &str) -> String; +} + +pub(crate) struct GitHub; + +impl Service for GitHub { + fn domain() -> &'static str { + "github.com" + } + fn url_path() -> &'static str { + "github" + } + fn commit_url(repo: &str, commit_ref: &str) -> String { + format!("https://{}/{}/commit/{}", Self::domain(), repo, commit_ref) + } +} + +pub(crate) struct Gitlab; + +impl Service for Gitlab { + fn domain() -> &'static str { + "gitlab.com" + } + fn url_path() -> &'static str { + "gitlab" + } + fn commit_url(repo: &str, commit_ref: &str) -> String { + format!("https://{}/{}/commit/{}", Self::domain(), repo, commit_ref) + } +} + +pub(crate) struct Bitbucket; + +impl Service for Bitbucket { + fn domain() -> &'static str { + "bitbucket.org" + } + fn url_path() -> &'static str { + "bitbucket" + } + fn commit_url(repo: &str, commit_ref: &str) -> String { + format!("https://{}/{}/commits/{}", Self::domain(), repo, commit_ref) + } +} From fd6f58ca9cb359efe164a41ebced52498116c937 Mon Sep 17 00:00:00 2001 From: Valentin Brandl Date: Mon, 29 Apr 2019 20:38:05 +0200 Subject: [PATCH 3/6] Add overview template --- templates/overview.rs.html | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) create mode 100644 templates/overview.rs.html diff --git a/templates/overview.rs.html b/templates/overview.rs.html new file mode 100644 index 0000000..c01ff0a --- /dev/null +++ b/templates/overview.rs.html @@ -0,0 +1,18 @@ +@use super::base; + +@(commit: &str, version: &str, domain: &str, path: &str, url: &str, hoc: u64, head: &str, commit_url: &str) + +@:base("Hits-of-Code Badges", "Overview", { + +

+The project at @url has @hoc hits of code at @head. +

+ +

+To include the badge in your readme, use the following markdown: +

+ +
+[![Hits-of-Code](https://@domain/@path)](https://@domain/view/@path)
+
+}, commit, version) From 34e2942f5f5618710b2a85ce8636d0bba48043ab Mon Sep 17 00:00:00 2001 From: Valentin Brandl Date: Mon, 29 Apr 2019 20:40:05 +0200 Subject: [PATCH 4/6] Also return HEAD when calculating HOC --- src/main.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/src/main.rs b/src/main.rs index 67310db..8152521 100644 --- a/src/main.rs +++ b/src/main.rs @@ -100,7 +100,7 @@ fn pull(path: impl AsRef) -> Result<(), Error> { Ok(()) } -fn hoc(repo: &str, repo_dir: &str, cache_dir: &str) -> Result { +fn hoc(repo: &str, repo_dir: &str, cache_dir: &str) -> Result<(u64, String), Error> { let repo_dir = format!("{}/{}", repo_dir, repo); let cache_dir = format!("{}/{}.json", cache_dir, repo); let cache_dir = Path::new(&cache_dir); @@ -125,7 +125,7 @@ fn hoc(repo: &str, repo_dir: &str, cache_dir: &str) -> Result { ]; let cache = CacheState::read_from_file(&cache_dir, &head)?; match &cache { - CacheState::Current(res) => return Ok(*res), + CacheState::Current(res) => return Ok((*res, head)), CacheState::Old(cache) => { arg.push(format!("{}..HEAD", cache.head)); } @@ -150,10 +150,10 @@ fn hoc(repo: &str, repo_dir: &str, cache_dir: &str) -> Result { }) .sum(); - let cache = cache.calculate_new_cache(count, head); + let cache = cache.calculate_new_cache(count, (&head).into()); cache.write_to_file(cache_dir)?; - Ok(cache.count) + Ok((cache.count, head)) } fn remote_exists(url: &str) -> Result { @@ -179,7 +179,7 @@ fn calculate_hoc( repo.remote_set_url("origin", &url)?; } pull(&path)?; - let hoc = hoc(&service_path, &state.repos, &state.cache)?; + let (hoc, _) = hoc(&service_path, &state.repos, &state.cache)?; let badge_opt = BadgeOptions { subject: "Hits-of-Code".to_string(), color: "#007ec6".to_string(), From 361b446f2a523175bd124188d8d31e9b39c167e1 Mon Sep 17 00:00:00 2001 From: Valentin Brandl Date: Mon, 29 Apr 2019 20:41:53 +0200 Subject: [PATCH 5/6] Use new Service trait --- src/main.rs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/main.rs b/src/main.rs index 8152521..85c7411 100644 --- a/src/main.rs +++ b/src/main.rs @@ -9,6 +9,7 @@ extern crate serde_derive; mod cache; mod error; +mod service; use crate::{cache::CacheState, error::Error}; use actix_web::{ @@ -160,12 +161,11 @@ fn remote_exists(url: &str) -> Result { Ok(CLIENT.head(url).send()?.status() == reqwest::StatusCode::OK) } -fn calculate_hoc( - service: &str, +fn calculate_hoc( state: web::Data>, data: web::Path<(String, String)>, ) -> Result { - let service_path = format!("{}/{}/{}", service, data.0, data.1); + let service_path = format!("{}/{}/{}", T::domain(), data.0, data.1); let path = format!("{}/{}", state.repos, service_path); let file = Path::new(&path); if !file.exists() { @@ -263,12 +263,12 @@ fn main() -> std::io::Result<()> { .wrap(middleware::Logger::default()) .service(index) .service(css) - .service(web::resource("/github/{user}/{repo}").to(github)) - .service(web::resource("/gitlab/{user}/{repo}").to(gitlab)) - .service(web::resource("/bitbucket/{user}/{repo}").to(bitbucket)) - .service(web::resource("/view/github/{user}/{repo}").to(overview)) - .service(web::resource("/view/gitlab/{user}/{repo}").to(overview)) - .service(web::resource("/view/github/{user}/{repo}").to(overview)) + .service(web::resource("/github/{user}/{repo}").to(calculate_hoc::)) + .service(web::resource("/gitlab/{user}/{repo}").to(calculate_hoc::)) + .service(web::resource("/bitbucket/{user}/{repo}").to(calculate_hoc::)) + .service(web::resource("/view/github/{user}/{repo}").to(overview::)) + .service(web::resource("/view/gitlab/{user}/{repo}").to(overview::)) + .service(web::resource("/view/bitbucket/{user}/{repo}").to(overview::)) .default_service(web::resource("").route(web::get().to(p404))) }) .bind(interface)? From c81fc5478baf62d82a790d23ac1e1cbf6b88bfdf Mon Sep 17 00:00:00 2001 From: Valentin Brandl Date: Mon, 29 Apr 2019 20:42:17 +0200 Subject: [PATCH 6/6] Implement improved overview page --- src/main.rs | 66 +++++++++++++++++++++++++++++++++-------------------- 1 file changed, 41 insertions(+), 25 deletions(-) diff --git a/src/main.rs b/src/main.rs index 85c7411..cd138ed 100644 --- a/src/main.rs +++ b/src/main.rs @@ -11,13 +11,14 @@ mod cache; mod error; mod service; -use crate::{cache::CacheState, error::Error}; +use crate::{ + cache::CacheState, + error::Error, + service::{Bitbucket, GitHub, Gitlab, Service}, +}; use actix_web::{ error::ErrorBadRequest, - http::{ - self, - header::{CacheControl, CacheDirective, Expires}, - }, + http::header::{CacheControl, CacheDirective, Expires}, middleware, web, App, HttpResponse, HttpServer, }; use badge::{Badge, BadgeOptions}; @@ -203,31 +204,46 @@ fn calculate_hoc( .streaming(rx_body.map_err(|_| ErrorBadRequest("bad request")))) } -fn github( +fn overview( state: web::Data>, data: web::Path<(String, String)>, ) -> Result { - calculate_hoc("github.com", state, data) -} + let repo = format!("{}/{}", data.0, data.1); + let service_path = format!("{}/{}", T::domain(), repo); + let path = format!("{}/{}", state.repos, service_path); + let file = Path::new(&path); + let url = format!("https://{}", service_path); + if !file.exists() { + if !remote_exists(&url)? { + return Ok(p404()); + } + create_dir_all(file)?; + let repo = Repository::init_bare(file)?; + repo.remote_add_fetch("origin", "refs/heads/*:refs/heads/*")?; + repo.remote_set_url("origin", &url)?; + } + pull(&path)?; + let (hoc, head) = hoc(&service_path, &state.repos, &state.cache)?; + let mut buf = Vec::new(); + let req_path = format!("{}/{}/{}", T::url_path(), data.0, data.1); + templates::overview( + &mut buf, + COMMIT, + VERSION, + &OPT.domain, + &req_path, + &url, + hoc, + &head, + &T::commit_url(&repo, &head), + )?; -fn gitlab( - state: web::Data>, - data: web::Path<(String, String)>, -) -> Result { - calculate_hoc("gitlab.com", state, data) -} + let (tx, rx_body) = mpsc::unbounded(); + let _ = tx.unbounded_send(Bytes::from(buf)); -fn bitbucket( - state: web::Data>, - data: web::Path<(String, String)>, -) -> Result { - calculate_hoc("bitbucket.org", state, data) -} - -fn overview(_: web::Path<(String, String)>) -> HttpResponse { - HttpResponse::TemporaryRedirect() - .header(http::header::LOCATION, "/") - .finish() + Ok(HttpResponse::Ok() + .content_type("text/html") + .streaming(rx_body.map_err(|_| ErrorBadRequest("bad request")))) } #[get("/")]