cpp-amalgamate 1.0.1

cpp-amalgamate recursively combines C++ source files and the headers they include into a single output file.
#![warn(
    // Lint groups
    future_incompatible,
    nonstandard_style,
    rust_2018_compatibility,
    rust_2018_idioms,
    rust_2021_compatibility,
    // Allow by default
    elided_lifetimes_in_paths,
    missing_debug_implementations,
    trivial_casts,
    trivial_numeric_casts,
    unused_extern_crates,
    unused_import_braces,
    unused_qualifications,
    // Clippy
    clippy::all,
    clippy::pedantic,
    clippy::cargo,
    clippy::clone_on_ref_ptr,
    clippy::decimal_literal_representation,
    clippy::filetype_is_file,
    clippy::float_cmp_const,
    clippy::get_unwrap,
    clippy::if_then_some_else_none,
    clippy::rc_mutex,
    clippy::rest_pat_in_fully_bound_structs,
    clippy::shadow_unrelated,
    clippy::todo,
    clippy::unimplemented,
    clippy::unwrap_used,
    clippy::verbose_file_reads,
)]
#![allow(clippy::module_name_repetitions, clippy::non_ascii_literal)]

mod cli;
mod filter;
mod logging;
mod process;
mod resolve;

use std::{
    env,
    fs::File,
    io::{self, BufWriter, Write},
    path::PathBuf,
};

use anyhow::{Context, Result};
use log::{error, info};

use crate::{
    cli::Opts,
    filter::InliningFilter,
    logging::ErrorHandling,
    process::{ErrorHandlingOpts, Processor},
    resolve::IncludeResolver,
};

fn run_with_writer(opts: &Opts, writer: impl Write) -> Result<()> {
    let resolver = IncludeResolver::new(
        opts.quote_search_dirs().map(PathBuf::from).collect(),
        opts.system_search_dirs().map(PathBuf::from).collect(),
    )?;
    let filter = InliningFilter::new(
        opts.quote_filter_globs().cloned(),
        opts.system_filter_globs().cloned(),
    )?;
    let error_handling_opts = ErrorHandlingOpts {
        cyclic_include: opts.cyclic_include_handling(),
        unresolvable_quote_include: opts.unresolvable_quote_include_handling(),
        unresolvable_system_include: opts.unresolvable_system_include_handling(),
    };
    let mut processor = Processor::new(
        writer,
        resolver,
        opts.line_directives,
        filter,
        error_handling_opts,
    );
    opts.files
        .iter()
        .try_for_each(|source_file| processor.process(source_file))
}

fn try_main() -> Result<()> {
    let opts = Opts::parse();

    let mut builder = env_logger::builder();
    if env::var_os("RUST_LOG_VERBOSE").is_some() {
        builder.format_timestamp_millis();
    } else {
        builder
            .format_level(true)
            .format_module_path(false)
            .format_target(false)
            .format_timestamp(None);
    }
    builder.filter_level(opts.log_level()).init();

    if let Some(out_file) = &opts.output {
        info!("Writing to {:?}", out_file);
        let writer = BufWriter::new(File::create(out_file).context("Failed to open output file")?);
        run_with_writer(&opts, writer)
    } else {
        info!("Writing to terminal");
        let stdout = io::stdout();
        run_with_writer(&opts, stdout.lock())
    }
}

fn main() {
    if let Err(error) = try_main() {
        error!("{:#}", error);
        std::process::exit(1);
    }
}