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
/// Generic `Iterator` over implementor's of
/// [`Entry`](trait.Entry.html)'s.
///
/// # Examples
///
/// #### Iterate over /etc/passwd printing usernames
///
/// ```
/// use std::path::Path;
/// use pgs_files::passwd::PasswdEntry;
/// use pgs_files::Entries;
///
/// for entry in Entries::<PasswdEntry>::new(&Path::new("/etc/passwd")) {
///     println!("{}", entry.name);
/// }
/// ```

use std::{
    io::{BufRead,BufReader,Write},
    fs::{OpenOptions,File},
    path::Path,
    marker::PhantomData,
    num::ParseIntError,
};

pub struct Entries<T> {
    path: String,
    cursor: BufReader<File>,
    marker: PhantomData<T>,
}

impl<T> Entries<T> {
    pub fn new(file_path: &str) -> Entries<T> {
        let file = Path::new(&file_path);
        if ! file.exists()  {
            File::create(file).unwrap_or_else(|e|{
               eprintln!("Error file: {}",e);
               std::process::exit(2)
            });
        }
        let reader = BufReader::new(File::open(file).ok().unwrap());
        Entries {
            path: file_path.to_owned(),
            cursor: reader,
            marker: PhantomData,
        }
    }
    pub fn append(&self, entry: String) -> anyhow::Result<()> {
        let file = Path::new(&self.path);
        if ! file.exists()  {
            std::fs::write(file,format!("{}\n",entry).as_bytes())?;
        } else {
            let target_file = OpenOptions::new()
                .write(true)
                .append(true) // This is needed to append to file
                .open(&file);
            if let Ok(mut file) = target_file {
                file.write_all( format!("{}\n",entry).as_bytes())?;
            }
        }
        Ok(())
    }
    pub fn write(&self, entries: &Vec<String>) -> anyhow::Result<()> {
        let file = Path::new(&self.path);
        std::fs::write(file,format!("{}\n",entries.clone().join("\n")).as_bytes())?;
        // let target_file = OpenOptions::new()
        //     .write(true)
        //     .open(&file);
        // if let Ok(mut file) = target_file {
        //     file.write_all(entries.clone().join("\n").as_bytes())?;
        // }
        Ok(())
    }
}


impl<T: Entry> Iterator for Entries<T> {

    type Item = T;

    fn next(&mut self) -> Option<T> {
        let mut line = String::new();
        loop {
            // We might need to make multiple loops to drain off
            // comment lines. Start with an empty string per loop.
            line.clear();
            match self.cursor.read_line(&mut line){
                Ok(0) => return None,
                Ok(_) => (),
                _     => return None,
            }

            if line.starts_with("#") {
                continue;
            }

            match T::from_line(&line) {
                Ok(entry) => return Some(entry),
                // Parse Error. Just ignore this entry.
                _         => (),
            }
        }
    }

}

/// A Trait to represent an entry of data from an
/// /etc/{`passwd`,`group`,`shadow`} file.
pub trait Entry: Sized {
    fn from_line(line: &str) -> anyhow::Result<Self, ParseIntError>;
}