1use std::fs;
2use std::path::{Path, PathBuf};
3
4use anyhow::Result;
5use clap::{Parser, Subcommand};
6
7use xee_xpath::Documents;
8use xee_xpath_load::PathLoadable;
9
10use crate::catalog::{Catalog, LoadContext};
11use crate::filter::{ExcludedNamesFilter, IncludeAllFilter, NameFilter, TestFilter};
12use crate::language::{Language, XPathLanguage, XsltLanguage};
13use crate::outcomes::{CatalogOutcomes, Outcomes, TestSetOutcomes};
14use crate::paths::{paths, Mode, PathInfo};
15use crate::runcontext::RunContext;
16use crate::testset::TestSet;
17
18#[derive(Parser)]
19#[command(author, version, about, long_about = None)]
20struct Cli {
21 #[clap(short, long)]
23 verbose: bool,
24 #[command(subcommand)]
25 command: Commands,
26}
27
28#[derive(Subcommand)]
29enum Commands {
30 Initialize {
35 path: PathBuf,
37 },
38 Check {
43 path: PathBuf,
48 },
49 Update {
55 path: PathBuf,
60 },
61 All {
65 path: PathBuf,
67 name_filter: Option<String>,
69 },
70}
71
72impl Commands {
73 fn path(&self) -> &Path {
74 match self {
75 Commands::Initialize { path } => path,
76 Commands::Check { path } => path,
77 Commands::Update { path } => path,
78 Commands::All { path, .. } => path,
79 }
80 }
81}
82
83pub fn cli() -> Result<()> {
84 let cli = Cli::parse();
85
86 let path = cli.command.path();
87 let path_info = paths(path)?;
88
89 match path_info.mode {
90 Mode::XPath => cli_run::<XPathLanguage>(cli, path_info),
91 Mode::Xslt => cli_run::<XsltLanguage>(cli, path_info),
92 }
93}
94
95fn cli_run<L: Language>(cli: Cli, path_info: PathInfo) -> Result<()> {
96 let mut documents = Documents::new();
97 let run_context = RunContext::new(&mut documents, L::known_dependencies(), cli.verbose);
98 let mut runner = Runner::<L>::new(run_context, path_info);
99 match cli.command {
100 Commands::Initialize { .. } => runner.initialize(),
101 Commands::Check { .. } => runner.check(),
102 Commands::Update { .. } => runner.update(),
103 Commands::All { name_filter, .. } => runner.all(name_filter),
104 }
105}
106
107struct Runner<'a, L: Language> {
108 run_context: RunContext<'a>,
109 path_info: PathInfo,
110 _l: std::marker::PhantomData<L>,
111}
112
113impl<'a, L: Language> Runner<'a, L> {
114 fn new(run_context: RunContext<'a>, path_info: PathInfo) -> Self {
115 Self {
116 run_context,
117 path_info,
118 _l: std::marker::PhantomData,
119 }
120 }
121
122 fn check(&mut self) -> Result<()> {
123 if !self.path_info.filter_path.exists() {
124 println!("Cannot check without filter file");
126 return Ok(());
127 }
128
129 let catalog = self.load_catalog()?;
130
131 let test_filter = self.load_check_test_filter()?;
132 if self.path_info.whole_catalog() {
133 let outcomes = self.catalog_outcomes(&catalog, &test_filter)?;
134 println!("{}", outcomes.display());
135 if outcomes.has_failures() {
136 return Err(anyhow::anyhow!("Failures found"));
138 }
139 } else {
140 let outcomes = self.test_set_outcomes(&catalog, &test_filter)?;
141 println!("{}", outcomes.display());
142 if outcomes.has_failures() {
143 return Err(anyhow::anyhow!("Failures found"));
145 }
146 }
147 Ok(())
148 }
149
150 fn all(&mut self, name_filter: Option<String>) -> Result<()> {
151 let catalog = self.load_catalog()?;
152
153 let test_filter = NameFilter::new(name_filter);
154
155 if self.path_info.whole_catalog() {
156 let outcomes = self.catalog_outcomes(&catalog, &test_filter)?;
157 println!("{}", outcomes.display());
158 } else {
159 let outcomes = self.test_set_outcomes(&catalog, &test_filter)?;
160 println!("{}", outcomes.display());
161 }
162 Ok(())
163 }
164
165 fn update(&mut self) -> Result<()> {
166 if !self.path_info.filter_path.exists() {
167 println!("Cannot update without filter file");
169 return Ok(());
170 }
171 let catalog = self.load_catalog()?;
172 let test_filter = IncludeAllFilter::new();
173 let mut update_filter = ExcludedNamesFilter::load_from_file(&self.path_info.filter_path)?;
174 if self.path_info.whole_catalog() {
175 let outcomes = self.catalog_outcomes(&catalog, &test_filter)?;
176 update_filter.update_with_catalog_outcomes(&outcomes);
177 println!("{}", outcomes.display());
178 } else {
179 let outcomes = self.test_set_outcomes(&catalog, &test_filter)?;
180 update_filter.update_with_test_set_outcomes(&outcomes);
181 println!("{}", outcomes.display());
182 }
183
184 let filter_data = update_filter.to_string();
185 fs::write(&self.path_info.filter_path, filter_data)?;
186 Ok(())
187 }
188
189 fn initialize(&mut self) -> Result<()> {
190 if self.path_info.filter_path.exists() {
191 println!("Cannot reinitialize filters. Use update or delete filters file first");
192 return Ok(());
193 }
194
195 let catalog = self.load_catalog()?;
196
197 let test_filter = IncludeAllFilter::new();
198
199 let outcomes = self.catalog_outcomes(&catalog, &test_filter)?;
200
201 let test_filter = ExcludedNamesFilter::from_outcomes(&outcomes);
202 let filter_data = test_filter.to_string();
203 fs::write(&self.path_info.filter_path, filter_data)?;
204 Ok(())
205 }
206
207 fn load_catalog(&mut self) -> Result<Catalog<L>> {
208 let context = LoadContext::new::<L>(self.path_info.catalog_path.clone());
209 Catalog::load_from_file(&context)
210 }
211
212 fn load_test_set(&mut self) -> Result<TestSet<L>> {
213 let context = LoadContext::new::<L>(self.path_info.test_file().clone());
214 TestSet::load_from_file(&context)
215 }
216
217 fn load_check_test_filter(&self) -> Result<impl TestFilter<L>> {
218 ExcludedNamesFilter::load_from_file(&self.path_info.filter_path)
219 }
220
221 fn catalog_outcomes(
222 &mut self,
223 catalog: &Catalog<L>,
224 test_filter: &impl TestFilter<L>,
225 ) -> Result<CatalogOutcomes> {
226 let mut out = std::io::stdout();
227 let renderer = self.run_context.renderer();
228 catalog.run(
229 &mut self.run_context,
230 test_filter,
231 &mut out,
232 renderer.as_ref(),
233 )
234 }
235
236 fn test_set_outcomes(
237 &mut self,
238 catalog: &Catalog<L>,
239 test_filter: &impl TestFilter<L>,
240 ) -> Result<TestSetOutcomes> {
241 let mut out = std::io::stdout();
242 let renderer = self.run_context.renderer();
243 let test_set = self.load_test_set()?;
244 test_set.run(
245 &mut self.run_context,
246 catalog,
247 test_filter,
248 &mut out,
249 renderer.as_ref(),
250 )
251 }
252}