cli_xtask/
fs.rs

1//! Utility functions for working with paths.
2
3use std::{borrow::Cow, fmt, fs::File};
4
5use cargo_metadata::camino::{Utf8Path, Utf8PathBuf};
6use eyre::{ensure, eyre};
7
8use crate::Result;
9
10/// Create a new [`File`](std::fs::File) from a `path`, and output the path to
11/// log.
12#[tracing::instrument(name = "create_file" fields(path = %path.as_ref().to_relative()), err)]
13pub fn create_file(path: impl AsRef<Utf8Path>) -> Result<File> {
14    let path = path.as_ref();
15    let dir = path.parent().ok_or_else(|| eyre!("path has no parent"))?;
16    create_dir(dir)?;
17
18    tracing::info!("creating file");
19    let file = File::create(path)?;
20    Ok(file)
21}
22
23/// Create a new directory if it doesn't exist, and output the path to log.
24#[tracing::instrument(name = "create_dir" fields(path = %path.as_ref().to_relative()), err)]
25pub fn create_dir(path: impl AsRef<Utf8Path>) -> Result<()> {
26    let path = path.as_ref();
27    if !path.is_dir() {
28        tracing::info!("creating directory");
29        std::fs::create_dir_all(path)?;
30    }
31    Ok(())
32}
33
34/// Remove a directory if exists and output the path to log.
35#[tracing::instrument(name = "remove_dir" fields(path = %path.as_ref().to_relative()), err)]
36pub fn remove_dir(path: impl AsRef<Utf8Path>) -> Result<()> {
37    let path = path.as_ref();
38    if path.is_dir() {
39        tracing::info!("removing directory");
40        std::fs::remove_dir_all(path)?;
41    }
42    Ok(())
43}
44
45/// Create a new directory if it doesn't exist, or remove all its contents if
46/// exists.
47pub fn create_or_cleanup_dir(dir: impl AsRef<Utf8Path>) -> Result<()> {
48    let dir = dir.as_ref();
49    remove_dir(dir)?;
50    create_dir(dir)?;
51    Ok(())
52}
53
54/// Copy a file from `from` to `to`, and output those path to log.
55#[tracing::instrument(name = "copy" skip_all, err)]
56pub fn copy(from: impl AsRef<Utf8Path>, to: impl AsRef<Utf8Path>) -> Result<()> {
57    let from = from.as_ref();
58    let to = to.as_ref();
59    if let Some(parent) = to.parent() {
60        create_dir(parent)?;
61    }
62    tracing::info!("{} -> {}", from.to_relative(), to.to_relative());
63    ensure!(from.is_file(), "not a file: {}", from.to_relative());
64    std::fs::copy(from, to)?;
65    Ok(())
66}
67
68/// Convert a path to a path relative to the current directory which implements
69/// [`Display`](std::fmt::Display).
70pub trait ToRelative {
71    /// Tye type of the converted path that implements
72    /// [`Display`](std::fmt::Display).
73    type Output: fmt::Display;
74
75    /// Convert the path to a path relative to the current directory which
76    /// implements [`Display`](std::fmt::Display).
77    fn to_relative(self) -> Self::Output;
78}
79
80impl<'a> ToRelative for &'a Utf8Path {
81    type Output = Cow<'a, Utf8Path>;
82    fn to_relative(self) -> Self::Output {
83        if self.is_relative() {
84            return Cow::Borrowed(self);
85        }
86
87        let current_dir = std::env::current_dir()
88            .ok()
89            .and_then(|path| Utf8PathBuf::try_from(path).ok());
90        let mut current_dir = current_dir.as_deref();
91        let mut prefix = Utf8PathBuf::new();
92        while let Some(cur_dir) = current_dir {
93            if cur_dir.parent().is_none() {
94                return Cow::Borrowed(self);
95            }
96            if let Ok(relative) = self.strip_prefix(cur_dir) {
97                if prefix != "" {
98                    return Cow::Owned(prefix.join(relative));
99                }
100                return if relative == "" {
101                    Cow::Borrowed(Utf8Path::new("."))
102                } else {
103                    Cow::Borrowed(relative)
104                };
105            }
106            current_dir = cur_dir.parent();
107            prefix.push("..");
108        }
109
110        Cow::Borrowed(self)
111    }
112}
113
114impl<'a> ToRelative for &'a Utf8PathBuf {
115    type Output = Cow<'a, Utf8Path>;
116    fn to_relative(self) -> Self::Output {
117        <&Utf8Path>::to_relative(self)
118    }
119}