1use std::{
4 collections::HashMap,
5 error::Error,
6 path::{Path, PathBuf},
7};
8
9use serde::{Deserialize, Serialize};
10use toml_edit::{DocumentMut, Item};
11
12use crate::util::{add_and_fill_inline_table, add_and_fill_missing_table};
13
14pub mod artifact;
15pub mod lockfile;
16pub mod profile;
17pub mod resource;
18pub mod server;
19pub mod sourcefile;
20mod util;
21
22pub const MANIFEST_NAME: &str = "dxm.toml";
23
24#[derive(Default, Serialize, Deserialize)]
26pub struct Manifest {
27 #[serde(default)]
29 pub artifact: artifact::Artifact,
30
31 #[serde(default)]
33 pub server: server::Server,
34
35 #[serde(default)]
37 pub profiles: HashMap<String, profile::Profile>,
38
39 #[serde(default)]
41 pub resources: HashMap<String, resource::Resource>,
42}
43
44impl Manifest {
45 pub fn new(
47 artifact: artifact::Artifact,
48 server: server::Server,
49 profiles: HashMap<String, profile::Profile>,
50 resources: HashMap<String, resource::Resource>,
51 ) -> Self {
52 Self {
53 artifact,
54 server,
55 profiles,
56 resources,
57 }
58 }
59
60 pub fn find<P>(dir: P) -> Result<Option<PathBuf>, Box<dyn Error>>
65 where
66 P: AsRef<Path>,
67 {
68 let full_dir = fs_err::canonicalize(dir.as_ref())?;
69
70 let mut dir = full_dir.as_path();
71 let mut path = Self::dir_manifest(dir);
72
73 while !path.try_exists()? {
74 if let Some(parent_dir) = dir.parent() {
75 dir = parent_dir;
76 } else {
77 log::debug!(
78 "could not find manifest in {} or its parents",
79 full_dir.display()
80 );
81
82 return Ok(None);
83 }
84
85 path = Self::dir_manifest(dir);
86 }
87
88 log::debug!("found manifest in {}", dir.display());
89
90 Ok(Some(dir.to_path_buf()))
91 }
92
93 pub fn read<P>(dir: P) -> Result<Self, Box<dyn Error>>
96 where
97 P: AsRef<Path>,
98 {
99 let path = Self::dir_manifest(dir);
100
101 log::debug!("reading manifest path {}", path.display());
102
103 let contents = fs_err::read_to_string(path)?;
104 let manifest = toml_edit::de::from_str(&contents)?;
105
106 Ok(manifest)
107 }
108
109 pub fn write<P>(&self, dir: P) -> Result<(), Box<dyn Error>>
111 where
112 P: AsRef<Path>,
113 {
114 self.fill_and_write(dir, |item| {
115 add_and_fill_missing_table(item, "artifact", |i| self.artifact.fill_toml_item(i));
116 add_and_fill_missing_table(item, "server", |i| self.server.fill_toml_item(i));
117 self.fill_resources(item, "resources");
118 })
119 }
120
121 pub fn write_artifact<P>(&self, dir: P) -> Result<(), Box<dyn Error>>
124 where
125 P: AsRef<Path>,
126 {
127 self.fill_and_write(dir, |item| {
128 add_and_fill_missing_table(item, "artifact", |i| self.artifact.fill_toml_item(i));
129 })
130 }
131
132 pub fn write_server<P>(&self, dir: P) -> Result<(), Box<dyn Error>>
135 where
136 P: AsRef<Path>,
137 {
138 self.fill_and_write(dir, |item| {
139 add_and_fill_missing_table(item, "server", |i| self.server.fill_toml_item(i));
140 })
141 }
142
143 pub fn write_resources<P>(&self, dir: P) -> Result<(), Box<dyn Error>>
146 where
147 P: AsRef<Path>,
148 {
149 self.fill_and_write(dir, |item| {
150 self.fill_resources(item, "resources");
151 })
152 }
153
154 fn fill_resources<S>(&self, item: &mut Item, key: S)
155 where
156 S: AsRef<str>,
157 {
158 add_and_fill_missing_table(item, key.as_ref(), |i| {
159 for (name, resource) in self.resources.iter() {
160 add_and_fill_inline_table(i, name, |ri| resource.fill_toml_item(ri));
161 }
162
163 if let Some(table) = i.as_table_mut() {
164 let keys = table
165 .iter()
166 .map(|(n, _)| n.to_string())
167 .filter(|n| !self.resources.contains_key(n))
168 .collect::<Vec<String>>();
169
170 for name in keys.clone() {
171 table[&name] = Item::None
172 }
173 }
174 });
175 }
176
177 fn fill_and_write<P>(&self, dir: P, fill: impl Fn(&mut Item)) -> Result<(), Box<dyn Error>>
180 where
181 P: AsRef<Path>,
182 {
183 let path = Self::dir_manifest(dir);
184
185 let mut document = match fs_err::read_to_string(&path) {
186 Ok(content) => {
187 log::debug!("parsing manifest path {}", path.display());
188
189 content.parse()?
190 }
191 Err(err) => match err.kind() {
192 std::io::ErrorKind::NotFound => {
193 log::trace!("creating new manifest file");
194
195 DocumentMut::new()
196 }
197 _ => Err(err)?,
198 },
199 };
200
201 log::debug!("writing manifest path {}", path.display());
202
203 fill(document.as_item_mut());
204 fs_err::write(path, document.to_string())?;
205
206 Ok(())
207 }
208
209 fn dir_manifest<P>(dir: P) -> PathBuf
211 where
212 P: AsRef<Path>,
213 {
214 let dir = dir.as_ref();
215
216 dir.join(MANIFEST_NAME)
217 }
218}