use std::path::{Path, PathBuf};
use clap::{ArgMatches, FromArgMatches, IntoApp, Parser};
use itertools::Itertools;
use log::LevelFilter;
use crate::{filter::InvertibleGlob, logging::ErrorHandling};
const ABOUT: &str = "cpp-amalgamate recursively combines C++ source files and the headers they
include into a single output file. It tracks which headers have been included and skips any further
references to them. Which includes are inlined and which are left as is can be precisely
controlled.";
const HELP_TEMPLATE: &str = "\
{before-help}{bin} {version}\n\
{author-with-newline}\
{about-section}\n\
{usage-heading}\n {usage}\n\
\n\
{all-args}{after-help}\
";
#[derive(Debug, Parser)]
#[clap(
author,
version,
about=ABOUT,
help_template=HELP_TEMPLATE,
hide_possible_values=true,
// To make this work, we cannot use default_value for arguments.
arg_required_else_help=true,
)]
pub struct Opts {
#[clap(skip)]
matches: ArgMatches,
#[clap(required = true, parse(from_os_str))]
pub files: Vec<PathBuf>,
#[clap(short, long, parse(from_os_str), value_name = "file")]
pub output: Option<PathBuf>,
#[clap(
short,
long,
value_name = "dir",
multiple_occurrences = true,
number_of_values = 1
)]
dir: Vec<PathBuf>,
#[clap(
long,
parse(from_os_str),
value_name = "dir",
multiple_occurrences = true,
number_of_values = 1
)]
dir_quote: Vec<PathBuf>,
#[clap(
long,
parse(from_os_str),
value_name = "dir",
multiple_occurrences = true,
number_of_values = 1
)]
dir_system: Vec<PathBuf>,
#[clap(
short,
long,
value_name = "glob",
multiple_occurrences = true,
number_of_values = 1
)]
filter: Vec<InvertibleGlob>,
#[clap(
long,
value_name = "glob",
multiple_occurrences = true,
number_of_values = 1
)]
filter_quote: Vec<InvertibleGlob>,
#[clap(
long,
value_name = "glob",
multiple_occurrences = true,
number_of_values = 1
)]
filter_system: Vec<InvertibleGlob>,
#[clap(
long,
value_name = "handling",
possible_values = &ErrorHandling::NAMES,
conflicts_with_all = &["unresolvable-quote-include", "unresolvable-system-include"]
)]
unresolvable_include: Option<ErrorHandling>,
#[clap(
long,
value_name = "handling",
possible_values = &ErrorHandling::NAMES,
)]
unresolvable_quote_include: Option<ErrorHandling>,
#[clap(
long,
value_name = "handling",
possible_values = &ErrorHandling::NAMES,
)]
unresolvable_system_include: Option<ErrorHandling>,
#[clap(
long,
value_name = "handling",
possible_values = &ErrorHandling::NAMES,
)]
cyclic_include: Option<ErrorHandling>,
#[clap(short, long, parse(from_occurrences))]
verbose: i8,
#[clap(short, long, parse(from_occurrences), conflicts_with = "verbose")]
quiet: i8,
#[clap(long)]
pub line_directives: bool,
}
fn with_indices<'a, T>(
matches: &'a ArgMatches,
name: &str,
values: &'a [T],
) -> impl Iterator<Item = (usize, &'a T)> + 'a {
matches.indices_of(name).into_iter().flatten().zip(values)
}
impl Opts {
pub fn parse() -> Self {
let cmd = Self::command();
let matches = cmd.get_matches();
let mut opts = Self::from_arg_matches(&matches)
.expect("from_arg_matches should never return None when derived?!");
opts.matches = matches;
opts
}
fn merge_by_cli_order<'a, T>(
&'a self,
list1: &'a [T],
name1: &str,
list2: &'a [T],
name2: &str,
) -> impl Iterator<Item = &'a T> + 'a {
with_indices(&self.matches, name1, list1)
.merge_by(with_indices(&self.matches, name2, list2), |x, y| x.0 < y.0)
.map(|(_, val)| val)
}
pub fn quote_search_dirs(&self) -> impl Iterator<Item = &Path> {
self.merge_by_cli_order(&self.dir, "dir", &self.dir_quote, "dir-quote")
.map(PathBuf::as_path)
}
pub fn system_search_dirs(&self) -> impl Iterator<Item = &Path> {
self.merge_by_cli_order(&self.dir, "dir", &self.dir_system, "dir-system")
.map(PathBuf::as_path)
}
pub fn quote_filter_globs(&self) -> impl Iterator<Item = &InvertibleGlob> {
self.merge_by_cli_order(&self.filter, "filter", &self.filter_quote, "filter-quote")
}
pub fn system_filter_globs(&self) -> impl Iterator<Item = &InvertibleGlob> {
self.merge_by_cli_order(&self.filter, "filter", &self.filter_system, "filter-system")
}
pub fn unresolvable_quote_include_handling(&self) -> ErrorHandling {
self.unresolvable_include
.or(self.unresolvable_quote_include)
.unwrap_or(ErrorHandling::Ignore)
}
pub fn unresolvable_system_include_handling(&self) -> ErrorHandling {
self.unresolvable_include
.or(self.unresolvable_system_include)
.unwrap_or(ErrorHandling::Ignore)
}
pub fn cyclic_include_handling(&self) -> ErrorHandling {
self.cyclic_include.unwrap_or(ErrorHandling::Error)
}
pub fn log_level(&self) -> LevelFilter {
match self.verbose - self.quiet {
i8::MIN..=-2 => LevelFilter::Off,
-1 => LevelFilter::Error,
0 => LevelFilter::Warn,
1 => LevelFilter::Info,
2 => LevelFilter::Debug,
3..=i8::MAX => LevelFilter::Trace,
}
}
}