parent
a6f707b911
commit
59b9e77d47
8 changed files with 265 additions and 1 deletions
7
Cargo.lock
generated
7
Cargo.lock
generated
|
@ -178,6 +178,11 @@ dependencies = [
|
||||||
"diesel 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"diesel 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
]
|
]
|
||||||
|
|
||||||
|
[[package]]
|
||||||
|
name = "diff"
|
||||||
|
version = "0.1.10"
|
||||||
|
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||||
|
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "digest"
|
name = "digest"
|
||||||
version = "0.6.2"
|
version = "0.6.2"
|
||||||
|
@ -574,6 +579,7 @@ dependencies = [
|
||||||
"clap 2.26.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"clap 2.26.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"diesel 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"diesel 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"diesel_codegen 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
"diesel_codegen 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
"diff 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"futures 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
"futures 0.1.16 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"futures-cpupool 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
"futures-cpupool 0.1.6 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
"hyper 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
"hyper 0.11.2 (registry+https://github.com/rust-lang/crates.io-index)",
|
||||||
|
@ -959,6 +965,7 @@ dependencies = [
|
||||||
"checksum diesel 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "304226fa7a3982b0405f6bb95dd9c10c3e2000709f194038a60ec2c277150951"
|
"checksum diesel 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "304226fa7a3982b0405f6bb95dd9c10c3e2000709f194038a60ec2c277150951"
|
||||||
"checksum diesel_codegen 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18a42ca5c9b660add51d58bc5a50a87123380e1e458069c5504528a851ed7384"
|
"checksum diesel_codegen 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18a42ca5c9b660add51d58bc5a50a87123380e1e458069c5504528a851ed7384"
|
||||||
"checksum diesel_infer_schema 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bf1957ff5cd3b04772e43c162c2f69c2aa918080ff9b020276792d236be8be52"
|
"checksum diesel_infer_schema 0.16.0 (registry+https://github.com/rust-lang/crates.io-index)" = "bf1957ff5cd3b04772e43c162c2f69c2aa918080ff9b020276792d236be8be52"
|
||||||
|
"checksum diff 0.1.10 (registry+https://github.com/rust-lang/crates.io-index)" = "0a515461b6c8c08419850ced27bc29e86166dcdcde8fbe76f8b1f0589bb49472"
|
||||||
"checksum digest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e5b29bf156f3f4b3c4f610a25ff69370616ae6e0657d416de22645483e72af0a"
|
"checksum digest 0.6.2 (registry+https://github.com/rust-lang/crates.io-index)" = "e5b29bf156f3f4b3c4f610a25ff69370616ae6e0657d416de22645483e72af0a"
|
||||||
"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
|
"checksum dtoa 0.4.2 (registry+https://github.com/rust-lang/crates.io-index)" = "09c3753c3db574d215cba4ea76018483895d7bff25a31b49ba45db21c48e50ab"
|
||||||
"checksum either 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18785c1ba806c258137c937e44ada9ee7e69a37e3c72077542cd2f069d78562a"
|
"checksum either 1.1.0 (registry+https://github.com/rust-lang/crates.io-index)" = "18785c1ba806c258137c937e44ada9ee7e69a37e3c72077542cd2f069d78562a"
|
||||||
|
|
|
@ -12,6 +12,7 @@ bart = "0.1.4"
|
||||||
bart_derive = "0.1.4"
|
bart_derive = "0.1.4"
|
||||||
chrono = "0.4"
|
chrono = "0.4"
|
||||||
clap = "2.26"
|
clap = "2.26"
|
||||||
|
diff = "0.1.10"
|
||||||
futures = "0.1"
|
futures = "0.1"
|
||||||
futures-cpupool = "0.1"
|
futures-cpupool = "0.1"
|
||||||
hyper = "0.11"
|
hyper = "0.11"
|
||||||
|
|
|
@ -405,3 +405,13 @@ input[type="search"] {
|
||||||
max-height: 500px;
|
max-height: 500px;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
.removed {
|
||||||
|
background: red;
|
||||||
|
}
|
||||||
|
.same {
|
||||||
|
background: white;
|
||||||
|
}
|
||||||
|
.added {
|
||||||
|
background: green;
|
||||||
|
}
|
||||||
|
|
|
@ -10,6 +10,7 @@
|
||||||
|
|
||||||
extern crate chrono;
|
extern crate chrono;
|
||||||
extern crate clap;
|
extern crate clap;
|
||||||
|
extern crate diff;
|
||||||
extern crate futures;
|
extern crate futures;
|
||||||
extern crate futures_cpupool;
|
extern crate futures_cpupool;
|
||||||
extern crate percent_encoding;
|
extern crate percent_encoding;
|
||||||
|
|
225
src/resources/diff_resource.rs
Normal file
225
src/resources/diff_resource.rs
Normal file
|
@ -0,0 +1,225 @@
|
||||||
|
use std;
|
||||||
|
use std::str::FromStr;
|
||||||
|
|
||||||
|
use diff;
|
||||||
|
use futures::{self, Future};
|
||||||
|
use futures::future::{done, finished};
|
||||||
|
use hyper;
|
||||||
|
use hyper::header::ContentType;
|
||||||
|
use hyper::server::*;
|
||||||
|
use serde_urlencoded;
|
||||||
|
|
||||||
|
use mimes::*;
|
||||||
|
use models::ArticleRevision;
|
||||||
|
use site::Layout;
|
||||||
|
use state::State;
|
||||||
|
use web::{Resource, ResponseFuture};
|
||||||
|
|
||||||
|
const NONE: &str = "none";
|
||||||
|
|
||||||
|
type BoxResource = Box<Resource + Sync + Send>;
|
||||||
|
type Error = Box<std::error::Error + Send + Sync>;
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub enum ArticleRevisionReference {
|
||||||
|
None,
|
||||||
|
Some {
|
||||||
|
article_id: u32,
|
||||||
|
revision: u32,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::num::ParseIntError;
|
||||||
|
|
||||||
|
pub enum ArticleRevisionReferenceParseError {
|
||||||
|
SplitError,
|
||||||
|
ParseIntError(ParseIntError),
|
||||||
|
}
|
||||||
|
|
||||||
|
use std::fmt;
|
||||||
|
impl fmt::Display for ArticleRevisionReferenceParseError {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
use self::ArticleRevisionReferenceParseError::*;
|
||||||
|
match self {
|
||||||
|
&SplitError => write!(f, "invalid format, must contain one @"),
|
||||||
|
&ParseIntError(ref r) => r.fmt(f)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl From<ParseIntError> for ArticleRevisionReferenceParseError {
|
||||||
|
fn from(x: ParseIntError) -> ArticleRevisionReferenceParseError {
|
||||||
|
ArticleRevisionReferenceParseError::ParseIntError(x)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl FromStr for ArticleRevisionReference {
|
||||||
|
type Err = ArticleRevisionReferenceParseError;
|
||||||
|
|
||||||
|
fn from_str(s: &str) -> Result<Self, Self::Err> {
|
||||||
|
if s == NONE {
|
||||||
|
return Ok(ArticleRevisionReference::None);
|
||||||
|
}
|
||||||
|
|
||||||
|
let items: Vec<&str> = s.split("@").collect();
|
||||||
|
if items.len() != 2 {
|
||||||
|
return Err(ArticleRevisionReferenceParseError::SplitError)
|
||||||
|
}
|
||||||
|
|
||||||
|
let article_id = items[0].parse::<u32>()?;
|
||||||
|
let revision = items[1].parse::<u32>()?;
|
||||||
|
|
||||||
|
Ok(ArticleRevisionReference::Some { article_id, revision })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl fmt::Display for ArticleRevisionReference {
|
||||||
|
fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
|
||||||
|
match self {
|
||||||
|
&ArticleRevisionReference::None => write!(f, "{}", NONE),
|
||||||
|
&ArticleRevisionReference::Some { article_id, revision } => {
|
||||||
|
write!(f, "{}@{}", article_id, revision)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use serde::{de, Deserialize, Deserializer};
|
||||||
|
impl<'de> Deserialize<'de> for ArticleRevisionReference {
|
||||||
|
fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
|
||||||
|
where D: Deserializer<'de>
|
||||||
|
{
|
||||||
|
let s = String::deserialize(deserializer)?;
|
||||||
|
FromStr::from_str(&s).map_err(de::Error::custom)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
use serde::{Serialize, Serializer};
|
||||||
|
impl Serialize for ArticleRevisionReference {
|
||||||
|
fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
|
||||||
|
where S: Serializer
|
||||||
|
{
|
||||||
|
serializer.serialize_str(&self.to_string())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone)]
|
||||||
|
pub struct DiffLookup {
|
||||||
|
state: State,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Serialize, Deserialize)]
|
||||||
|
pub struct QueryParameters {
|
||||||
|
from: ArticleRevisionReference,
|
||||||
|
to: ArticleRevisionReference,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl QueryParameters {
|
||||||
|
pub fn new(from: ArticleRevisionReference, to: ArticleRevisionReference) -> QueryParameters {
|
||||||
|
QueryParameters { from, to }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn into_link(self) -> String {
|
||||||
|
serde_urlencoded::to_string(self).expect("Serializing to String cannot fail")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiffLookup {
|
||||||
|
pub fn new(state: State) -> DiffLookup {
|
||||||
|
Self { state }
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn lookup(&self, query: Option<&str>) -> Box<Future<Item=Option<BoxResource>, Error=::web::Error>> {
|
||||||
|
let state = self.state.clone();
|
||||||
|
|
||||||
|
Box::new(done((|| -> Result<Option<BoxResource>, ::web::Error> {
|
||||||
|
let params: QueryParameters = serde_urlencoded::from_str(query.unwrap_or(""))?;
|
||||||
|
|
||||||
|
Ok(Some(Box::new(DiffResource::new(state, params.from, params.to))))
|
||||||
|
}())))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub struct DiffResource {
|
||||||
|
state: State,
|
||||||
|
from: ArticleRevisionReference,
|
||||||
|
to: ArticleRevisionReference,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl DiffResource {
|
||||||
|
pub fn new(state: State, from: ArticleRevisionReference, to: ArticleRevisionReference) -> Self {
|
||||||
|
Self { state, from, to }
|
||||||
|
}
|
||||||
|
|
||||||
|
fn query_args(&self) -> QueryParameters {
|
||||||
|
QueryParameters {
|
||||||
|
from: self.from.clone(),
|
||||||
|
to: self.to.clone(),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get_article_revision(&self, r: &ArticleRevisionReference) -> Box<Future<Item = Option<ArticleRevision>, Error = Error>> {
|
||||||
|
match r {
|
||||||
|
&ArticleRevisionReference::None => Box::new(finished(None)),
|
||||||
|
&ArticleRevisionReference::Some { article_id, revision } => Box::new(
|
||||||
|
self.state.get_article_revision(article_id as i32, revision as i32)
|
||||||
|
),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
impl Resource for DiffResource {
|
||||||
|
fn allow(&self) -> Vec<hyper::Method> {
|
||||||
|
use hyper::Method::*;
|
||||||
|
vec![Options, Head, Get]
|
||||||
|
}
|
||||||
|
|
||||||
|
fn head(&self) -> ResponseFuture {
|
||||||
|
Box::new(futures::finished(Response::new()
|
||||||
|
.with_status(hyper::StatusCode::Ok)
|
||||||
|
.with_header(ContentType(TEXT_HTML.clone()))
|
||||||
|
))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn get(self: Box<Self>) -> ResponseFuture {
|
||||||
|
#[derive(BartDisplay)]
|
||||||
|
#[template = "templates/diff.html"]
|
||||||
|
struct Template<'a> {
|
||||||
|
lines: &'a [DiffLine<'a>],
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Default)]
|
||||||
|
struct DiffLine<'a> {
|
||||||
|
removed: Option<&'a str>,
|
||||||
|
same: Option<&'a str>,
|
||||||
|
added: Option<&'a str>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let from = self.get_article_revision(&self.from);
|
||||||
|
let to = self.get_article_revision(&self.to);
|
||||||
|
|
||||||
|
let head = self.head();
|
||||||
|
|
||||||
|
Box::new(head.join3(from, to)
|
||||||
|
.and_then(move |(head, from, to)| {
|
||||||
|
Ok(head
|
||||||
|
.with_body(Layout {
|
||||||
|
base: None, // Hmm, should perhaps accept `base` as argument
|
||||||
|
title: "Difference",
|
||||||
|
body: &Template {
|
||||||
|
lines: &diff::lines(
|
||||||
|
from.as_ref().map(|x| &*x.body).unwrap_or(""),
|
||||||
|
to.as_ref().map(|x| &*x.body).unwrap_or("")
|
||||||
|
)
|
||||||
|
.into_iter()
|
||||||
|
.map(|x| match x {
|
||||||
|
diff::Result::Left(x) => DiffLine { removed: Some(x), ..Default::default() },
|
||||||
|
diff::Result::Both(x, _) => DiffLine { same: Some(x), ..Default::default() },
|
||||||
|
diff::Result::Right(x) => DiffLine { added: Some(x), ..Default::default() },
|
||||||
|
})
|
||||||
|
.collect::<Vec<_>>()
|
||||||
|
},
|
||||||
|
}.to_string()))
|
||||||
|
}))
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,7 @@ pub mod pagination;
|
||||||
mod article_revision_resource;
|
mod article_revision_resource;
|
||||||
mod article_resource;
|
mod article_resource;
|
||||||
mod changes_resource;
|
mod changes_resource;
|
||||||
|
mod diff_resource;
|
||||||
mod new_article_resource;
|
mod new_article_resource;
|
||||||
mod search_resource;
|
mod search_resource;
|
||||||
mod sitemap_resource;
|
mod sitemap_resource;
|
||||||
|
@ -11,6 +12,7 @@ mod temporary_redirect_resource;
|
||||||
pub use self::article_revision_resource::ArticleRevisionResource;
|
pub use self::article_revision_resource::ArticleRevisionResource;
|
||||||
pub use self::article_resource::ArticleResource;
|
pub use self::article_resource::ArticleResource;
|
||||||
pub use self::changes_resource::{ChangesLookup, ChangesResource};
|
pub use self::changes_resource::{ChangesLookup, ChangesResource};
|
||||||
|
pub use self::diff_resource::{DiffLookup, DiffResource};
|
||||||
pub use self::new_article_resource::NewArticleResource;
|
pub use self::new_article_resource::NewArticleResource;
|
||||||
pub use self::search_resource::SearchLookup;
|
pub use self::search_resource::SearchLookup;
|
||||||
pub use self::sitemap_resource::SitemapResource;
|
pub use self::sitemap_resource::SitemapResource;
|
||||||
|
|
|
@ -47,6 +47,7 @@ lazy_static! {
|
||||||
pub struct WikiLookup {
|
pub struct WikiLookup {
|
||||||
state: State,
|
state: State,
|
||||||
changes_lookup: ChangesLookup,
|
changes_lookup: ChangesLookup,
|
||||||
|
diff_lookup: DiffLookup,
|
||||||
search_lookup: SearchLookup,
|
search_lookup: SearchLookup,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -78,9 +79,10 @@ fn asset_lookup(path: &str) -> FutureResult<Option<BoxResource>, Box<::std::erro
|
||||||
impl WikiLookup {
|
impl WikiLookup {
|
||||||
pub fn new(state: State, show_authors: bool) -> WikiLookup {
|
pub fn new(state: State, show_authors: bool) -> WikiLookup {
|
||||||
let changes_lookup = ChangesLookup::new(state.clone(), show_authors);
|
let changes_lookup = ChangesLookup::new(state.clone(), show_authors);
|
||||||
|
let diff_lookup = DiffLookup::new(state.clone());
|
||||||
let search_lookup = SearchLookup::new(state.clone());
|
let search_lookup = SearchLookup::new(state.clone());
|
||||||
|
|
||||||
WikiLookup { state, changes_lookup, search_lookup }
|
WikiLookup { state, changes_lookup, diff_lookup, search_lookup }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn revisions_lookup(&self, path: &str, _query: Option<&str>) -> <Self as Lookup>::Future {
|
fn revisions_lookup(&self, path: &str, _query: Option<&str>) -> <Self as Lookup>::Future {
|
||||||
|
@ -143,6 +145,8 @@ impl WikiLookup {
|
||||||
self.by_id_lookup(tail, query),
|
self.by_id_lookup(tail, query),
|
||||||
("_changes", None) =>
|
("_changes", None) =>
|
||||||
Box::new(self.changes_lookup.lookup(query)),
|
Box::new(self.changes_lookup.lookup(query)),
|
||||||
|
("_diff", None) =>
|
||||||
|
Box::new(self.diff_lookup.lookup(query)),
|
||||||
("_new", None) =>
|
("_new", None) =>
|
||||||
Box::new(finished(Some(Box::new(NewArticleResource::new(self.state.clone(), None)) as BoxResource))),
|
Box::new(finished(Some(Box::new(NewArticleResource::new(self.state.clone(), None)) as BoxResource))),
|
||||||
("_revisions", Some(tail)) =>
|
("_revisions", Some(tail)) =>
|
||||||
|
|
14
templates/diff.html
Normal file
14
templates/diff.html
Normal file
|
@ -0,0 +1,14 @@
|
||||||
|
<div class="container">
|
||||||
|
<header>
|
||||||
|
<h1>Diff</h1>
|
||||||
|
</header>
|
||||||
|
|
||||||
|
<article>
|
||||||
|
<pre>{{#lines}}{{#.removed}}<div class="removed">{{.}}
|
||||||
|
</div>{{/.removed}}{{#.same}}<div class="same">{{.}}
|
||||||
|
</div>{{/.same}}{{#.added}}<div class="added">{{.}}
|
||||||
|
</div>{{/.added}}{{/lines}}</pre>
|
||||||
|
</article>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
{{>footer/default.html}}
|
Loading…
Reference in a new issue