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

use std;
use std::fmt;

use std::fs::File;
use std::io::Read;

#[derive(Debug)]
pub enum FileLoadError {
    FileLoadFailure(String, std::io::Error),
    Utf8Error(String, std::string::FromUtf8Error)
}

#[derive(Debug,Eq,PartialEq,Ord,PartialOrd,Hash,Copy,Clone)]
pub struct FileID {
    value: usize
}

impl FileID {
    pub fn new(value: usize) -> FileID { FileID { value: value } }
}

impl fmt::Display for FileID {
    fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
        write!(f, "FileID({})", self.value)
    }    
}

pub struct SourceFile {
    id: FileID,
    name: String,
    contents: String
}

impl SourceFile {
    pub fn id(&self) -> FileID {
        self.id
    }

    pub fn name(&self) -> &str {
        &self.name
    }

    pub fn contents(&self) -> &str {
        &self.contents
    }
}

pub struct SourceFiles {
    working_dir: String,
    files: Vec<SourceFile>
}

pub fn load_bytes(path: &str) -> Result<Vec<u8>, FileLoadError> {
    let mut file = File::open(path).map_err(|err| FileLoadError::FileLoadFailure(path.to_string(), err))?;
    let mut bytes = vec![];
    file.read_to_end(&mut bytes).map_err(|err| FileLoadError::FileLoadFailure(path.to_string(), err))?;
    Ok(bytes)
}

pub fn load_file(path: &str) -> Result<String, FileLoadError> {
    let bytes = load_bytes(path)?;
    let text = String::from_utf8(bytes).map_err(|err| FileLoadError::Utf8Error(path.to_string(), err))?;
    Ok(text)
}

impl SourceFiles {
    pub fn new(working_dir: String) -> SourceFiles {
        SourceFiles {
            working_dir: working_dir,
            files: vec![]
        }
    }

    pub fn for_id(&self, id: FileID) -> Option<&SourceFile> {
        if id.value < self.files.len() {
            Some(&self.files[id.value])
        } else {
            None
        }
    }

    pub fn get_id(&self, filename: &str) -> Option<FileID> {
        for (idx, file) in self.files.iter().enumerate() {
            if &file.name == filename {
                return Some(FileID { value: idx })
            }
        }
        None
    }

    pub fn get(&self, filename: &str) -> Option<&str> {
        match self.get_id(filename) {
            Some(id) => Some(&self.files[id.value].contents),
            None => None
        }
    }

    pub fn path(&self, filename: &str) -> String {
        if self.working_dir.is_empty() {
            filename.to_owned()
        } else {
            format!("{}/{}", self.working_dir, filename)
        }
    }

    pub fn load(&mut self, filename: &str) -> Result<&SourceFile,FileLoadError> {
        let path = self.path(filename);
        match self.get_id(&path) {
            Some(id) => Ok(&self.files[id.value]),
            None => {
                let contents = load_file(&path)?;
                let id_value = self.files.len();
                let file = SourceFile {
                    id: FileID { value: id_value },
                    name: filename.to_owned(),
                    contents: contents
                };
                self.files.push(file);
                Ok(&self.files[id_value])
            }
        }
    }
}