Merge branch 'feature/generator-from'

This commit is contained in:
Valentin Brandl 2019-05-04 15:45:35 +02:00
commit 6d9b95e7d0
No known key found for this signature in database
GPG Key ID: 30D341DD34118D7D
5 changed files with 108 additions and 11 deletions

View File

@ -2,6 +2,8 @@ use crate::P500;
use actix_web::{HttpResponse, ResponseError}; use actix_web::{HttpResponse, ResponseError};
use std::fmt; use std::fmt;
pub(crate) type Result<T> = std::result::Result<T, Error>;
#[derive(Debug)] #[derive(Debug)]
pub(crate) enum Error { pub(crate) enum Error {
Badge(String), Badge(String),

View File

@ -13,8 +13,8 @@ mod service;
use crate::{ use crate::{
cache::CacheState, cache::CacheState,
error::Error, error::{Error, Result},
service::{Bitbucket, GitHub, Gitlab, Service}, service::{Bitbucket, FormService, GitHub, Gitlab, Service},
}; };
use actix_web::{ use actix_web::{
error::ErrorBadRequest, error::ErrorBadRequest,
@ -27,6 +27,7 @@ use futures::{unsync::mpsc, Stream};
use git2::Repository; use git2::Repository;
use number_prefix::{NumberPrefix, Prefixed, Standalone}; use number_prefix::{NumberPrefix, Prefixed, Standalone};
use std::{ use std::{
borrow::Cow,
fs::create_dir_all, fs::create_dir_all,
path::{Path, PathBuf}, path::{Path, PathBuf},
process::Command, process::Command,
@ -42,6 +43,13 @@ pub struct VersionInfo<'a> {
pub version: &'a str, pub version: &'a str,
} }
#[derive(Deserialize, Serialize)]
struct GeneratorForm<'a> {
service: FormService,
user: Cow<'a, str>,
repo: Cow<'a, str>,
}
const VERSION_INFO: VersionInfo = VersionInfo { const VERSION_INFO: VersionInfo = VersionInfo {
commit: env!("VERGEN_SHA_SHORT"), commit: env!("VERGEN_SHA_SHORT"),
version: env!("CARGO_PKG_VERSION"), version: env!("CARGO_PKG_VERSION"),
@ -106,14 +114,14 @@ struct Opt {
workers: usize, workers: usize,
} }
fn pull(path: impl AsRef<Path>) -> Result<(), Error> { fn pull(path: impl AsRef<Path>) -> Result<()> {
let repo = Repository::open_bare(path)?; let repo = Repository::open_bare(path)?;
let mut origin = repo.find_remote("origin")?; let mut origin = repo.find_remote("origin")?;
origin.fetch(&["refs/heads/*:refs/heads/*"], None, None)?; origin.fetch(&["refs/heads/*:refs/heads/*"], None, None)?;
Ok(()) Ok(())
} }
fn hoc(repo: &str, repo_dir: &str, cache_dir: &str) -> Result<(u64, String), Error> { fn hoc(repo: &str, repo_dir: &str, cache_dir: &str) -> Result<(u64, String)> {
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);
@ -164,7 +172,7 @@ fn hoc(repo: &str, repo_dir: &str, cache_dir: &str) -> Result<(u64, String), Err
s.split_whitespace() s.split_whitespace()
.take(2) .take(2)
.map(str::parse::<u64>) .map(str::parse::<u64>)
.filter_map(Result::ok) .filter_map(std::result::Result::ok)
.sum::<u64>() .sum::<u64>()
}) })
.sum(); .sum();
@ -175,7 +183,7 @@ fn hoc(repo: &str, repo_dir: &str, cache_dir: &str) -> Result<(u64, String), Err
Ok((cache.count, head)) Ok((cache.count, head))
} }
fn remote_exists(url: &str) -> Result<bool, Error> { fn remote_exists(url: &str) -> Result<bool> {
Ok(CLIENT.head(url).send()?.status() == reqwest::StatusCode::OK) Ok(CLIENT.head(url).send()?.status() == reqwest::StatusCode::OK)
} }
@ -195,10 +203,10 @@ fn handle_hoc_request<T, F>(
state: web::Data<Arc<State>>, state: web::Data<Arc<State>>,
data: web::Path<(String, String)>, data: web::Path<(String, String)>,
mapper: F, mapper: F,
) -> Result<HttpResponse, Error> ) -> Result<HttpResponse>
where where
T: Service, T: Service,
F: Fn(HocResult) -> Result<HttpResponse, Error>, F: Fn(HocResult) -> Result<HttpResponse>,
{ {
hoc_request::<T>(state, data).and_then(mapper) hoc_request::<T>(state, data).and_then(mapper)
} }
@ -206,7 +214,7 @@ where
fn hoc_request<T: Service>( fn hoc_request<T: Service>(
state: web::Data<Arc<State>>, state: web::Data<Arc<State>>,
data: web::Path<(String, String)>, data: web::Path<(String, String)>,
) -> Result<HocResult, Error> { ) -> Result<HocResult> {
let repo = format!("{}/{}", data.0.to_lowercase(), data.1.to_lowercase()); let repo = format!("{}/{}", data.0.to_lowercase(), data.1.to_lowercase());
let service_path = format!("{}/{}", T::domain(), repo); let service_path = format!("{}/{}", T::domain(), repo);
let path = format!("{}/{}", state.repos, service_path); let path = format!("{}/{}", state.repos, service_path);
@ -242,7 +250,7 @@ fn hoc_request<T: Service>(
fn calculate_hoc<T: Service>( fn calculate_hoc<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> {
let mapper = |r| match r { let mapper = |r| match r {
HocResult::NotFound => Ok(p404()), HocResult::NotFound => Ok(p404()),
HocResult::Hoc { hoc_pretty, .. } => { HocResult::Hoc { hoc_pretty, .. } => {
@ -275,7 +283,7 @@ fn calculate_hoc<T: Service>(
fn overview<T: Service>( 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> {
let mapper = |r| match r { let mapper = |r| match r {
HocResult::NotFound => Ok(p404()), HocResult::NotFound => Ok(p404()),
HocResult::Hoc { HocResult::Hoc {
@ -317,6 +325,26 @@ fn index() -> HttpResponse {
.body(INDEX.as_slice()) .body(INDEX.as_slice())
} }
#[post("/generate")]
fn generate(params: web::Form<GeneratorForm>) -> Result<HttpResponse> {
let repo = format!("{}/{}", params.user, params.repo);
let mut buf = Vec::new();
templates::generate(
&mut buf,
VERSION_INFO,
&OPT.domain,
params.service.url(),
params.service.service(),
&repo,
)?;
let (tx, rx_body) = mpsc::unbounded();
let _ = tx.unbounded_send(Bytes::from(buf));
Ok(HttpResponse::Ok()
.content_type("text/html")
.streaming(rx_body.map_err(|_| ErrorBadRequest("bad request"))))
}
fn p404() -> HttpResponse { fn p404() -> HttpResponse {
HttpResponse::NotFound() HttpResponse::NotFound()
.content_type("text/html") .content_type("text/html")
@ -343,6 +371,7 @@ fn main() -> std::io::Result<()> {
.wrap(middleware::Logger::default()) .wrap(middleware::Logger::default())
.service(index) .service(index)
.service(css) .service(css)
.service(generate)
.service(web::resource("/github/{user}/{repo}").to(calculate_hoc::<GitHub>)) .service(web::resource("/github/{user}/{repo}").to(calculate_hoc::<GitHub>))
.service(web::resource("/gitlab/{user}/{repo}").to(calculate_hoc::<Gitlab>)) .service(web::resource("/gitlab/{user}/{repo}").to(calculate_hoc::<Gitlab>))
.service(web::resource("/bitbucket/{user}/{repo}").to(calculate_hoc::<Bitbucket>)) .service(web::resource("/bitbucket/{user}/{repo}").to(calculate_hoc::<Bitbucket>))

View File

@ -4,6 +4,34 @@ pub(crate) trait Service {
fn commit_url(repo: &str, commit_ref: &str) -> String; fn commit_url(repo: &str, commit_ref: &str) -> String;
} }
#[derive(Deserialize, Serialize)]
pub(crate) enum FormService {
#[serde(rename = "github")]
GitHub,
#[serde(rename = "gitlab")]
Gitlab,
#[serde(rename = "bitbucket")]
Bitbucket,
}
impl FormService {
pub(crate) fn url(&self) -> &str {
match self {
FormService::GitHub => "github.com",
FormService::Gitlab => "gitlab.com",
FormService::Bitbucket => "bitbucket.org",
}
}
pub(crate) fn service(&self) -> &str {
match self {
FormService::GitHub => "github",
FormService::Gitlab => "gitlab",
FormService::Bitbucket => "bitbucket",
}
}
}
pub(crate) struct GitHub; pub(crate) struct GitHub;
impl Service for GitHub { impl Service for GitHub {

View File

@ -0,0 +1,23 @@
@use super::base;
@use crate::VersionInfo;
@(version_info: VersionInfo, domain: &str, url: &str, service: &str, path: &str)
@:base("Hits-of-Code Badges", "Badge Generator", {
<p>
Here is the markdown for the badge for <a href="https://@url/@path">@url/@path</a>
</p>
<pre>
[![Hits-of-Code](https://@domain/@service/@path)](https://@domain/view/@service/@path)
</pre>
<p>
It will be rendered like this
</p>
<pre>
<a href="https://@domain/view/@service/@path"><img src="https://@domain/@service/@path" alt="example badge" /></a>
</pre>
}, version_info)

View File

@ -45,6 +45,21 @@ would render this badge:
alt="example badge" /></a> alt="example badge" /></a>
</pre> </pre>
<h2>Badge Generator</h2>
<form method="post" action="/generate">
<select name="service" id="service">
<option value="github">GitHub</option>
<option value="gitlab">Gitlab</option>
<option value="bitbucket">Bitbucket</option>
</select>
<label>/</label>
<input name="user" id="user" type="text" placeholder="user" />
<label>/</label>
<input name="repo" id="repo" type="text" placeholder="repository" />
<button type="submit">Generate</button>
</form>
<h2>Source Code</h2> <h2>Source Code</h2>
<p> <p>