libpijul 0.12.0

A patch-based distributed version control system, easy to use and fast.
use super::{Change, ChangeContext, NewEdge, Patch, PatchHeader, UnsignedPatch};
use base64;
use serde_json;
use std::collections::HashMap;
use std::str::from_utf8;
use {EdgeFlags, Hash, Key, LineId};

#[derive(Debug, Serialize, Deserialize, Clone)]
pub struct PrettyPatch {
    pub header: PatchHeader,
    pub dependencies: Vec<String>,
    pub changes: Vec<PrettyChange>,
}

impl Patch {
    pub fn to_pretty(&self) -> PrettyPatch {
        match *self {
            Patch::Signed0 | Patch::Unsigned0 => {
                panic!("refusing to interact with old patch version")
            }
            Patch::Unsigned(ref patch) => patch.to_pretty(),
        }
    }
}

impl UnsignedPatch {
    pub fn to_pretty(&self) -> PrettyPatch {
        let deps = self
            .dependencies
            .iter()
            .map(|x| x.clone())
            .zip(1..)
            .collect();
        PrettyPatch {
            header: self.header.clone(),
            dependencies: self.dependencies.iter().map(|x| x.to_base58()).collect(),
            changes: self.changes.iter().map(|x| x.to_pretty(&deps)).collect(),
        }
    }
}

impl PrettyPatch {
    pub fn to_patch(&self) -> UnsignedPatch {
        let deps_vec: Vec<_> = self
            .dependencies
            .iter()
            .map(|x| Hash::from_base58(x).unwrap())
            .collect();

        UnsignedPatch {
            header: self.header.clone(),
            dependencies: self
                .dependencies
                .iter()
                .map(|x| Hash::from_base58(x).unwrap())
                .collect(),
            changes: self
                .changes
                .iter()
                .map(|x| x.to_change(&deps_vec))
                .collect(),
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub enum PrettyChange {
    NewNodes {
        up_context: Vec<String>,
        down_context: Vec<String>,
        flag: u8,
        line_num: u64,
        nodes: Vec<serde_json::Value>,
        inode: String,
    },
    NewEdges {
        previous: u8,
        flag: u8,
        edges: Vec<PrettyNewEdge>,
        inode: String,
    },
}

#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct PrettyNewEdge {
    pub from: String,
    pub to: String,
    pub introduced_by: usize,
}

impl PrettyNewEdge {
    fn to_new_edge(&self, deps: &[Hash]) -> NewEdge {
        NewEdge {
            from: read_key(deps, &self.from),
            to: read_key(deps, &self.to),
            introduced_by: Some(deps[self.introduced_by].clone()),
        }
    }
}

impl NewEdge {
    fn to_pretty(&self, deps: &HashMap<Hash, usize>) -> PrettyNewEdge {
        PrettyNewEdge {
            from: print_key(deps, &self.from),
            to: print_key(deps, &self.to),
            introduced_by: if let Some(ref i) = self.introduced_by {
                *deps.get(i).unwrap()
            } else {
                0
            },
        }
    }
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Filename {
    name: String,
    flag: u16,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
struct Binary {
    binary: String,
}

fn print_key(deps: &HashMap<Hash, usize>, key: &Key<Option<Hash>>) -> String {
    if let Some(ref k) = key.patch {
        if *k == Hash::None {
            String::new()
        } else {
            debug!("print key: {:?} {:?}", k, deps);
            if let Some(dep) = deps.get(&k) {
                format!("{}.{}", dep, key.line.to_base58())
            } else {
                format!("{}", key.line.to_base58())
            }
        }
    } else {
        format!("{}", key.line.to_base58())
    }
}

fn read_key(deps: &[Hash], key: &str) -> Key<Option<Hash>> {
    let mut it = key.split('.');
    match (it.next(), it.next()) {
        (Some(a), Some(b)) => Key {
            patch: Some(deps[a.parse::<usize>().unwrap()].clone()),
            line: LineId::from_base58(b).unwrap(),
        },
        (Some(a), None) => Key {
            patch: None,
            line: LineId::from_base58(a).unwrap(),
        },
        _ => Key {
            patch: Some(Hash::None),
            line: LineId(0),
        },
    }
}

impl Change<ChangeContext<Hash>> {
    fn to_pretty(&self, deps: &HashMap<Hash, usize>) -> PrettyChange {
        match *self {
            Change::NewNodes {
                ref up_context,
                ref down_context,
                flag,
                line_num,
                ref nodes,
                ref inode,
            } => PrettyChange::NewNodes {
                up_context: up_context.iter().map(|x| print_key(&deps, x)).collect(),
                down_context: down_context.iter().map(|x| print_key(&deps, x)).collect(),
                flag: flag.bits(),
                line_num: line_num.0,
                nodes: if flag.contains(EdgeFlags::FOLDER_EDGE) {
                    nodes
                        .iter()
                        .map(|x| {
                            if x.len() >= 2 {
                                serde_json::to_value(Filename {
                                    name: from_utf8(&x[2..]).unwrap().to_string(),
                                    flag: ((x[0] as u16) << 8) | (x[1] as u16),
                                })
                                .unwrap()
                            } else {
                                serde_json::to_value("").unwrap()
                            }
                        })
                        .collect()
                } else {
                    nodes
                        .iter()
                        .map(|x| {
                            if let Ok(x) = from_utf8(x) {
                                serde_json::to_value(x).unwrap()
                            } else {
                                serde_json::to_value(Binary {
                                    binary: base64::encode(x),
                                })
                                .unwrap()
                            }
                        })
                        .collect()
                },
                inode: print_key(deps, inode),
            },
            Change::NewEdges {
                previous,
                flag,
                ref edges,
                ref inode,
            } => PrettyChange::NewEdges {
                previous: previous.bits(),
                flag: flag.bits(),
                inode: print_key(deps, inode),
                edges: edges.iter().map(|x| x.to_pretty(deps)).collect(),
            },
        }
    }
}

impl PrettyChange {
    pub fn to_change(&self, deps: &[Hash]) -> Change<ChangeContext<Hash>> {
        match *self {
            PrettyChange::NewNodes {
                ref up_context,
                ref down_context,
                flag,
                line_num,
                ref nodes,
                ref inode,
            } => Change::NewNodes {
                up_context: up_context.iter().map(|x| read_key(deps, x)).collect(),
                down_context: down_context.iter().map(|x| read_key(deps, x)).collect(),
                flag: EdgeFlags::from_bits_truncate(flag),
                line_num: LineId(line_num),
                nodes: nodes
                    .iter()
                    .map(|x| {
                        let filename: Result<Filename, _> = serde_json::from_value(x.clone());
                        if let Ok(filename) = filename {
                            let mut v = Vec::new();
                            v.push((filename.flag >> 8) as u8);
                            v.push(filename.flag as u8);
                            v.extend(filename.name.as_bytes());
                            return v;
                        }
                        let binary: Result<Binary, _> = serde_json::from_value(x.clone());
                        if let Ok(binary) = binary {
                            return base64::decode(&binary.binary).unwrap();
                        }
                        let string: String = serde_json::from_value(x.clone()).unwrap();
                        string.into()
                    })
                    .collect(),
                inode: read_key(deps, inode),
            },
            PrettyChange::NewEdges {
                previous,
                flag,
                ref edges,
                ref inode,
            } => Change::NewEdges {
                previous: EdgeFlags::from_bits_truncate(previous),
                flag: EdgeFlags::from_bits_truncate(flag),
                inode: read_key(deps, inode),
                edges: edges.iter().map(|x| x.to_new_edge(deps)).collect(),
            },
        }
    }
}