feat(printer): add support for colored output

This commit is contained in:
Alexandre Pasmantier 2024-07-21 16:16:24 +02:00
parent e69dbb086e
commit 754f79edeb
6 changed files with 132 additions and 17 deletions

1
Cargo.lock generated
View File

@ -306,6 +306,7 @@ dependencies = [
"ignore",
"serde",
"serde_json",
"termcolor",
]
[[package]]

View File

@ -25,6 +25,7 @@ grep = "0.3.1"
ignore = "0.4.22"
serde = { version = "1.0.204", features = ["derive"] }
serde_json = "1.0.120"
termcolor = "1.4.1"
[[bin]]
name = "gg"

View File

@ -40,6 +40,8 @@ Options:
-U, --multiline enable multiline matching
--json output in JSON format
-f, --file-paths-only output file paths only
-A, --absolute-paths output absolute paths (defaults to relative)
-C, --colored-output toggle colored output (defaults to ON)
-h, --help Print help
-V, --version Print version

View File

@ -40,6 +40,14 @@ pub struct Cli {
/// output file paths only
#[clap(short = 'f', long, default_value_t = false)]
pub file_paths_only: bool,
/// output absolute paths (defaults to relative)
#[clap(short = 'A', long, default_value_t = false)]
pub absolute_paths: bool,
/// toggle colored output (defaults to ON)
#[clap(short = 'C', long, default_value_t = true)]
pub colored_output: bool,
}
#[derive(Debug)]
@ -52,6 +60,8 @@ pub struct PostProcessedCli {
pub respect_gitignore: bool,
pub multiline: bool,
pub print_mode: PrintMode,
pub absolute_paths: bool,
pub colored_output: bool,
}
pub fn process_cli_args(cli: Cli) -> anyhow::Result<PostProcessedCli> {
@ -70,5 +80,7 @@ pub fn process_cli_args(cli: Cli) -> anyhow::Result<PostProcessedCli> {
} else {
PrintMode::Text
},
absolute_paths: cli.absolute_paths,
colored_output: cli.colored_output,
})
}

View File

@ -2,6 +2,7 @@ use clap::Parser;
use crossbeam::queue::ArrayQueue;
use ignore::DirEntry;
use printer::PrinterConfig;
use crate::cli::{process_cli_args, Cli};
use crate::fs::walk_builder;
@ -49,7 +50,13 @@ pub fn main() -> anyhow::Result<()> {
})
});
let mut printer = Printer::new(cli_args.print_mode);
let printer_config = PrinterConfig {
mode: cli_args.print_mode,
absolute_paths: cli_args.absolute_paths,
colored_output: cli_args.colored_output,
..Default::default()
};
let mut printer = Printer::new(printer_config);
queue
.into_iter()
.for_each(|file_results| printer.write(file_results).unwrap());

View File

@ -1,8 +1,13 @@
use std::io::{self, Result, StdoutLock, Write};
use std::{
env::current_dir,
io::{Result, Write},
path::{Path, PathBuf},
};
use termcolor::{Buffer, BufferWriter, Color, ColorChoice, ColorSpec, WriteColor};
use crate::search::FileResults;
use crate::search::{FileResults, SearchResult};
#[derive(Debug)]
#[derive(Debug, Eq, PartialEq)]
pub enum PrintMode {
Text,
Json,
@ -10,33 +15,120 @@ pub enum PrintMode {
}
pub struct Printer {
writer: io::BufWriter<StdoutLock<'static>>,
mode: PrintMode,
writer: BufferWriter,
buffer: Buffer,
config: PrinterConfig,
cwd: PathBuf,
}
pub struct PrinterConfig {
pub mode: PrintMode,
// TODO: refactorize this
pub colored_output: bool,
pub color_specs: ColorSpecs,
pub absolute_paths: bool,
}
impl Default for PrinterConfig {
fn default() -> PrinterConfig {
PrinterConfig {
mode: PrintMode::Text,
colored_output: true,
color_specs: ColorSpecs::default(),
absolute_paths: false,
}
}
}
pub struct ColorSpecs {
paths: ColorSpec,
line_numbers: ColorSpec,
lines: ColorSpec,
}
impl Default for ColorSpecs {
fn default() -> ColorSpecs {
let mut paths: ColorSpec = ColorSpec::new();
paths
.set_fg(Some(Color::Green))
.set_italic(true)
.set_bold(true)
.set_underline(true);
let mut line_numbers: ColorSpec = ColorSpec::new();
line_numbers.set_fg(Some(Color::Yellow)).set_bold(true);
let mut lines: ColorSpec = ColorSpec::new();
lines.set_fg(Some(Color::White));
ColorSpecs {
paths,
line_numbers,
lines,
}
}
}
impl Printer {
pub fn new(mode: PrintMode) -> Printer {
let stdout = io::stdout();
let lock = stdout.lock();
pub fn new(config: PrinterConfig) -> Printer {
let color_choice = if config.mode == PrintMode::Text {
ColorChoice::Always
} else {
ColorChoice::Never
};
let bufwriter = BufferWriter::stdout(color_choice);
let buffer = bufwriter.buffer();
Printer {
writer: io::BufWriter::new(lock),
mode,
writer: bufwriter,
buffer,
config,
cwd: current_dir().unwrap(),
}
}
pub fn write(&mut self, results: FileResults) -> Result<()> {
self.writeln_to_buffer(match self.mode {
PrintMode::Text => format!("{}", results),
PrintMode::Json => serde_json::to_string(&results)?,
PrintMode::Files => format!("{}", results.path.to_string_lossy()),
let path: &Path;
if self.config.absolute_paths {
path = &results.path;
} else {
path = results.path.strip_prefix(self.cwd.clone()).unwrap();
}
match self.config.mode {
PrintMode::Text => self.write_colored(path, results.results),
PrintMode::Json => self.writeln_to_buffer(serde_json::to_string(&results)?),
PrintMode::Files => {
self.writeln_to_buffer(format!("{}", results.path.to_string_lossy()))
}
}
}
fn write_colored(&mut self, path: &Path, search_results: Vec<SearchResult>) -> Result<()> {
self.write_colored_path(path)?;
self.write_colored_search_results(search_results)?;
self.write_newline_to_buffer()
}
fn write_colored_path(&mut self, path: &Path) -> Result<()> {
self.buffer.set_color(&self.config.color_specs.paths)?;
writeln!(&mut self.buffer, "{}", path.to_string_lossy())
}
fn write_colored_search_results(&mut self, results: Vec<SearchResult>) -> Result<()> {
results.iter().try_for_each(|result| {
self.buffer
.set_color(&self.config.color_specs.line_numbers)?;
write!(&mut self.buffer, "{}:\t", result.line_number)?;
self.buffer.set_color(&self.config.color_specs.lines)?;
write!(&mut self.buffer, "{}", result.line)
})
}
fn writeln_to_buffer(&mut self, text: String) -> Result<()> {
writeln!(self.writer, "{}", text)
writeln!(self.buffer, "{}", text)
}
fn write_newline_to_buffer(&mut self) -> Result<()> {
writeln!(self.buffer, "")
}
pub fn print(&mut self) -> Result<()> {
self.writer.flush()
self.writer.print(&self.buffer)
}
}