use std::io;
use std::io::prelude::*;
use std::iter::Peekable;
use std::marker::PhantomData;
use itertools::Itertools;
use super::lcs_diff::*;
use super::Regex;
use hunked::Hunk;
use conf::{Conf, CharacterClassExpansion};
#[derive(PartialEq, Clone, Debug)]
pub struct Word<'l>(&'l [u8]);
pub trait Writeable {
fn write_to(&self, out : &mut Write) -> io::Result<()>;
}
impl Writeable for u8 {
fn write_to(&self, out : &mut Write) -> io::Result<()> {
let mut w = vec![];
w.push(*self);
out.write_all(&w[..])
}
}
impl<'l> Writeable for Word<'l> {
fn write_to(&self, out : &mut Write) -> io::Result<()> {
out.write_all(&self.0[..])
}
}
impl<'a, T> Writeable for &'a [T]
where
T: Writeable,
{
fn write_to(&self, out : &mut Write) -> io::Result<()> {
for w in self.iter() {
w.write_to(out).unwrap()
}
Ok (())
}
}
impl<T> Writeable for Vec<T>
where
T : Writeable,
{
fn write_to(&self, out : &mut Write) -> io::Result<()> {
(&self[..]).write_to(out)
}
}
#[cfg_attr(feature = "cargo-clippy", allow(enum_variant_names))]
enum WordDiffState {
ShowingCommon,
ShowingRemoves,
ShowingAdds,
}
fn extend(line : &mut Vec<u8>, s : &str) {
line.extend(s.bytes())
}
#[derive(Clone, Copy, PartialEq, Debug)]
pub enum CharacterClass<T : PartialEq + Clone> {
White,
Digit,
Alpha,
Word,
Any (PhantomData<T>),
}
impl<'l> Into<CharacterClass<Word<'l>>> for CharacterClass<u8> {
fn into(self) -> CharacterClass<Word<'l>> {
use self::CharacterClass::*;
match self {
White => White,
Digit => Digit,
Alpha => Alpha,
Word => Word,
Any (_) => Any (PhantomData),
}
}
}
pub trait HasCharacterClass {
type Item : Clone + PartialEq;
fn cc(&self) -> CharacterClass<Self::Item>;
fn len(&self) -> usize;
}
impl HasCharacterClass for u8 {
type Item = u8;
fn cc(&self) -> CharacterClass<u8> {
use self::CharacterClass::*;
if self.is_ascii_alphabetic() {
Alpha
} else if self.is_ascii_digit() {
Digit
} else if self.is_ascii_whitespace() {
White
} else {
Any (PhantomData)
}
}
fn len(&self) -> usize {
1
}
}
impl<'l> HasCharacterClass for Word<'l> {
type Item = Word<'l>;
fn cc(&self) -> CharacterClass<Word<'l>> {
let mut chars = self.0.iter();
let cc = match chars.next() {
None => CharacterClass::Any (PhantomData),
Some (ch) => ch.cc()
};
let cc : CharacterClass<u8> = chars.fold(cc, |cc, ch| cc.merge(&ch.cc()));
cc.into()
}
fn len(&self) -> usize {
self.0.iter().count()
}
}
impl<T> CharacterClass<T>
where
T: PartialEq + Clone + HasCharacterClass<Item=T>
{
fn merge(&self, other : &Self) -> Self {
use self::CharacterClass::*;
match other {
White => {
match self {
White => White,
_ => Any (PhantomData),
}
},
Digit => {
match self {
Digit => Digit,
Alpha | Word => Word,
_ => Any (PhantomData)
}
},
Alpha => {
match self {
Alpha => Alpha,
Digit | Word => Word,
_ => Any (PhantomData),
}
},
Word => {
match self {
Alpha | Digit | Word => Word,
_ => Any (PhantomData),
}
},
_ => Any (PhantomData),
}
}
fn accepts(&self, el : &T) -> bool {
let ncc = self.merge(&el.cc());
&ncc == self
}
fn write(&self, out : &mut Write) -> io::Result<()>{
use self::CharacterClass::*;
match self {
White => out.write_all(b"\\s"),
Digit => out.write_all(b"\\d"),
Alpha => out.write_all(b"\\a"),
Word => out.write_all(b"\\w"),
Any (_) => out.write_all(b".")
}
}
}
fn res_data<T : PartialEq + Clone + HasCharacterClass>(res : &DiffResult<T>) -> T {
match res {
DiffResult::Common (el) => el.data.clone(),
DiffResult::Added (el) => el.data.clone(),
DiffResult::Removed (el) => el.data.clone(),
}
}
fn is_common<T : PartialEq + Clone>(d : &DiffResult<T>) -> bool {
match d {
DiffResult::Common (_) => true,
_ => false,
}
}
fn narrow_do_common<'a, I, T>(out : &mut Write,
prev_cc : Option<CharacterClass<T>>,
mut acc : Vec<T>,
mut items : Peekable<I>) -> io::Result<()>
where
I : Iterator<Item=&'a DiffResult<T>>,
T: PartialEq + Clone + HasCharacterClass<Item=T> + Writeable + 'a,
Peekable<I> : Clone,
{
{
let common = items.peeking_take_while(|d| is_common(d)).map(res_data);
acc.extend(common);
}
narrow_do_differences(out, prev_cc, acc, items)
}
fn skip_common<'a, T, I>(cc : &CharacterClass<T>, items : &mut Peekable<I>)
where
I : Iterator<Item=&'a DiffResult<T>>,
T: PartialEq + Clone + HasCharacterClass<Item=T> + 'a,
{
items.peeking_take_while(|d| {
match d {
DiffResult::Common (_) => {
match cc {
CharacterClass::Any (_) => false,
ref cc => cc.accepts(&res_data(d)),
}
},
_ => false,
}
}).count();
}
fn narrow_do_differences<'a, T, I>(out : &mut Write,
prev_cc : Option<CharacterClass<T>>,
context_pre : Vec<T>,
mut items : Peekable<I>) -> io::Result<()>
where
I : Iterator<Item=&'a DiffResult<T>>,
T: PartialEq + Clone + HasCharacterClass<Item=T> + Writeable + 'a,
Peekable<I> : Clone,
{
let first = items.next();
let first = match first {
None => {
context_pre.write_to(out)?;
return Ok (())
},
Some (d) => {
assert!(!is_common(d));
d
}
};
let cc = res_data(first).cc();
let cc = items.peeking_take_while(|d| !is_common(d))
.map(res_data).fold(cc, |cc : CharacterClass<T>, ch| cc.merge(&ch.cc()));
let n_unsummarizable = context_pre.iter().rev().skip_while(|ch| {
match cc {
CharacterClass::Any (_) => false,
ref cc => cc.accepts(*ch),
}
}).count();
(&context_pre[..n_unsummarizable]).write_to(out)?;
let print_cc = match prev_cc {
Some (ref prev_cc) if prev_cc == &cc => n_unsummarizable != 0,
_ => true
};
if print_cc {
cc.write(out)?;
out.write_all(b"+")?;
}
skip_common(&cc, &mut items);
narrow_do_common(out, Some (cc), vec![], items)
}
fn wide_do_differences<'a, T, I>(out : &mut Write,
mut items : Peekable<I>) -> io::Result<()>
where
I : Iterator<Item=&'a DiffResult<T>>,
T: PartialEq + Clone + HasCharacterClass<Item=T> + Writeable + 'a,
Peekable<I> : Clone,
{
let mut nadded = 0;
let mut nremoved = 0;
let cc = {
let mut count = |d : &DiffResult<T>| {
match d {
DiffResult::Common (_) => unreachable!(),
DiffResult::Added (el) => nadded += el.data.len(),
DiffResult::Removed (el) => nremoved += el.data.len(),
}
};
let cc = match items.next() {
None => return Ok (()),
Some (d) => {
count(d);
res_data(d).cc()
},
};
items.peeking_take_while(|d| !is_common(d))
.fold(cc, |cc : CharacterClass<T>, d| {
count(d);
cc.merge(&res_data(d).cc())
})
};
cc.write(out)?;
if nadded == nremoved {
write!(out, "{{{}}}", nadded)?;
} else {
write!(out, "{{{},{}}}", nremoved, nadded)?;
}
wide_do_common(out, items)
}
fn wide_do_common<'a, T, I>(out : &mut Write,
mut items : Peekable<I>) -> io::Result<()>
where
I : Iterator<Item=&'a DiffResult<T>>,
T: PartialEq + Clone + HasCharacterClass<Item=T> + Writeable + 'a,
Peekable<I> : Clone,
{
let common : Vec<T> = items.peeking_take_while(|d| is_common(d)).map(res_data).collect();
common.write_to(out)?;
wide_do_differences(out, items)
}
pub fn intra_line_write_cc<T>(hunk : &Hunk<T>,
expansion : CharacterClassExpansion,
_ : &Conf, _ : &[T], _ : &[T],
out : &mut Write) -> io::Result<()>
where
T: PartialEq + Clone + HasCharacterClass<Item=T> + Writeable,
{
use CharacterClassExpansion::*;
let items = hunk.items.iter().peekable();
match expansion {
Narrow => {
narrow_do_common(out, None, vec![], items)
},
Wide => {
wide_do_common(out, items)
}
}
}
pub fn intra_line_write_wdiff<T>(hunk : &Hunk<T>, _ : &Conf,
_ : &[T], _ : &[T],
out : &mut Write) -> io::Result<()>
where
T: PartialEq + Clone + Writeable,
{
use self::WordDiffState::*;
let mut line : Vec<u8> = vec![];
let mut state = ShowingCommon;
for d in &hunk.items {
state = match state {
ShowingCommon => {
match d {
DiffResult::Common (el) => {
el.data.write_to(&mut line)?;
ShowingCommon
},
DiffResult::Added (el) => {
extend(&mut line, "{+");
el.data.write_to(&mut line)?;
ShowingAdds
},
DiffResult::Removed(el) => {
extend(&mut line, "{-");
el.data.write_to(&mut line)?;
ShowingRemoves
}
}
},
ShowingAdds => {
match d {
DiffResult::Common (el) => {
line.push(b'}');
el.data.write_to(&mut line)?;
ShowingCommon
},
DiffResult::Added (el) => {
el.data.write_to(&mut line)?;
ShowingAdds
},
DiffResult::Removed (el) => {
line.push(b'}');
extend(&mut line, "{-");
el.data.write_to(&mut line)?;
ShowingRemoves
},
}
},
ShowingRemoves => {
match d {
DiffResult::Common (el) => {
line.push(b'}');
el.data.write_to(&mut line)?;
ShowingCommon
},
DiffResult::Added (el) => {
line.push(b'}');
extend(&mut line, "{+");
el.data.write_to(&mut line)?;
ShowingAdds
},
DiffResult::Removed (el) => {
el.data.write_to(&mut line)?;
ShowingRemoves
}
}
},
}
}
match state {
ShowingAdds | ShowingRemoves => line.push(b'}'),
ShowingCommon => (),
};
out.write_all(&line)?;
Ok (())
}
pub fn tokenize<'l>(line : &'l [u8]) -> Vec<Word<'l>> {
let re = Regex::new(r"\b").unwrap();
let mut words : Vec<Word<'l>> = re.split(line).map(|w| Word(w)).collect();
let need_split =
if words.len() > 0 {
let lastw = &words[words.len() - 1];
let wlen = lastw.len();
if wlen > 1 { let lastc = lastw.0[wlen - 1];
if lastc == b'\n' {
Some (wlen)
} else {
None
}
} else {
None
}
} else {
None
};
if let Some (wlen) = need_split {
words.pop();
let len = line.len();
words.push(Word(&line[(len - wlen -1)..(len - 2)]));
words.push(Word(&line[(len - 1)..len]));
}
words
}