1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
use std::path::Path;
use std::io::{Result as IoResult, Error, ErrorKind};

use std::collections::{HashMap, VecDeque};

pub type Ltsv = Vec<Record>;

#[derive(Debug)]
pub struct Record {
    map: HashMap<String, String>,
    order: VecDeque<String>,
}

impl Record {
    pub fn new() -> Self {
        Record{
            map: HashMap::new(),
            order: VecDeque::new(),
        }
    }
    pub fn insert(&mut self, label: String, value: String) -> Option<String> {
        self.order.push_back(label.clone());
        self.map.insert(label, value)
    }
}

struct LtsvError(String);

impl From<LtsvError> for IoResult<Ltsv> {
    fn from(e: LtsvError) -> Self {
        Result::Err(Error::new(
            ErrorKind::Other,
            e.0,
        ))
    }
}

/// Load ltsv data from path.
pub fn from_path<P: AsRef<Path>>(path: P) -> IoResult<Ltsv> {
    use std::fs::File;
    use std::io::{BufReader, Read};
    use std::iter::FromIterator;

    let mut reader = BufReader::new(File::open(path)?);
    let mut buf = String::new();
    let _ = reader.read_to_string(&mut buf)?;
    let content = buf.trim();

    if content.is_empty() {
        return Ok(vec![]);
    }

    let mut error = None;
    let data: Ltsv = buf.trim().split("\n").enumerate().map(|(l, line)|{
        let mut order = VecDeque::new();
        let iter = line.split("\t").filter_map(|f|{
            let split: Vec<&str> = f.splitn(2, ':').collect();
            if split.len() == 2 {
                let (label, value) = (split[0].to_owned(), split[1].to_owned());
                order.push_back(label.clone());
                return Some((label, value));
            } else if error.is_none() {
                error = Some(LtsvError(format!("Incorrect file format found at line`{}`", l)));
            }
            None
        });
        Record{
            map: HashMap::from_iter(iter),
            order
        }
    }).collect();

    if let Some(error) = error {
        error.into()
    } else {
        Ok(data)
    }
}

pub fn append<P: AsRef<Path>>(path: P, mut data: Record) -> IoResult<()> {
    use std::fs::OpenOptions;
    use std::io::{BufWriter, Write};

    let mut record_str = vec![];
    while let Some(label) = data.order.pop_front() {
        if let Some(value) = data.map.remove(&label) {
            record_str.push(format!("{}:{}", label, value))
        }
    }
    let record = record_str.join("\t") + "\n";

    let mut file = BufWriter::new(
        OpenOptions::new()
            .create(true)
            .append(true)
            .open(path)?
    );
    file.write(record.as_bytes())?;

    Ok(())
}

/// Save as a new ltsv file.
///
/// # Example
///
/// ```
/// let data = Ltsv::new();
/// let record = Record::new();
/// data.push(record);
///
/// save(data, "sample.ltsv").unwrap();
/// ```
pub fn save<P: AsRef<Path>>(path: P, data: Ltsv) -> IoResult<()> {
    use std::fs::OpenOptions;
    use std::io::{BufWriter, Write};

    let mut file = BufWriter::new(
        OpenOptions::new()
            .create(true)
            .append(true)
            .open(path)?
    );

    for mut d in data {
        let mut record_str = vec![];
        while let Some(label) = d.order.pop_front() {
            if let Some(value) = d.map.remove(&label) {
                record_str.push(format!("{}:{}", label, value))
            }
        }
        let record = record_str.join("\t") + "\n";
        file.write(record.as_bytes())?;
    }

    Ok(())
}