1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
use anyhow::{anyhow, Context};
use itertools::Itertools;
use jsona_util::{config::Config, environment::Environment, schema::Schemas};
use std::{
path::{Path, PathBuf},
sync::Arc,
};
pub use crate::commands::{AppArgs, Colors, GeneralArgs};
pub mod commands;
pub mod printing;
pub struct App<E: Environment> {
env: E,
colors: bool,
schemas: Schemas<E>,
config: Option<Arc<Config>>,
}
impl<E: Environment> App<E> {
pub fn new(env: E) -> Self {
Self {
schemas: Schemas::new(env.clone()),
colors: env.atty_stderr(),
config: None,
env,
}
}
#[tracing::instrument(skip_all)]
async fn load_config(&mut self, general: &GeneralArgs) -> Result<Arc<Config>, anyhow::Error> {
if let Some(c) = self.config.clone() {
return Ok(c);
}
let mut config_path = general.config.clone();
if config_path.is_none() && !general.no_auto_config {
if let Some(cwd) = self.env.cwd() {
config_path = self.env.find_config_file(&cwd).await
}
}
let mut config = Config::default();
if let Some(c) = config_path {
tracing::info!(path = ?c, "found configuration file");
match self.env.read_file(&c).await {
Ok(source) => {
match std::str::from_utf8(&source)
.map_err(|_| anyhow!("invalid utf8"))
.and_then(Config::from_jsona)
{
Ok(c) => config = c,
Err(error) => {
tracing::warn!(%error, "invalid configuration file");
}
}
}
Err(error) => {
tracing::warn!(%error, "failed to read configuration file");
}
}
}
config
.prepare(
&self.env,
&self
.env
.cwd()
.ok_or_else(|| anyhow!("working directory is required"))?,
)
.context("invalid configuration")?;
let c = Arc::new(config);
self.config = Some(c.clone());
Ok(c)
}
#[tracing::instrument(skip_all, fields(?cwd))]
async fn collect_files(
&self,
cwd: &Path,
_config: &Config,
arg_patterns: impl Iterator<Item = String>,
) -> Result<Vec<PathBuf>, anyhow::Error> {
let mut patterns: Vec<String> = arg_patterns
.map(|pat| {
if !self.env.is_absolute(Path::new(&pat)) {
cwd.join(&pat).to_string_lossy().into_owned()
} else {
pat
}
})
.collect();
if patterns.is_empty() {
patterns = Vec::from([cwd.join("**/*.jsona").to_string_lossy().into_owned()])
};
let patterns = patterns
.into_iter()
.unique()
.map(|p| glob::Pattern::new(&p).map(|_| p))
.collect::<Result<Vec<_>, _>>()?;
let files = patterns
.into_iter()
.map(|pat| self.env.glob_files(&pat))
.collect::<Result<Vec<_>, _>>()
.into_iter()
.flatten()
.flatten()
.collect::<Vec<_>>();
let total = files.len();
let excluded = total - files.len();
tracing::info!(total, excluded, "found files");
Ok(files)
}
}