#![warn(dead_code_pub_in_binary)]
#![cfg_attr(not(test), warn(unused_crate_dependencies))]
use std::{assert_matches, env, io, process};
use cargo_metadata::{Metadata, Package};
use clap::CommandFactory as _;
use clap_complete::{Generator, Shell};
use clap_verbosity_flag::{InfoLevel, Verbosity};
use miette::MietteHandlerOpts;
use supports_color::Stream;
use tracing::Level;
use tracing_subscriber::{EnvFilter, filter::LevelFilter, fmt::writer::BoxMakeWriter};
use crate::{
args::Args,
config::{Config, ConfigLoader},
manifest::ManifestLoader,
sync::PackageSyncContext,
terminal::Terminal,
};
mod args;
mod cargo;
mod config;
mod diff;
mod manifest;
mod parse;
mod source;
mod sync;
mod terminal;
#[cfg(test)]
mod testing;
mod traits;
fn main() -> miette::Result<()> {
let bin_name = env!("CARGO_BIN_NAME");
let env_prefix = bin_name.to_uppercase().replace('-', "_");
if let Ok(shell) = env::var(format!("{env_prefix}_COMPLETE")) {
print_completion(bin_name, &shell);
process::exit(0);
}
if let Ok(output_dir) = env::var(format!("{env_prefix}_GENERATE_MAN_TO")) {
generate_man(&output_dir);
process::exit(0);
}
let args = args::parse();
let terminal = Terminal::init(args.color);
set_miette_hook(&terminal);
install_logger(&terminal, args.verbosity);
let workspace = cargo::metadata(&args.manifest)?;
let mut manifest_loader = ManifestLoader::new(&workspace);
let mut config_loader = ConfigLoader::new();
let cxs = cargo::select_packages(&workspace, &args.package)?
.into_iter()
.map(|package| {
build_package_context(
&mut manifest_loader,
&mut config_loader,
&terminal,
&args,
&workspace,
package,
)
})
.collect::<Result<Vec<_>, _>>()?;
for cx in cxs {
sync::sync_all(&cx).map_err(|source| miette::Report::new_boxed(source))?;
}
Ok(())
}
fn set_miette_hook(terminal: &Terminal) {
let stream = terminal.diagnostic_stream();
assert_matches!(stream.kind(), Stream::Stderr);
miette::set_hook(Box::new(move |_| {
Box::new(
MietteHandlerOpts::new()
.color(stream.should_use_color())
.build(),
)
}))
.unwrap();
}
fn install_logger(terminal: &Terminal, verbosity: Verbosity<InfoLevel>) {
let env_filter = if !verbosity.is_present() && env::var_os("RUST_LOG").is_some() {
EnvFilter::from_default_env()
} else {
let default_level = match verbosity.into() {
Some(Level::ERROR) => LevelFilter::ERROR,
Some(Level::WARN) => LevelFilter::WARN,
Some(Level::INFO) => LevelFilter::INFO,
Some(Level::DEBUG) => LevelFilter::DEBUG,
Some(Level::TRACE) => LevelFilter::TRACE,
None => LevelFilter::OFF,
};
EnvFilter::builder()
.with_default_directive(default_level.into())
.from_env_lossy()
};
let stream = terminal.log_stream();
let writer = match stream.kind() {
Stream::Stdout => BoxMakeWriter::new(io::stdout),
Stream::Stderr => BoxMakeWriter::new(io::stderr),
};
tracing_subscriber::fmt()
.with_env_filter(env_filter)
.with_ansi(stream.should_use_color())
.with_writer(writer)
.with_target(false)
.without_time()
.init();
}
fn print_completion(bin_name: &str, shell: &str) {
fn print<G>(bin_name: &str, g: G)
where
G: Generator,
{
clap_complete::generate(g, &mut Args::command(), bin_name, &mut io::stdout());
}
match shell {
"bash" => print(bin_name, Shell::Bash),
"elvish" => print(bin_name, Shell::Elvish),
"fish" => print(bin_name, Shell::Fish),
"powershell" => print(bin_name, Shell::PowerShell),
"zsh" => print(bin_name, Shell::Zsh),
"nushell" => print(bin_name, clap_complete_nushell::Nushell),
_ => panic!(
"error: unknown shell `{shell}`, expected one of `bash`, `elvish`, `fish`, `powershell`, `zsh`, `nushell`"
),
}
}
fn generate_man(output_dir: &str) {
clap_mangen::generate_to(Args::command(), output_dir).unwrap();
}
fn build_package_context<'a>(
manifest_loader: &mut ManifestLoader<'_>,
config_loader: &mut ConfigLoader,
terminal: &'a Terminal,
args: &'a Args,
workspace: &'a Metadata,
package: &'a Package,
) -> miette::Result<PackageSyncContext<'a>> {
let manifest = manifest_loader
.load_package_manifest(package)
.map_err(|source| miette::Report::new_boxed(source))?;
let config = Config::load(manifest_loader, config_loader, args, package)?;
Ok(PackageSyncContext::new(
terminal, args, workspace, package, manifest, config,
))
}