calcit/
snapshot.rs

1use cirru_edn::Edn;
2use cirru_parser::Cirru;
3use std::collections::hash_map::HashMap;
4use std::collections::hash_set::HashSet;
5use std::path::Path;
6use std::sync::Arc;
7
8#[derive(Debug, Clone, PartialEq)]
9pub struct SnapshotConfigs {
10  pub init_fn: Arc<str>,
11  pub reload_fn: Arc<str>,
12  pub modules: Vec<Arc<str>>,
13  pub version: Arc<str>,
14}
15
16#[derive(Debug, Clone, PartialEq)]
17pub struct FileInSnapShot {
18  pub ns: Cirru,
19  pub defs: HashMap<Arc<str>, Cirru>,
20}
21
22#[derive(Debug, Clone, PartialEq)]
23pub struct Snapshot {
24  pub package: Arc<str>,
25  pub configs: SnapshotConfigs,
26  pub entries: HashMap<Arc<str>, SnapshotConfigs>,
27  pub files: HashMap<Arc<str>, FileInSnapShot>,
28}
29
30fn load_configs(data: &Edn) -> Result<SnapshotConfigs, String> {
31  let c = SnapshotConfigs {
32    init_fn: match data.map_get("init-fn")?.read_str() {
33      Ok(v) => (*v).into(),
34      Err(e) => return Err(format!("failed to load init-fn from: {}", e)),
35    },
36    reload_fn: match data.map_get("reload-fn")?.read_str() {
37      Ok(v) => (*v).into(),
38      Err(e) => return Err(format!("failed to load reload-fn from: {}", e)),
39    },
40    version: match data.map_get("version")? {
41      Edn::Nil => "".into(),
42      x => match x.read_str() {
43        Ok(v) => (*v).into(),
44        Err(e) => return Err(format!("failed to load version, {}", e)),
45      },
46    },
47    modules: match data.map_get("modules")? {
48      Edn::Nil => vec![],
49      x => load_modules(&x)?,
50    },
51  };
52  Ok(c)
53}
54
55fn load_modules(data: &Edn) -> Result<Vec<Arc<str>>, String> {
56  match data.read_list() {
57    Ok(xs) => {
58      let mut ys: Vec<Arc<str>> = Vec::with_capacity(xs.len());
59      for x in xs {
60        ys.push(x.read_str()?.into())
61      }
62      Ok(ys)
63    }
64    Err(e) => Err(format!("failed to load modules, {}", e)),
65  }
66}
67
68fn load_file_info(data: &Edn) -> Result<FileInSnapShot, String> {
69  let ns_code = data.map_get("ns")?.read_quoted_cirru()?;
70  let defs = data.map_get("defs")?.read_map().map_err(|e| format!("failed get `defs`:{}", e))?;
71  let mut defs_info: HashMap<Arc<str>, Cirru> = HashMap::with_capacity(defs.len());
72  for (k, v) in defs {
73    let var = k.read_str()?;
74    let def_code = v.read_quoted_cirru()?;
75    defs_info.insert((*var).into(), def_code);
76  }
77  let file = FileInSnapShot {
78    ns: ns_code,
79    defs: defs_info,
80  };
81  Ok(file)
82}
83
84fn load_files(data: &Edn) -> Result<HashMap<Arc<str>, FileInSnapShot>, String> {
85  let xs = data.read_map().map_err(|e| format!("failed loading files, {}", e))?;
86  let mut ys: HashMap<Arc<str>, FileInSnapShot> = HashMap::with_capacity(xs.len());
87  for (k, v) in xs {
88    let key = k.read_str()?;
89    let file = load_file_info(&v)?;
90    ys.insert((*key).into(), file);
91  }
92  Ok(ys)
93}
94
95fn load_entries(data: &Edn) -> Result<HashMap<Arc<str>, SnapshotConfigs>, String> {
96  let xs = data.read_map_or_nil().map_err(|e| format!("failed loading entries, {}", e))?;
97  let mut ys: HashMap<Arc<str>, SnapshotConfigs> = HashMap::with_capacity(xs.len());
98  for (k, v) in xs {
99    let key: Box<str> = match k {
100      Edn::Keyword(s) => s.to_str(),
101      Edn::Str(s) => s,
102      _ => return Err(format!("unknown data for an entry: {}", k)),
103    };
104    let configs = load_configs(&v)?;
105    ys.insert((*key).into(), configs);
106  }
107  Ok(ys)
108}
109
110/// parse snapshot
111pub fn load_snapshot_data(data: &Edn, path: &str) -> Result<Snapshot, String> {
112  let pkg = data.map_get("package")?.read_str()?;
113  let mut files = load_files(&data.map_get("files")?)?;
114  let meta_ns = format!("{}.$meta", pkg);
115  files.insert(meta_ns.to_owned().into(), gen_meta_ns(&meta_ns, path));
116  let s = Snapshot {
117    package: (*pkg).into(),
118    configs: load_configs(&data.map_get("configs")?)?,
119    entries: load_entries(&data.map_get("entries")?)?,
120    files,
121  };
122  Ok(s)
123}
124
125pub fn gen_meta_ns(ns: &str, path: &str) -> FileInSnapShot {
126  let mut def_dict: HashMap<Arc<str>, Cirru> = HashMap::with_capacity(2);
127  def_dict.insert(
128    "calcit-filename".into(),
129    Cirru::List(vec![
130      Cirru::leaf("def"),
131      Cirru::leaf("calcit-filename"),
132      Cirru::leaf(format!("|{}", path.escape_default())),
133    ]),
134  );
135  let path_data = Path::new(path);
136  let parent = path_data.parent().unwrap();
137  let parent_str = parent.to_str().unwrap();
138
139  def_dict.insert(
140    "calcit-dirname".into(),
141    Cirru::List(vec![
142      Cirru::leaf("def"),
143      Cirru::leaf("calcit-dirname"),
144      Cirru::leaf(format!("|{}", parent_str.escape_default())),
145    ]),
146  );
147
148  FileInSnapShot {
149    ns: Cirru::List(vec![Cirru::leaf("ns"), Cirru::Leaf(ns.to_owned().into())]),
150    defs: def_dict,
151  }
152}
153
154pub fn gen_default() -> Snapshot {
155  Snapshot {
156    package: "app".into(),
157    configs: SnapshotConfigs {
158      init_fn: "app.main/main!".into(),
159      reload_fn: "app.main/reload!".into(),
160      version: "0.0.0".into(),
161      modules: vec![],
162    },
163    entries: HashMap::new(),
164    files: HashMap::new(),
165  }
166}
167
168pub fn create_file_from_snippet(raw: &str) -> Result<FileInSnapShot, String> {
169  match cirru_parser::parse(raw) {
170    Ok(lines) => {
171      let mut def_dict: HashMap<Arc<str>, Cirru> = HashMap::with_capacity(2);
172      let mut func_code = vec![Cirru::leaf("defn"), Cirru::leaf("main!"), Cirru::List(vec![])];
173      for line in lines {
174        func_code.push(line.to_owned());
175      }
176      def_dict.insert("main!".into(), Cirru::List(func_code));
177      def_dict.insert(
178        "reload!".into(),
179        Cirru::List(vec![Cirru::leaf("defn"), Cirru::leaf("reload!"), Cirru::List(vec![])]),
180      );
181      Ok(FileInSnapShot {
182        ns: Cirru::List(vec![Cirru::leaf("ns"), Cirru::leaf("app.main")]),
183        defs: def_dict,
184      })
185    }
186    Err(e) => Err(format!("failed to make snapshot: {}", e)),
187  }
188}
189
190#[derive(Debug, PartialEq, Clone)]
191pub struct FileChangeInfo {
192  pub ns: Option<Cirru>,
193  pub added_defs: HashMap<Arc<str>, Cirru>,
194  pub removed_defs: HashSet<Arc<str>>,
195  pub changed_defs: HashMap<Arc<str>, Cirru>,
196}
197
198#[derive(Debug, PartialEq, Clone)]
199pub struct ChangesDict {
200  pub added: HashMap<Arc<str>, FileInSnapShot>,
201  pub removed: HashSet<Arc<str>>,
202  pub changed: HashMap<Arc<str>, FileChangeInfo>,
203}
204
205pub fn load_changes_info(data: &Edn) -> Result<ChangesDict, String> {
206  // println!("loading changes: {}", data);
207  let mut added: HashMap<Arc<str>, FileInSnapShot> = HashMap::new();
208  for (ns, file) in &data.map_get("added")?.read_map_or_nil()? {
209    added.insert(ns.read_str()?.into(), load_file_info(file)?);
210  }
211
212  let mut removed: HashSet<Arc<str>> = HashSet::new();
213  for item in &data.map_get("removed")?.read_set_or_nil()? {
214    removed.insert(item.read_str()?.into());
215  }
216
217  let mut changed: HashMap<Arc<str>, FileChangeInfo> = HashMap::new();
218  for (ns, file) in &data.map_get("changed")?.read_map_or_nil()? {
219    changed.insert(ns.read_str()?.into(), extract_changed_info(file)?);
220  }
221
222  Ok(ChangesDict { added, removed, changed })
223}
224
225pub fn extract_changed_info(data: &Edn) -> Result<FileChangeInfo, String> {
226  let ns_info = match data.map_get("ns")? {
227    Edn::Nil => None,
228    Edn::Quote(code) => Some(code),
229    a => return Err(format!("invalid information for ns code: {}", a)),
230  };
231
232  let mut added_defs: HashMap<Arc<str>, Cirru> = HashMap::new();
233
234  for (def, code) in data.map_get("added-defs")?.read_map_or_nil()? {
235    added_defs.insert((*def.read_str()?).into(), code.read_quoted_cirru()?);
236  }
237
238  let mut removed_defs: HashSet<Arc<str>> = HashSet::new();
239
240  for def in data.map_get("removed-defs")?.read_set_or_nil()? {
241    removed_defs.insert((*def.read_str()?).into());
242  }
243
244  let mut changed_defs: HashMap<Arc<str>, Cirru> = HashMap::new();
245  for (def, code) in data.map_get("changed-defs")?.read_map_or_nil()? {
246    changed_defs.insert((*(def.read_str()?)).into(), code.read_quoted_cirru()?);
247  }
248
249  Ok(FileChangeInfo {
250    ns: ns_info,
251    added_defs,
252    removed_defs,
253    changed_defs,
254  })
255}