artifact_app/user/
save.rs

1//! loadrs
2//! loading of raw artifacts from files and text
3
4use toml;
5use difference::Changeset;
6use std::collections::BTreeMap;
7
8use dev_prefix::*;
9use types::*;
10use user::types::*;
11
12// Public Struct
13
14/// struct for representing a project as just a collection of
15/// Path and String values, used for loading/formatting/saving files
16#[derive(Debug, PartialEq)]
17pub struct ProjectText {
18    pub origin: PathBuf,
19    pub files: HashMap<PathBuf, String>,
20}
21
22/// used for finding the difference between files in a project
23pub enum PathDiff {
24    DoesNotExist,
25    NotUtf8,
26    Changeset(Changeset),
27    None,
28}
29
30impl Default for ProjectText {
31    fn default() -> ProjectText {
32        ProjectText {
33            origin: PathBuf::from("INVALID-ORIGIN"),
34            files: HashMap::default(),
35        }
36    }
37}
38
39impl ProjectText {
40    /// convert a `Project` -> `ProjectText`
41    pub fn from_project(project: &Project) -> Result<ProjectText> {
42        let mut files: HashMap<PathBuf, BTreeMap<String, UserArtifact>> = HashMap::new();
43
44        // we just go through each item, growing `files` as necessary
45        // TODO: how to make the equivalent of a yielding function,
46        // to not copy/paste the path filtering code.
47        for (name, artifact) in &project.artifacts {
48            // insert artifact into a table
49            if !files.contains_key(&artifact.def) {
50                files.insert(artifact.def.clone(), BTreeMap::new());
51            }
52            let tbl = files.get_mut(&artifact.def).unwrap();
53
54            let partof = {
55                let mut auto_partof = name.named_partofs();
56                if !name.is_root() {
57                    auto_partof.push(name.parent().expect("no parent"));
58                }
59                let auto_partof: HashSet<Name> = HashSet::from_iter(auto_partof.drain(0..));
60                let mut strs = artifact
61                    .partof
62                    .iter()
63                    .filter(|p| !auto_partof.contains(p))
64                    .map(|p| p.raw.clone())
65                    .collect::<Vec<_>>();
66                if strs.is_empty() {
67                    None
68                } else if strs.len() == 1 {
69                    let s = strs.drain(0..).next().unwrap();
70                    Some(UserPartof::Single(s))
71                } else {
72                    strs.sort();
73                    Some(UserPartof::Multi(strs))
74                }
75            };
76
77            let raw = UserArtifact {
78                partof: partof,
79                text: if artifact.text.is_empty() {
80                    None
81                } else {
82                    Some(artifact.text.clone())
83                },
84                done: if let Done::Defined(ref d) = artifact.done {
85                    Some(d.clone())
86                } else {
87                    None
88                },
89            };
90            tbl.insert(name.raw.clone(), raw);
91        }
92
93        // convert Values to text
94        let mut text: HashMap<PathBuf, String> = HashMap::new();
95        for (p, v) in files.drain() {
96            let mut s = String::new();
97            v.serialize(&mut toml::Serializer::pretty(&mut s))
98                .expect("serialize");
99            text.insert(p, s);
100        }
101
102        // files that exist but have no data need to also be
103        // written
104        for p in &project.files {
105            if !text.contains_key(p) {
106                text.insert(p.clone(), String::with_capacity(0));
107            }
108        }
109
110        Ok(ProjectText {
111            files: text,
112            origin: project.origin.clone(),
113        })
114    }
115
116    /// dump text to origin
117    pub fn dump(&self) -> Result<()> {
118        for (path, text) in &self.files {
119            debug!("Writing to {}", path.display());
120            // create the directory
121            if let Err(err) = fs::create_dir_all(path.parent().expect("path not file")) {
122                match err.kind() {
123                    io::ErrorKind::AlreadyExists => {}
124                    _ => return Err(err.into()),
125                }
126            }
127            let mut f = fs::File::create(path)?;
128            f.write_all(text.as_bytes())?;
129        }
130        Ok(())
131    }
132
133    /// get a hash table with the diff values of the files
134    /// in a project to what currently exists
135    pub fn diff(&self) -> Result<HashMap<PathBuf, PathDiff>> {
136        let mut out: HashMap<PathBuf, PathDiff> = HashMap::new();
137        for (path, text) in &self.files {
138            debug!("Diffing: {}", path.display());
139            let mut f = match fs::File::open(path) {
140                Ok(f) => f,
141                Err(_) => {
142                    out.insert(path.clone(), PathDiff::DoesNotExist);
143                    continue;
144                }
145            };
146
147            let mut bytes = Vec::new();
148            f.read_to_end(&mut bytes)?;
149
150            // get the original text
151            let original = match str::from_utf8(&bytes) {
152                Ok(s) => s,
153                Err(_) => {
154                    out.insert(path.clone(), PathDiff::NotUtf8);
155                    continue;
156                }
157            };
158
159            let ch = Changeset::new(original, text, "\n");
160            let d = if ch.distance == 0 {
161                PathDiff::None
162            } else {
163                PathDiff::Changeset(ch)
164            };
165            out.insert(path.clone(), d);
166        }
167        Ok(out)
168    }
169}