Skip to main content

pistonite_cu/fs/
write.rs

1use std::path::Path;
2
3use crate::pre::*;
4/// Writes the content to the path, automatically creating the directory if doesn't exist.
5#[inline(always)]
6pub fn write(path: impl AsRef<Path>, content: impl AsRef<[u8]>) -> crate::Result<()> {
7    write_impl(path.as_ref(), content.as_ref())
8}
9fn write_impl(path: &Path, content: &[u8]) -> crate::Result<()> {
10    crate::trace!("write '{}'", path.display());
11    let Err(x) = std::fs::write(path, content) else {
12        return Ok(());
13    };
14    if x.kind() == std::io::ErrorKind::NotFound {
15        // ensure parent dir exists and try again
16        if let Ok(parent) = path.parent_abs() {
17            crate::trace!("retrying with parent creation: write '{}'", path.display());
18            if !parent.exists() {
19                crate::check!(
20                    super::make_dir(&parent),
21                    "could not automatically create parent directory for '{}'",
22                    path.display()
23                )?;
24                // try again
25                return crate::check!(
26                    std::fs::write(path, content),
27                    "failed to write to file '{}'",
28                    path.display()
29                );
30            }
31        }
32    }
33    make_write_error(path, x)
34}
35
36/// Make a non-buffered writer to the file
37///
38/// Note that using a writer only really makes a difference
39/// if the content is large (i.e. does not fit in memory)
40///
41/// If you are making small writes, use [`cu::fs::buf_writer`]
42///
43/// Make sure to manually flush to capture errors while flushing
44///
45/// [`cu::fs::buf_writer`]: buf_writer
46#[inline(always)]
47pub fn writer(path: impl AsRef<Path>) -> crate::Result<std::fs::File> {
48    writer_impl(path.as_ref())
49}
50fn writer_impl(path: &Path) -> crate::Result<std::fs::File> {
51    crate::trace!("writer '{}'", path.display());
52    if let Ok(file) = std::fs::File::create(path) {
53        return Ok(file);
54    }
55    // write empty to the file to ensure it exists
56    write(path, [])?;
57    crate::trace!(
58        "retrying after creating the file: writer '{}'",
59        path.display()
60    );
61    // try again
62    match std::fs::File::create(path) {
63        Ok(file) => Ok(file),
64        Err(e) => make_write_error(path, e),
65    }
66}
67
68pub type BufFile = std::io::BufWriter<std::fs::File>;
69
70/// Make a buffered writer to the file
71///
72/// Note that using a writer only really makes a difference
73/// if the content is large (i.e. does not fit in memory)
74///
75/// If you know the writes will be in large chunks anyway, use unbuffered [`cu::fs::writer`]
76///
77/// Make sure to manually flush to capture errors while flushing
78///
79/// [`cu::fs::writer`]: writer
80#[inline(always)]
81pub fn buf_writer(path: impl AsRef<Path>) -> crate::Result<BufFile> {
82    writer(path).map(BufFile::new)
83}
84
85/// Async version of [`write`](function@crate::fs::write)
86#[cfg(feature = "coroutine")]
87#[inline(always)]
88pub async fn co_write(path: impl AsRef<Path>, content: impl AsRef<[u8]>) -> crate::Result<()> {
89    co_write_impl(path.as_ref(), content.as_ref()).await
90}
91#[cfg(feature = "coroutine")]
92async fn co_write_impl(path: &Path, content: &[u8]) -> crate::Result<()> {
93    crate::trace!("co_write '{}'", path.display());
94    let Err(x) = tokio::fs::write(path, content).await else {
95        return Ok(());
96    };
97    if x.kind() == std::io::ErrorKind::NotFound {
98        // ensure parent dir exists and try again
99        if let Ok(parent) = path.parent_abs() {
100            crate::trace!(
101                "retrying with parent creation: co_write '{}'",
102                path.display()
103            );
104            if !parent.exists() {
105                crate::check!(
106                    super::co_make_dir(&parent).await,
107                    "could not automatically create parent directory for '{}'",
108                    path.display()
109                )?;
110                // try again
111                return crate::check!(
112                    tokio::fs::write(path, content).await,
113                    "failed to write to file '{}'",
114                    path.display()
115                );
116            }
117        }
118    }
119    make_write_error(path, x)
120}
121
122fn make_write_error<T>(path: &Path, e: std::io::Error) -> crate::Result<T> {
123    if e.kind() == std::io::ErrorKind::NotADirectory {
124        if let Ok(parent) = path.parent_abs() {
125            if parent.exists() && !parent.is_dir() {
126                return Err(e).context(format!(
127                    "the parent path '{}' exists and is not a directory",
128                    path.display()
129                ))?;
130            }
131        }
132    }
133    Err(e).context(format!("failed to write to file '{}'", path.display()))
134}
135
136/// Serialize data as JSON and write it to a file.
137#[cfg(feature = "json")]
138#[inline(always)]
139pub fn write_json<S: serde::Serialize>(path: impl AsRef<Path>, content: &S) -> crate::Result<()> {
140    write_json_impl(path.as_ref(), crate::json::stringify(content))
141}
142/// Serialize data as JSON, prettified, and write it to a file.
143#[cfg(feature = "json")]
144#[inline(always)]
145pub fn write_json_pretty<S: serde::Serialize>(
146    path: impl AsRef<Path>,
147    content: &S,
148) -> crate::Result<()> {
149    write_json_impl(path.as_ref(), crate::json::stringify_pretty(content))
150}
151#[cfg(feature = "json")]
152fn write_json_impl(path: &Path, content: crate::Result<String>) -> crate::Result<()> {
153    let content = crate::check!(content, "failed to write json to file '{}'", path.display())?;
154    write(path, content)
155}
156
157/// Async version of [`write_json`]. The serialization part is synchronous, since it does not
158/// involve IO.
159#[cfg(all(feature = "json", feature = "coroutine"))]
160#[inline(always)]
161pub async fn co_write_json<S: serde::Serialize>(
162    path: impl AsRef<Path>,
163    content: &S,
164) -> crate::Result<()> {
165    co_write_json_impl(path.as_ref(), crate::json::stringify(content)).await
166}
167/// Async version of [`write_json_pretty`]. The serialization part is synchronous, since it does not
168/// involve IO.
169#[cfg(all(feature = "json", feature = "coroutine"))]
170pub async fn co_write_json_pretty<S: serde::Serialize>(
171    path: impl AsRef<Path>,
172    content: &S,
173) -> crate::Result<()> {
174    co_write_json_impl(path.as_ref(), crate::json::stringify_pretty(content)).await
175}
176#[cfg(all(feature = "json", feature = "coroutine"))]
177async fn co_write_json_impl(path: &Path, content: crate::Result<String>) -> crate::Result<()> {
178    let content = crate::check!(content, "failed to write json to file '{}'", path.display())?;
179    co_write(path, content).await
180}
181
182/// Serialize data as TOML and write it to a file.
183#[cfg(feature = "toml")]
184#[inline(always)]
185pub fn write_toml<S: serde::Serialize>(path: impl AsRef<Path>, content: &S) -> crate::Result<()> {
186    write_toml_impl(path.as_ref(), crate::toml::stringify(content))
187}
188/// Serialize data as TOML, prettified, and write it to a file.
189#[cfg(feature = "toml")]
190#[inline(always)]
191pub fn write_toml_pretty<S: serde::Serialize>(
192    path: impl AsRef<Path>,
193    content: &S,
194) -> crate::Result<()> {
195    write_toml_impl(path.as_ref(), crate::toml::stringify_pretty(content))
196}
197#[cfg(feature = "toml")]
198fn write_toml_impl(path: &Path, content: crate::Result<String>) -> crate::Result<()> {
199    let content = crate::check!(content, "failed to write toml to file '{}'", path.display())?;
200    write(path, content)
201}
202
203/// Async version of [`write_toml`]. The serialization part is synchronous, since it does not
204/// involve IO.
205#[cfg(all(feature = "toml", feature = "coroutine"))]
206#[inline(always)]
207pub async fn co_write_toml<S: serde::Serialize>(
208    path: impl AsRef<Path>,
209    content: &S,
210) -> crate::Result<()> {
211    co_write_toml_impl(path.as_ref(), crate::toml::stringify(content)).await
212}
213/// Async version of [`write_toml_pretty`]. The serialization part is synchronous, since it does not
214/// involve IO.
215#[cfg(all(feature = "toml", feature = "coroutine"))]
216pub async fn co_write_toml_pretty<S: serde::Serialize>(
217    path: impl AsRef<Path>,
218    content: &S,
219) -> crate::Result<()> {
220    co_write_toml_impl(path.as_ref(), crate::toml::stringify_pretty(content)).await
221}
222#[cfg(all(feature = "toml", feature = "coroutine"))]
223async fn co_write_toml_impl(path: &Path, content: crate::Result<String>) -> crate::Result<()> {
224    let content = crate::check!(content, "failed to write toml to file '{}'", path.display())?;
225    co_write(path, content).await
226}
227
228/// Serialize data as YAML and write it to a file.
229#[cfg(feature = "yaml")]
230#[inline(always)]
231pub fn write_yaml<S: serde::Serialize>(path: impl AsRef<Path>, content: &S) -> crate::Result<()> {
232    write_yaml_impl(path.as_ref(), crate::yaml::stringify(content))
233}
234#[cfg(feature = "yaml")]
235fn write_yaml_impl(path: &Path, content: crate::Result<String>) -> crate::Result<()> {
236    let content = crate::check!(content, "failed to write yaml to file '{}'", path.display())?;
237    write(path, content)
238}
239
240/// Async version of [`write_yaml`]. The serialization part is synchronous, since it does not
241/// involve IO.
242#[cfg(all(feature = "yaml", feature = "coroutine"))]
243#[inline(always)]
244pub async fn co_write_yaml<S: serde::Serialize>(
245    path: impl AsRef<Path>,
246    content: &S,
247) -> crate::Result<()> {
248    co_write_yaml_impl(path.as_ref(), crate::yaml::stringify(content)).await
249}
250#[cfg(all(feature = "yaml", feature = "coroutine"))]
251async fn co_write_yaml_impl(path: &Path, content: crate::Result<String>) -> crate::Result<()> {
252    let content = crate::check!(content, "failed to write yaml to file '{}'", path.display())?;
253    co_write(path, content).await
254}