taplo_cli/
lib.rs

1use anyhow::{anyhow, Context};
2use args::GeneralArgs;
3use itertools::Itertools;
4use std::{
5    path::{Path, PathBuf},
6    str,
7    sync::Arc,
8};
9use taplo_common::{config::Config, environment::Environment, schema::Schemas, util::Normalize};
10
11pub mod args;
12pub mod commands;
13pub mod printing;
14
15pub struct Taplo<E: Environment> {
16    env: E,
17    colors: bool,
18    schemas: Schemas<E>,
19    config: Option<Arc<Config>>,
20}
21
22impl<E: Environment> Taplo<E> {
23    pub fn new(env: E) -> Self {
24        #[cfg(not(target_arch = "wasm32"))]
25        let http =
26            taplo_common::util::get_reqwest_client(std::time::Duration::from_secs(5)).unwrap();
27
28        #[cfg(target_arch = "wasm32")]
29        let http = reqwest::Client::default();
30
31        Self {
32            schemas: Schemas::new(env.clone(), http),
33            colors: env.atty_stderr(),
34            config: None,
35            env,
36        }
37    }
38
39    #[tracing::instrument(skip_all)]
40    async fn load_config(&mut self, general: &GeneralArgs) -> Result<Arc<Config>, anyhow::Error> {
41        if let Some(c) = self.config.clone() {
42            return Ok(c);
43        }
44
45        let mut config_path = general.config.clone();
46
47        if config_path.is_none() && !general.no_auto_config {
48            if let Some(cwd) = self.env.cwd_normalized() {
49                config_path = self.env.find_config_file_normalized(&cwd).await
50            }
51        }
52
53        let mut config = Config::default();
54        if let Some(c) = config_path {
55            tracing::info!(path = ?c, "found configuration file");
56            match self.env.read_file(&c).await {
57                Ok(cfg) => match toml::from_str(str::from_utf8(&cfg)?) {
58                    Ok(c) => config = c,
59                    Err(error) => {
60                        tracing::warn!(%error, "invalid configuration file");
61                    }
62                },
63                Err(error) => {
64                    tracing::warn!(%error, "failed to read configuration file");
65                }
66            }
67        }
68
69        config
70            .prepare(
71                &self.env,
72                &self
73                    .env
74                    .cwd_normalized()
75                    .ok_or_else(|| anyhow!("working directory is required"))?,
76            )
77            .context("invalid configuration")?;
78
79        let c = Arc::new(config);
80
81        self.config = Some(c.clone());
82
83        Ok(c)
84    }
85
86    #[tracing::instrument(skip_all, fields(?cwd))]
87    async fn collect_files(
88        &self,
89        cwd: &Path,
90        config: &Config,
91        arg_patterns: impl Iterator<Item = String>,
92    ) -> Result<Vec<PathBuf>, anyhow::Error> {
93        let mut patterns: Vec<String> = arg_patterns
94            .map(|pat| {
95                if !self.env.is_absolute(Path::new(&pat)) {
96                    cwd.join(&pat).normalize().to_string_lossy().into_owned()
97                } else {
98                    pat
99                }
100            })
101            .collect();
102
103        if patterns.is_empty() {
104            patterns = match config.include.clone() {
105                Some(patterns) => patterns,
106                None => Vec::from([cwd
107                    .join("**/*.toml")
108                    .normalize()
109                    .to_string_lossy()
110                    .into_owned()]),
111            };
112        };
113
114        let patterns = patterns
115            .into_iter()
116            .unique()
117            .map(|p| glob::Pattern::new(&p).map(|_| p))
118            .collect::<Result<Vec<_>, _>>()?;
119
120        let files = patterns
121            .into_iter()
122            .map(|pat| self.env.glob_files_normalized(&pat))
123            .collect::<Result<Vec<_>, _>>()
124            .into_iter()
125            .flatten()
126            .flatten()
127            .collect::<Vec<_>>();
128
129        let total = files.len();
130
131        let files = files
132            .into_iter()
133            .filter(|path| config.is_included(path))
134            .collect::<Vec<_>>();
135
136        let excluded = total - files.len();
137
138        tracing::info!(total, excluded, "found files");
139
140        Ok(files)
141    }
142}
143
144pub fn default_config() -> Config {
145    Config {
146        plugins: None,
147        ..Default::default()
148    }
149}