1
0
mirror of https://github.com/lise-henry/crowbook synced 2024-09-26 00:30:52 +02:00
crowbook/src/lib/grammar_check.rs
2017-10-09 00:31:42 +02:00

158 lines
5.2 KiB
Rust

// Copyright (C) 2016, 2017 Élisabeth HENRY.
//
// This file is part of Crowbook.
//
// Crowbook is free software: you can redistribute it and/or modify
// it under the terms of the GNU Lesser General Public License as published
// by the Free Software Foundation, either version 2.1 of the License, or
// (at your option) any later version.
//
// Crowbook is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Lesser General Public License for more details.
//
// You should have received a copy of the GNU Lesser General Public License
// along with Crowbook. If not, see <http://www.gnu.org/licenses/>.
use serde_json;
use hyper;
use hyper::Client;
use url::form_urlencoded;
use rayon::prelude::*;
use std::io::Read;
use text_view::view_as_text;
use text_view::insert_annotation;
use token::Token;
use token::Data;
use error::{Error, Result, Source};
/// Represents a grammar error from language tool
///
/// Note: lots of fields are missing
#[derive(Deserialize, Debug)]
struct GrammarError {
pub message: String,
pub offset: usize,
pub length: usize,
pub short_message: Option<String>,
pub issue_type: Option<String>,
}
/// Contains a list of matches to errors
///
/// Corresponds to the JSON that LanguageTool-server sends back
///
/// Note: lots of fields are missing
#[derive(Deserialize, Debug)]
struct GrammarCheck {
pub matches: Vec<GrammarError>,
}
/// GrammarChecker
pub struct GrammarChecker {
lang: String,
port: usize,
}
impl GrammarChecker {
/// Initialize the grammarchecker
pub fn new<S: Into<String>>(port: usize, lang: S) -> Result<GrammarChecker> {
let checker = GrammarChecker {
lang: lang.into(),
port: port,
};
let res = Client::new()
.get(&format!("http://localhost:{}/v2/languages", port))
.send()
.map_err(|e| {
Error::grammar_check(Source::empty(),
lformat!("could not connect to language tool server: {error}",
error = e))
})?;
if res.status != hyper::Ok {
return Err(Error::grammar_check(Source::empty(),
lformat!("server didn't respond with a OK status \
code")));
}
Ok(checker)
}
/// Send a query to LanguageTools server and get back a list of errors
fn check(&self, text: &str) -> Result<GrammarCheck> {
let query: String = form_urlencoded::Serializer::new(String::new())
.append_pair("language", &self.lang)
.append_pair("text", text)
.finish();
let client = Client::new();
let mut res = client.post(&format!("http://localhost:{}/v2/check", self.port))
.body(&query)
.send()
.map_err(|e| {
Error::grammar_check(Source::empty(),
lformat!("could not send request to server: {error}",
error = e))
})?;
if res.status != hyper::Ok {
return Err(Error::grammar_check(Source::empty(),
lformat!("server didn't respond with a OK status \
code")));
}
let mut s = String::new();
res.read_to_string(&mut s)
.map_err(|e| {
Error::grammar_check(Source::empty(),
lformat!("could not read response: {error}", error = e))
})?;
let reponse: GrammarCheck = serde_json::from_str(&s)
.map_err(|e| {
Error::default(Source::empty(),
lformat!("could not decode JSON: {error}", error = e))
})?;
Ok(reponse)
}
}
impl GrammarChecker {
/// Check the grammar in a vector of tokens.
///
/// This modifies the AST
pub fn check_chapter(&self, tokens: &mut Vec<Token>) -> Result<()> {
let res = tokens.par_iter_mut()
.map(|token| {
match *token {
Token::Paragraph(ref mut v) |
Token::Header(_, ref mut v) |
Token::BlockQuote(ref mut v) |
Token::List(ref mut v) |
Token::OrderedList(_, ref mut v) => {
let check = self.check(&view_as_text(v))?;
for error in check.matches {
insert_annotation(v,
&Data::GrammarError(error.message.clone()),
error.offset,
error.length);
}
Ok(())
},
_ => Ok(()),
}
})
.find_any(|r| r.is_err());
if let Some(err) = res {
err
} else {
Ok(())
}
}
}