commit
e7473fbd13
15
src/cache.rs
15
src/cache.rs
@ -1,21 +1,22 @@
|
|||||||
use crate::Error;
|
use crate::Error;
|
||||||
use std::{
|
use std::{
|
||||||
|
borrow::Cow,
|
||||||
fs::{create_dir_all, File, OpenOptions},
|
fs::{create_dir_all, File, OpenOptions},
|
||||||
io::BufReader,
|
io::BufReader,
|
||||||
path::Path,
|
path::Path,
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Enum to indicate the state of the cache
|
/// 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 head and cached head are the same
|
||||||
Current(u64),
|
Current(u64),
|
||||||
/// Cached head is older than current head
|
/// Cached head is older than current head
|
||||||
Old(Cache),
|
Old(Cache<'a>),
|
||||||
/// No cache was found
|
/// No cache was found
|
||||||
No,
|
No,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CacheState {
|
impl<'a> CacheState<'a> {
|
||||||
pub(crate) fn read_from_file(path: impl AsRef<Path>, head: &str) -> Result<CacheState, Error> {
|
pub(crate) fn read_from_file(path: impl AsRef<Path>, head: &str) -> Result<CacheState, Error> {
|
||||||
if path.as_ref().exists() {
|
if path.as_ref().exists() {
|
||||||
let cache: Cache = serde_json::from_reader(BufReader::new(File::open(path)?))?;
|
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 {
|
match self {
|
||||||
CacheState::Old(mut cache) => {
|
CacheState::Old(mut cache) => {
|
||||||
cache.head = head;
|
cache.head = head;
|
||||||
@ -42,12 +43,12 @@ impl CacheState {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Serialize, Deserialize)]
|
#[derive(Serialize, Deserialize)]
|
||||||
pub(crate) struct Cache {
|
pub(crate) struct Cache<'a> {
|
||||||
pub head: String,
|
pub head: Cow<'a, str>,
|
||||||
pub count: u64,
|
pub count: u64,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Cache {
|
impl<'a> Cache<'a> {
|
||||||
pub(crate) fn write_to_file(&self, path: impl AsRef<Path>) -> Result<(), Error> {
|
pub(crate) fn write_to_file(&self, path: impl AsRef<Path>) -> Result<(), Error> {
|
||||||
create_dir_all(path.as_ref().parent().ok_or(Error::Internal)?)?;
|
create_dir_all(path.as_ref().parent().ok_or(Error::Internal)?)?;
|
||||||
serde_json::to_writer(
|
serde_json::to_writer(
|
||||||
|
90
src/main.rs
90
src/main.rs
@ -9,14 +9,16 @@ extern crate serde_derive;
|
|||||||
|
|
||||||
mod cache;
|
mod cache;
|
||||||
mod error;
|
mod error;
|
||||||
|
mod service;
|
||||||
|
|
||||||
use crate::{cache::CacheState, error::Error};
|
use crate::{
|
||||||
|
cache::CacheState,
|
||||||
|
error::Error,
|
||||||
|
service::{Bitbucket, GitHub, Gitlab, Service},
|
||||||
|
};
|
||||||
use actix_web::{
|
use actix_web::{
|
||||||
error::ErrorBadRequest,
|
error::ErrorBadRequest,
|
||||||
http::{
|
http::header::{CacheControl, CacheDirective, Expires},
|
||||||
self,
|
|
||||||
header::{CacheControl, CacheDirective, Expires},
|
|
||||||
},
|
|
||||||
middleware, web, App, HttpResponse, HttpServer,
|
middleware, web, App, HttpResponse, HttpServer,
|
||||||
};
|
};
|
||||||
use badge::{Badge, BadgeOptions};
|
use badge::{Badge, BadgeOptions};
|
||||||
@ -100,7 +102,7 @@ fn pull(path: impl AsRef<Path>) -> Result<(), Error> {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn hoc(repo: &str, repo_dir: &str, cache_dir: &str) -> Result<u64, Error> {
|
fn hoc(repo: &str, repo_dir: &str, cache_dir: &str) -> Result<(u64, String), Error> {
|
||||||
let repo_dir = format!("{}/{}", repo_dir, repo);
|
let repo_dir = format!("{}/{}", repo_dir, repo);
|
||||||
let cache_dir = format!("{}/{}.json", cache_dir, repo);
|
let cache_dir = format!("{}/{}.json", cache_dir, repo);
|
||||||
let cache_dir = Path::new(&cache_dir);
|
let cache_dir = Path::new(&cache_dir);
|
||||||
@ -125,7 +127,7 @@ fn hoc(repo: &str, repo_dir: &str, cache_dir: &str) -> Result<u64, Error> {
|
|||||||
];
|
];
|
||||||
let cache = CacheState::read_from_file(&cache_dir, &head)?;
|
let cache = CacheState::read_from_file(&cache_dir, &head)?;
|
||||||
match &cache {
|
match &cache {
|
||||||
CacheState::Current(res) => return Ok(*res),
|
CacheState::Current(res) => return Ok((*res, head)),
|
||||||
CacheState::Old(cache) => {
|
CacheState::Old(cache) => {
|
||||||
arg.push(format!("{}..HEAD", cache.head));
|
arg.push(format!("{}..HEAD", cache.head));
|
||||||
}
|
}
|
||||||
@ -150,22 +152,21 @@ fn hoc(repo: &str, repo_dir: &str, cache_dir: &str) -> Result<u64, Error> {
|
|||||||
})
|
})
|
||||||
.sum();
|
.sum();
|
||||||
|
|
||||||
let cache = cache.calculate_new_cache(count, head);
|
let cache = cache.calculate_new_cache(count, (&head).into());
|
||||||
cache.write_to_file(cache_dir)?;
|
cache.write_to_file(cache_dir)?;
|
||||||
|
|
||||||
Ok(cache.count)
|
Ok((cache.count, head))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn remote_exists(url: &str) -> Result<bool, Error> {
|
fn remote_exists(url: &str) -> Result<bool, Error> {
|
||||||
Ok(CLIENT.head(url).send()?.status() == reqwest::StatusCode::OK)
|
Ok(CLIENT.head(url).send()?.status() == reqwest::StatusCode::OK)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn calculate_hoc(
|
fn calculate_hoc<T: Service>(
|
||||||
service: &str,
|
|
||||||
state: web::Data<Arc<State>>,
|
state: web::Data<Arc<State>>,
|
||||||
data: web::Path<(String, String)>,
|
data: web::Path<(String, String)>,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
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 path = format!("{}/{}", state.repos, service_path);
|
||||||
let file = Path::new(&path);
|
let file = Path::new(&path);
|
||||||
if !file.exists() {
|
if !file.exists() {
|
||||||
@ -179,7 +180,7 @@ fn calculate_hoc(
|
|||||||
repo.remote_set_url("origin", &url)?;
|
repo.remote_set_url("origin", &url)?;
|
||||||
}
|
}
|
||||||
pull(&path)?;
|
pull(&path)?;
|
||||||
let hoc = hoc(&service_path, &state.repos, &state.cache)?;
|
let (hoc, _) = hoc(&service_path, &state.repos, &state.cache)?;
|
||||||
let badge_opt = BadgeOptions {
|
let badge_opt = BadgeOptions {
|
||||||
subject: "Hits-of-Code".to_string(),
|
subject: "Hits-of-Code".to_string(),
|
||||||
color: "#007ec6".to_string(),
|
color: "#007ec6".to_string(),
|
||||||
@ -203,31 +204,46 @@ fn calculate_hoc(
|
|||||||
.streaming(rx_body.map_err(|_| ErrorBadRequest("bad request"))))
|
.streaming(rx_body.map_err(|_| ErrorBadRequest("bad request"))))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn github(
|
fn overview<T: Service>(
|
||||||
state: web::Data<Arc<State>>,
|
state: web::Data<Arc<State>>,
|
||||||
data: web::Path<(String, String)>,
|
data: web::Path<(String, String)>,
|
||||||
) -> Result<HttpResponse, Error> {
|
) -> Result<HttpResponse, Error> {
|
||||||
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)?;
|
||||||
fn gitlab(
|
let repo = Repository::init_bare(file)?;
|
||||||
state: web::Data<Arc<State>>,
|
repo.remote_add_fetch("origin", "refs/heads/*:refs/heads/*")?;
|
||||||
data: web::Path<(String, String)>,
|
repo.remote_set_url("origin", &url)?;
|
||||||
) -> Result<HttpResponse, Error> {
|
|
||||||
calculate_hoc("gitlab.com", state, data)
|
|
||||||
}
|
}
|
||||||
|
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 bitbucket(
|
let (tx, rx_body) = mpsc::unbounded();
|
||||||
state: web::Data<Arc<State>>,
|
let _ = tx.unbounded_send(Bytes::from(buf));
|
||||||
data: web::Path<(String, String)>,
|
|
||||||
) -> Result<HttpResponse, Error> {
|
|
||||||
calculate_hoc("bitbucket.org", state, data)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn overview(_: web::Path<(String, String)>) -> HttpResponse {
|
Ok(HttpResponse::Ok()
|
||||||
HttpResponse::TemporaryRedirect()
|
.content_type("text/html")
|
||||||
.header(http::header::LOCATION, "/")
|
.streaming(rx_body.map_err(|_| ErrorBadRequest("bad request"))))
|
||||||
.finish()
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[get("/")]
|
#[get("/")]
|
||||||
@ -263,12 +279,12 @@ fn main() -> std::io::Result<()> {
|
|||||||
.wrap(middleware::Logger::default())
|
.wrap(middleware::Logger::default())
|
||||||
.service(index)
|
.service(index)
|
||||||
.service(css)
|
.service(css)
|
||||||
.service(web::resource("/github/{user}/{repo}").to(github))
|
.service(web::resource("/github/{user}/{repo}").to(calculate_hoc::<GitHub>))
|
||||||
.service(web::resource("/gitlab/{user}/{repo}").to(gitlab))
|
.service(web::resource("/gitlab/{user}/{repo}").to(calculate_hoc::<Gitlab>))
|
||||||
.service(web::resource("/bitbucket/{user}/{repo}").to(bitbucket))
|
.service(web::resource("/bitbucket/{user}/{repo}").to(calculate_hoc::<Bitbucket>))
|
||||||
.service(web::resource("/view/github/{user}/{repo}").to(overview))
|
.service(web::resource("/view/github/{user}/{repo}").to(overview::<GitHub>))
|
||||||
.service(web::resource("/view/gitlab/{user}/{repo}").to(overview))
|
.service(web::resource("/view/gitlab/{user}/{repo}").to(overview::<Gitlab>))
|
||||||
.service(web::resource("/view/github/{user}/{repo}").to(overview))
|
.service(web::resource("/view/bitbucket/{user}/{repo}").to(overview::<Bitbucket>))
|
||||||
.default_service(web::resource("").route(web::get().to(p404)))
|
.default_service(web::resource("").route(web::get().to(p404)))
|
||||||
})
|
})
|
||||||
.bind(interface)?
|
.bind(interface)?
|
||||||
|
47
src/service.rs
Normal file
47
src/service.rs
Normal file
@ -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)
|
||||||
|
}
|
||||||
|
}
|
18
templates/overview.rs.html
Normal file
18
templates/overview.rs.html
Normal file
@ -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", {
|
||||||
|
|
||||||
|
<p>
|
||||||
|
The project at <a href="@url">@url</a> has <strong>@hoc</strong> hits of code at <a href="@commit_url">@head</a>.
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<p>
|
||||||
|
To include the badge in your readme, use the following markdown:
|
||||||
|
</p>
|
||||||
|
|
||||||
|
<pre>
|
||||||
|
[![Hits-of-Code](https://@domain/@path)](https://@domain/view/@path)
|
||||||
|
</pre>
|
||||||
|
}, commit, version)
|
Loading…
Reference in New Issue
Block a user