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