artifact_app/user/
save.rs1use toml;
5use difference::Changeset;
6use std::collections::BTreeMap;
7
8use dev_prefix::*;
9use types::*;
10use user::types::*;
11
12#[derive(Debug, PartialEq)]
17pub struct ProjectText {
18 pub origin: PathBuf,
19 pub files: HashMap<PathBuf, String>,
20}
21
22pub 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 pub fn from_project(project: &Project) -> Result<ProjectText> {
42 let mut files: HashMap<PathBuf, BTreeMap<String, UserArtifact>> = HashMap::new();
43
44 for (name, artifact) in &project.artifacts {
48 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 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 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 pub fn dump(&self) -> Result<()> {
118 for (path, text) in &self.files {
119 debug!("Writing to {}", path.display());
120 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 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 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}