1use crate::error::{IncludeError, Result};
4use crate::lock;
5use crate::node::Node;
6use crate::options::EntryOptions;
7use indexmap::IndexMap;
8use serde::{Deserialize, Serialize};
9use std::fs;
10use std::io::Write;
11use std::path::{Path, PathBuf};
12use std::sync::{Arc, Mutex};
13
14#[derive(Debug, Clone, Copy, PartialEq, Eq)]
16pub enum FileFormat {
17 Yaml,
19 Json,
21}
22
23#[derive(Debug, Clone, PartialEq, Default, Serialize, Deserialize)]
26pub struct Document {
27 #[serde(default, skip_serializing_if = "Vec::is_empty")]
29 pub entries: Vec<EntryOptions>,
30 #[serde(flatten, default, skip_serializing_if = "IndexMap::is_empty")]
32 pub extra: IndexMap<String, Node>,
33}
34
35impl Document {
36 pub fn with_entries(entries: Vec<EntryOptions>) -> Self {
38 Self {
39 entries,
40 extra: IndexMap::new(),
41 }
42 }
43}
44
45struct FileInner {
47 path: PathBuf,
48 format: FileFormat,
49 suspend: Mutex<usize>,
50}
51
52#[derive(Clone)]
60pub struct LoaderFile {
61 inner: Arc<FileInner>,
62}
63
64impl std::fmt::Debug for LoaderFile {
65 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
66 f.debug_struct("LoaderFile")
67 .field("path", &self.inner.path)
68 .field("format", &self.inner.format)
69 .finish_non_exhaustive()
70 }
71}
72
73impl LoaderFile {
74 pub fn open(path: impl Into<PathBuf>) -> Result<Self> {
78 let path = path.into();
79 let format = match path.extension().and_then(|ext| ext.to_str()) {
80 Some("yml" | "yaml") => FileFormat::Yaml,
81 Some("json") => FileFormat::Json,
82 _ => return Err(IncludeError::UnknownFormat { path }),
83 };
84 Ok(Self {
85 inner: Arc::new(FileInner {
86 path,
87 format,
88 suspend: Mutex::new(0),
89 }),
90 })
91 }
92
93 pub fn path(&self) -> &Path {
95 &self.inner.path
96 }
97
98 pub fn format(&self) -> FileFormat {
100 self.inner.format
101 }
102
103 pub fn read(&self) -> Result<Document> {
107 let content = match fs::read_to_string(&self.inner.path) {
108 Ok(content) => content,
109 Err(error) if error.kind() == std::io::ErrorKind::NotFound => {
110 return Ok(Document::default());
111 }
112 Err(error) => return Err(error.into()),
113 };
114 if content.trim().is_empty() {
115 return Ok(Document::default());
116 }
117 match self.inner.format {
118 FileFormat::Yaml => {
119 let value =
120 serde_yaml_ng::from_str::<serde_yaml_ng::Value>(&content).map_err(|error| {
121 IncludeError::Parse {
122 format: "yaml",
123 source: Box::new(error),
124 }
125 })?;
126 if value.is_null() {
127 return Ok(Document::default());
128 }
129 serde_yaml_ng::from_value(value).map_err(|error| IncludeError::Parse {
130 format: "yaml",
131 source: Box::new(error),
132 })
133 }
134 FileFormat::Json => {
135 let value =
136 serde_json::from_str::<serde_json::Value>(&content).map_err(|error| {
137 IncludeError::Parse {
138 format: "json",
139 source: Box::new(error),
140 }
141 })?;
142 if value.is_null() {
143 return Ok(Document::default());
144 }
145 serde_json::from_value(value).map_err(|error| IncludeError::Parse {
146 format: "json",
147 source: Box::new(error),
148 })
149 }
150 }
151 }
152
153 pub fn write(&self, document: &Document) -> Result<()> {
156 if self.is_suspended() {
157 return Ok(());
158 }
159 if let Ok(metadata) = fs::metadata(&self.inner.path) {
160 if metadata.permissions().readonly() {
161 return Err(IncludeError::ReadOnly {
162 path: self.inner.path.clone(),
163 });
164 }
165 }
166 let content = match self.inner.format {
167 FileFormat::Yaml => {
168 serde_yaml_ng::to_string(document).map_err(|error| IncludeError::Parse {
169 format: "yaml",
170 source: Box::new(error),
171 })?
172 }
173 FileFormat::Json => {
174 let mut text = serde_json::to_string_pretty(document).map_err(|error| {
175 IncludeError::Parse {
176 format: "json",
177 source: Box::new(error),
178 }
179 })?;
180 text.push('\n');
181 text
182 }
183 };
184 if let Some(parent) = self.inner.path.parent() {
185 if !parent.as_os_str().is_empty() {
186 fs::create_dir_all(parent)?;
187 }
188 }
189 let tmp = self.tmp_path();
190 {
191 let mut file = fs::File::create(&tmp)?;
192 file.write_all(content.as_bytes())?;
193 file.sync_all()?;
194 }
195 fs::rename(&tmp, &self.inner.path)?;
196 Ok(())
197 }
198
199 pub fn suspend(&self) -> FileSuspendGuard {
203 {
204 let mut suspend = lock(&self.inner.suspend);
205 *suspend += 1;
206 }
207 FileSuspendGuard { file: self.clone() }
208 }
209
210 pub fn is_suspended(&self) -> bool {
212 *lock(&self.inner.suspend) > 0
213 }
214
215 fn tmp_path(&self) -> PathBuf {
216 let file_name = self
217 .inner
218 .path
219 .file_name()
220 .map(|name| name.to_string_lossy().into_owned())
221 .unwrap_or_default();
222 self.inner.path.with_file_name(format!("{file_name}.tmp"))
223 }
224}
225
226#[derive(Debug)]
228pub struct FileSuspendGuard {
229 file: LoaderFile,
230}
231
232impl Drop for FileSuspendGuard {
233 fn drop(&mut self) {
234 let mut suspend = lock(&self.file.inner.suspend);
235 *suspend = suspend.saturating_sub(1);
236 }
237}