use std::fs;
use std::path::{Path, PathBuf};
use anyhow::Result;
use clap::{Parser, Subcommand};
use xee_xpath::Documents;
use xee_xpath_load::PathLoadable;
use crate::catalog::{Catalog, LoadContext};
use crate::filter::{ExcludedNamesFilter, IncludeAllFilter, NameFilter, TestFilter};
use crate::language::{Language, XPathLanguage, XsltLanguage};
use crate::outcomes::{CatalogOutcomes, Outcomes, TestSetOutcomes};
use crate::paths::{paths, Mode, PathInfo};
use crate::runcontext::RunContext;
use crate::testset::TestSet;
#[derive(Parser)]
#[command(author, version, about, long_about = None)]
struct Cli {
#[clap(short, long)]
verbose: bool,
#[command(subcommand)]
command: Commands,
}
#[derive(Subcommand)]
enum Commands {
Initialize {
path: PathBuf,
},
Check {
path: PathBuf,
},
Update {
path: PathBuf,
},
All {
path: PathBuf,
name_filter: Option<String>,
},
}
impl Commands {
fn path(&self) -> &Path {
match self {
Commands::Initialize { path } => path,
Commands::Check { path } => path,
Commands::Update { path } => path,
Commands::All { path, .. } => path,
}
}
}
pub fn cli() -> Result<()> {
let cli = Cli::parse();
let path = cli.command.path();
let path_info = paths(path)?;
match path_info.mode {
Mode::XPath => cli_run::<XPathLanguage>(cli, path_info),
Mode::Xslt => cli_run::<XsltLanguage>(cli, path_info),
}
}
fn cli_run<L: Language>(cli: Cli, path_info: PathInfo) -> Result<()> {
let mut documents = Documents::new();
let run_context = RunContext::new(&mut documents, L::known_dependencies(), cli.verbose);
let mut runner = Runner::<L>::new(run_context, path_info);
match cli.command {
Commands::Initialize { .. } => runner.initialize(),
Commands::Check { .. } => runner.check(),
Commands::Update { .. } => runner.update(),
Commands::All { name_filter, .. } => runner.all(name_filter),
}
}
struct Runner<'a, L: Language> {
run_context: RunContext<'a>,
path_info: PathInfo,
_l: std::marker::PhantomData<L>,
}
impl<'a, L: Language> Runner<'a, L> {
fn new(run_context: RunContext<'a>, path_info: PathInfo) -> Self {
Self {
run_context,
path_info,
_l: std::marker::PhantomData,
}
}
fn check(&mut self) -> Result<()> {
if !self.path_info.filter_path.exists() {
println!("Cannot check without filter file");
return Ok(());
}
let catalog = self.load_catalog()?;
let test_filter = self.load_check_test_filter()?;
if self.path_info.whole_catalog() {
let outcomes = self.catalog_outcomes(&catalog, &test_filter)?;
println!("{}", outcomes.display());
if outcomes.has_failures() {
return Err(anyhow::anyhow!("Failures found"));
}
} else {
let outcomes = self.test_set_outcomes(&catalog, &test_filter)?;
println!("{}", outcomes.display());
if outcomes.has_failures() {
return Err(anyhow::anyhow!("Failures found"));
}
}
Ok(())
}
fn all(&mut self, name_filter: Option<String>) -> Result<()> {
let catalog = self.load_catalog()?;
let test_filter = NameFilter::new(name_filter);
if self.path_info.whole_catalog() {
let outcomes = self.catalog_outcomes(&catalog, &test_filter)?;
println!("{}", outcomes.display());
} else {
let outcomes = self.test_set_outcomes(&catalog, &test_filter)?;
println!("{}", outcomes.display());
}
Ok(())
}
fn update(&mut self) -> Result<()> {
if !self.path_info.filter_path.exists() {
println!("Cannot update without filter file");
return Ok(());
}
let catalog = self.load_catalog()?;
let test_filter = IncludeAllFilter::new();
let mut update_filter = ExcludedNamesFilter::load_from_file(&self.path_info.filter_path)?;
if self.path_info.whole_catalog() {
let outcomes = self.catalog_outcomes(&catalog, &test_filter)?;
update_filter.update_with_catalog_outcomes(&outcomes);
println!("{}", outcomes.display());
} else {
let outcomes = self.test_set_outcomes(&catalog, &test_filter)?;
update_filter.update_with_test_set_outcomes(&outcomes);
println!("{}", outcomes.display());
}
let filter_data = update_filter.to_string();
fs::write(&self.path_info.filter_path, filter_data)?;
Ok(())
}
fn initialize(&mut self) -> Result<()> {
if self.path_info.filter_path.exists() {
println!("Cannot reinitialize filters. Use update or delete filters file first");
return Ok(());
}
let catalog = self.load_catalog()?;
let test_filter = IncludeAllFilter::new();
let outcomes = self.catalog_outcomes(&catalog, &test_filter)?;
let test_filter = ExcludedNamesFilter::from_outcomes(&outcomes);
let filter_data = test_filter.to_string();
fs::write(&self.path_info.filter_path, filter_data)?;
Ok(())
}
fn load_catalog(&mut self) -> Result<Catalog<L>> {
let context = LoadContext::new::<L>(self.path_info.catalog_path.clone());
Catalog::load_from_file(&context)
}
fn load_test_set(&mut self) -> Result<TestSet<L>> {
let context = LoadContext::new::<L>(self.path_info.test_file().clone());
TestSet::load_from_file(&context)
}
fn load_check_test_filter(&self) -> Result<impl TestFilter<L>> {
ExcludedNamesFilter::load_from_file(&self.path_info.filter_path)
}
fn catalog_outcomes(
&mut self,
catalog: &Catalog<L>,
test_filter: &impl TestFilter<L>,
) -> Result<CatalogOutcomes> {
let mut out = std::io::stdout();
let renderer = self.run_context.renderer();
catalog.run(
&mut self.run_context,
test_filter,
&mut out,
renderer.as_ref(),
)
}
fn test_set_outcomes(
&mut self,
catalog: &Catalog<L>,
test_filter: &impl TestFilter<L>,
) -> Result<TestSetOutcomes> {
let mut out = std::io::stdout();
let renderer = self.run_context.renderer();
let test_set = self.load_test_set()?;
test_set.run(
&mut self.run_context,
catalog,
test_filter,
&mut out,
renderer.as_ref(),
)
}
}