use rgrc::{
ColorMode,
args::{get_completion_script, parse_args},
buffer::LineBufferedWriter,
colorizer::colorize_regex as colorize,
grc::GrcatConfigEntry,
load_rules_for_command,
utils::{SUPPORTED_COMMANDS, command_exists, should_use_colorization_for_command_supported},
};
#[cfg(feature = "debug")]
use rgrc::args::DebugLevel;
#[cfg(feature = "debug")]
use rgrc::colorize_regex_with_debug;
use std::io::{self, IsTerminal, Write};
use std::process::{Command, Stdio};
#[cfg(feature = "debug")]
use std::time::Instant;
fn handle_box_error(e: Box<dyn std::error::Error>) -> Result<(), Box<dyn std::error::Error>> {
match e.downcast::<std::io::Error>() {
Ok(io_err) => handle_io_error(*io_err),
Err(e) => Err(e),
}
}
fn handle_io_error(e: std::io::Error) -> Result<(), Box<dyn std::error::Error>> {
if e.kind() == std::io::ErrorKind::BrokenPipe {
std::process::exit(0);
}
Err(Box::new(e))
}
#[cfg(not(target_env = "msvc"))]
#[global_allocator]
static GLOBAL: mimalloc::MiMalloc = mimalloc::MiMalloc;
#[cfg(feature = "embed-configs")]
fn flush_and_rebuild_cache() {
use rgrc::EMBEDDED_CONFIGS;
println!("Flushing and rebuilding cache directory...");
match rgrc::flush_and_rebuild_cache() {
Some((cache_dir, config_count)) => {
println!("Cache rebuild successful!");
println!(" Location: {}", cache_dir.display());
println!(" Main config: rgrc.conf");
println!(" Color configs: {} files in conf/", config_count);
println!(" Total embedded configs: {}", EMBEDDED_CONFIGS.len());
}
None => {
eprintln!("Error: Failed to rebuild cache directory");
std::process::exit(1);
}
}
}
fn main() -> Result<(), Box<dyn std::error::Error>> {
let args = match parse_args() {
Ok(args) => args,
Err(e) => {
eprintln!("Error: {}", e);
std::process::exit(1);
}
};
if args.show_version {
println!("rgrc {}", env!("CARGO_PKG_VERSION"));
std::process::exit(0);
}
if let Some(shell) = args.show_completions.as_deref() {
match get_completion_script(shell) {
Some(script) => {
print!("{}", script);
std::process::exit(0);
}
None => {
eprintln!("Unsupported shell for completions: {}", shell);
std::process::exit(1);
}
}
}
if args.show_aliases || args.show_all_aliases {
let grc = std::env::current_exe()
.ok()
.and_then(|p| p.file_name().map(|n| n.to_string_lossy().into()))
.unwrap_or_else(|| "rgrc".to_string());
let except_set: std::collections::HashSet<String> = args
.except_aliases
.iter()
.flat_map(|s| s.split(',').map(|p| p.trim().to_string()))
.collect();
for cmd in SUPPORTED_COMMANDS {
if !except_set.contains(cmd as &str) && (args.show_all_aliases || command_exists(cmd)) {
if cmd == &"journalctl" {
println!("alias {}='{} journalctl --no-pager | less -R'", cmd, grc);
} else {
println!("alias {}='{} {}'", cmd, grc, cmd);
}
}
}
std::process::exit(0);
}
#[cfg(feature = "embed-configs")]
if args.flush_cache {
flush_and_rebuild_cache();
std::process::exit(0);
}
if let Some(ref config_name) = args.config {
let color_mode = args.color;
let stdout_is_terminal = io::stdout().is_terminal();
let should_colorize = match color_mode {
ColorMode::Off => false,
ColorMode::On => true,
ColorMode::Auto => stdout_is_terminal,
};
if !should_colorize {
let stdin = io::stdin();
let stdout = io::stdout();
let mut reader = io::BufReader::new(stdin.lock());
let mut writer = io::BufWriter::new(stdout.lock());
match io::copy(&mut reader, &mut writer) {
Ok(_) => {
let _ = writer.flush();
std::process::exit(0);
}
Err(e) => {
if e.kind() != std::io::ErrorKind::BrokenPipe {
eprintln!("Error copying stdin to stdout: {}", e);
}
let _ = writer.flush();
std::process::exit(0);
}
}
}
let rules: Vec<GrcatConfigEntry> = load_rules_for_command(config_name);
if rules.is_empty() {
let stdin = io::stdin();
let stdout = io::stdout();
let mut reader = io::BufReader::new(stdin.lock());
let mut writer = io::BufWriter::new(stdout.lock());
match io::copy(&mut reader, &mut writer) {
Ok(_) => {
let _ = writer.flush();
std::process::exit(0);
}
Err(e) => {
if e.kind() != std::io::ErrorKind::BrokenPipe {
eprintln!(
"Error: Failed to load rules for config '{}': No matching rules found",
config_name
);
}
let _ = writer.flush();
std::process::exit(1);
}
}
}
let stdin = io::stdin();
let mut buffered_stdin = io::BufReader::with_capacity(64 * 1024, stdin.lock());
let mut buffered_stdout = io::BufWriter::with_capacity(64 * 1024, io::stdout());
let mut line_buffered_writer = LineBufferedWriter::new(&mut buffered_stdout);
#[cfg(feature = "debug")]
{
if args.debug_level != DebugLevel::Off {
if let Err(e) = colorize_regex_with_debug(
&mut buffered_stdin,
&mut line_buffered_writer,
rules.as_slice(),
args.debug_level,
) {
handle_box_error(e)?;
}
} else if let Err(e) = colorize(
&mut buffered_stdin,
&mut line_buffered_writer,
rules.as_slice(),
) {
handle_box_error(e)?;
}
}
#[cfg(not(feature = "debug"))]
{
if let Err(e) = colorize(
&mut buffered_stdin,
&mut line_buffered_writer,
rules.as_slice(),
) {
handle_box_error(e)?;
}
}
if let Err(e) = buffered_stdout.flush() {
handle_io_error(e)?;
}
std::process::exit(0);
}
if args.command.is_empty() {
eprintln!("No command specified.");
std::process::exit(1);
}
let color_mode = args.color;
let command_name = args.command.first().unwrap();
let stdout_is_terminal = io::stdout().is_terminal();
let should_colorize = match color_mode {
ColorMode::Off => false,
ColorMode::On => should_use_colorization_for_command_supported(command_name),
ColorMode::Auto => {
stdout_is_terminal && should_use_colorization_for_command_supported(command_name)
}
};
let pseudo_command = args.command.join(" ");
let should_colorize = if should_colorize {
!rgrc::utils::pseudo_command_excluded(&pseudo_command)
} else {
false
};
#[cfg(feature = "debug")]
let record_time = std::env::var_os("RGRCTIME").is_some();
#[cfg(feature = "debug")]
let t0 = if record_time {
Some(Instant::now())
} else {
None
};
#[cfg(feature = "debug")]
let t_load_start = if record_time {
Some(Instant::now())
} else {
None
};
let rules: Vec<GrcatConfigEntry> = if should_colorize {
load_rules_for_command(&pseudo_command)
} else {
Vec::new()
};
#[cfg(feature = "debug")]
if let Some(start) = t_load_start.filter(|_| record_time) {
eprintln!(
"[rgrc:time] load_rules_for_command: {:} in {:?}",
&pseudo_command,
start.elapsed()
);
}
let mut cmd = Command::new(command_name);
cmd.args(args.command.iter().skip(1));
if !should_colorize && stdout_is_terminal {
cmd.stdout(Stdio::inherit()); cmd.stderr(Stdio::inherit());
let mut child = match cmd.spawn() {
Ok(c) => c,
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
eprintln!("Error: command not found: '{}'", command_name);
std::process::exit(127);
} else {
eprintln!("Failed to spawn '{}': {}", command_name, e);
std::process::exit(1);
}
}
};
let ecode = match child.wait() {
Ok(status) => status,
Err(e) => {
eprintln!("Failed while waiting for '{}': {}", command_name, e);
std::process::exit(1);
}
};
std::process::exit(ecode.code().unwrap_or(1));
}
if should_colorize && rules.is_empty() {
cmd.stdout(Stdio::inherit());
cmd.stderr(Stdio::inherit());
let mut child = cmd.spawn().expect("failed to spawn command");
let ecode = child.wait().expect("failed to wait on child");
std::process::exit(ecode.code().unwrap_or(1));
}
cmd.stdout(Stdio::piped());
let mut child = match cmd.spawn() {
Ok(c) => c,
Err(e) => {
if e.kind() == std::io::ErrorKind::NotFound {
eprintln!("Error: command not found: '{}'", command_name);
std::process::exit(127);
} else {
eprintln!("Failed to spawn '{}': {}", command_name, e);
std::process::exit(1);
}
}
};
#[cfg(feature = "debug")]
if let Some(start) = t0.filter(|_| record_time) {
eprintln!("[rgrc:time] spawn child: {:?}", start.elapsed());
}
let mut stdout = child
.stdout
.take()
.expect("child did not have a handle to stdout");
let mut buffered_stdout = std::io::BufReader::with_capacity(64 * 1024, &mut stdout);
let mut buffered_writer = std::io::BufWriter::with_capacity(64 * 1024, std::io::stdout());
let mut line_buffered_writer = LineBufferedWriter::new(&mut buffered_writer);
#[cfg(feature = "debug")]
{
if args.debug_level != DebugLevel::Off {
if let Err(e) = colorize_regex_with_debug(
&mut buffered_stdout,
&mut line_buffered_writer,
rules.as_slice(),
args.debug_level,
) {
handle_box_error(e)?;
}
} else {
#[cfg(feature = "debug")]
{
if record_time {
let t_before_colorize = Instant::now();
if let Err(e) = colorize(
&mut buffered_stdout,
&mut line_buffered_writer,
rules.as_slice(),
) {
handle_box_error(e)?;
}
eprintln!("[rgrc:time] colorize: {:?}", t_before_colorize.elapsed());
} else {
colorize(
&mut buffered_stdout,
&mut line_buffered_writer,
rules.as_slice(),
)?;
}
}
#[cfg(not(feature = "debug"))]
{
if let Err(e) = colorize(
&mut buffered_stdout,
&mut line_buffered_writer,
rules.as_slice(),
) {
handle_box_error(e)?;
}
}
}
}
#[cfg(not(feature = "debug"))]
{
#[cfg(feature = "debug")]
{
if record_time {
let t_before_colorize = Instant::now();
if let Err(e) = colorize(
&mut buffered_stdout,
&mut line_buffered_writer,
rules.as_slice(),
) {
handle_box_error(e)?;
}
eprintln!("[rgrc:time] colorize: {:?}", t_before_colorize.elapsed());
} else {
if let Err(e) = colorize(
&mut buffered_stdout,
&mut line_buffered_writer,
rules.as_slice(),
) {
handle_box_error(e)?;
}
}
}
#[cfg(not(feature = "debug"))]
{
if let Err(e) = colorize(
&mut buffered_stdout,
&mut line_buffered_writer,
rules.as_slice(),
) {
handle_box_error(e)?;
};
}
}
if let Err(e) = buffered_writer.flush() {
handle_io_error(e)?;
}
let ecode = child.wait().expect("failed to wait on child");
std::process::exit(ecode.code().expect("need an exit code"));
}