Skip to main content

dxm_manifest/
lib.rs

1//! A crate that contains manifest structures used by dxm.
2
3use 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/// The parent manifest structure containing data used by dxm.
25#[derive(Default, Serialize, Deserialize)]
26pub struct Manifest {
27    /// The data about the FXServer installation.
28    #[serde(default)]
29    pub artifact: artifact::Artifact,
30
31    /// The data about the server.
32    #[serde(default)]
33    pub server: server::Server,
34
35    /// The FXServer startup profiles.
36    #[serde(default)]
37    pub profiles: HashMap<String, profile::Profile>,
38
39    /// The data about the third-party resources.
40    #[serde(default)]
41    pub resources: HashMap<String, resource::Resource>,
42}
43
44impl Manifest {
45    /// Constructs and returns a new `Manifest` instance.
46    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    /// Attempts to find a `dxm.toml` file in the given directory, searching
61    /// through parent directories as well.
62    ///
63    /// Returns the manifest's directory if found, or `None` if not.
64    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    /// Reads a `dxm.toml` file in the given directory, and returns a new
94    /// `Manifest` instance.
95    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    /// Updates and writes a `dxm.toml` file in the given directory.
110    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    /// Updates the artifact table and writes a `dxm.toml` file in the given
122    /// directory.
123    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    /// Updates the server table and writes a `dxm.toml` file in the given
133    /// directory.
134    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    /// Updates the resources table and writes a `dxm.toml` file in the given
144    /// directory.
145    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    /// Reads a `dxm.toml` file in the given directory, runs the given function
178    /// on it, and writes the document back.
179    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    /// Returns the given directory's path joined with the manifest file name.
210    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}