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}