Skip to content

Commit

Permalink
Move to anstream/anstyle + set CLICOLOR_FORCE=1
Browse files Browse the repository at this point in the history
  • Loading branch information
nilehmann committed Jul 6, 2024
1 parent ac68b7c commit 4e3f6eb
Show file tree
Hide file tree
Showing 6 changed files with 111 additions and 102 deletions.
21 changes: 2 additions & 19 deletions Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

3 changes: 2 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,9 +6,10 @@ version = "0.1.0"
# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

[dependencies]
anstream = "0.6.14"
anstyle = "1.0.7"
clap = { version = "4.5.8", features = ["derive", "wrap_help"] }
regex = "1.10.5"
serde = { version = "1.0.203", features = ["derive"] }
termcolor = "1.4.1"
termion = "4.0.2"
toml = "0.8.14"
34 changes: 19 additions & 15 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
# Backtracetk

Backtracetk is a command line tool to print colorized Rust backtraces without the need to add extra
dependencies to your project.
It works by capturing the output of a process, detecting anything that looks like a backtrace, and then printing
it with colors to be easier on the eyes.
It also prints snippets of the code at each frame if it can find them in the file system.
Backtracetk is a command-line tool that prints colorized Rust backtraces without needing extra dependencies. It works by capturing the output of a process, detecting anything that looks like a backtrace, and then printing it with colors to make it easier on the eyes. It also prints snippets of the code at each frame if it can find them in the filesystem.

Backtracetk is useful in situations where you can't or don't want to add runtime dependencies to your project. It is also more dynamic, allowing you to run the process many times (if it's cheap to do so) and adjust the output accordingly.

If you're okay with adding dependencies, consider looking at [color-eyre](https://crates.io/crates/color-eyre) or [color-backtrace](https://crates.io/crates/color-backtrace).

## Installation

Expand All @@ -28,20 +28,24 @@ Arguments:
[CMD]...

Options:
--style <STYLE> Set the backtrace style to short (RUST_BACKTRACE=1) or full
(RUST_BACKTRACE=full) [default: short] [possible values: short, full]
--enable-lib-backtrace By default, backtracetk sets RUST_LIB_BACKTRACE=0. Set this flag to revert this
behavior
--hide-output By default, backtracetk prints each captured line as it reads it, providing
immediate feedback. If this flag is set, this output is suppressed, and nothing
will be printed until the program exits
-h, --help Print help

--style <STYLE> Set the backtrace style to `short` (RUST_BACKTRACE=1) or `full`
(RUST_BACKTRACE=full) [default: short] [possible values: short, full]
--lib-backtrace <LIB_BACKTRACE> Enable or disable `Backtrace::capture`. If this flag is set to `no`,
backtracetk sets RUST_LIB_BACKTRACE=0, disabling
`Backtrace::capture`. If the flag is set to `yes`, no changes are
made, and the default behavior of capturing backtraces remains
enabled [default: no] [possible values: yes, no]
--clicolor-force <CLICOLOR_FORCE> If this flag is `yes`, set CLICOLOR_FORCE=1. If the flag is `no`, no
changes are made [default: yes] [possible values: yes, no]
--hide-output By default, backtracetk prints each captured line as it reads it,
providing immediate feedback. If this flag is set, this output is
suppressed, and nothing will be printed until the program exits
-h, --help Print help
```
### Configuration
Backtracetk will attempt to locate a configuration file named `backtrack.toml` or `.backtrack.toml` in the parent directories starting from where the command is executed. Currently, the only supported configuration is `hide`, which accepts a list of regex patterns.
Any frame matching on of these patterns will be hidden from the output. For example:
Any frame matching one of these patterns will be hidden from the output. For example:
![Screenshot2](./screenshot2.png)
Binary file modified screenshot2.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
106 changes: 55 additions & 51 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,44 +1,53 @@
use std::{
fs::File,
io::{self, BufRead, Write},
io::{self, BufRead},
path::Path,
};

use regex::Regex;
use termcolor::{Color, ColorSpec, StandardStream, WriteColor};

const GREEN: anstyle::Style =
anstyle::Style::new().fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Green)));
const CYAN: anstyle::Style =
anstyle::Style::new().fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Cyan)));
const RED: anstyle::Style =
anstyle::Style::new().fg_color(Some(anstyle::Color::Ansi(anstyle::AnsiColor::Red)));
const BOLD: anstyle::Style = anstyle::Style::new().bold();
const RESET: anstyle::Reset = anstyle::Reset;

pub struct Backtrace {
frames: Vec<Frame>,
panic_info: Option<PanicInfo>,
}

impl Backtrace {
pub fn render(&self, out: &mut StandardStream, filter: &mut impl Filter) -> io::Result<()> {
pub fn render(&self, filter: &mut impl Filter) -> io::Result<()> {
if self.frames.is_empty() {
return Ok(());
}
let framnow = self.compute_frameno_width();
let linenow = self.compute_lineno_width();
let width = self.compute_width(framnow);
writeln!(out, "\n{:━^width$}", " BACKTRACE ")?;
anstream::println!("\n{:━^width$}", " BACKTRACE ");

let mut hidden = 0;
for frame in self.frames.iter().rev() {
if filter.exclude(frame) {
hidden += 1;
} else {
print_hidden_frames_message(out, hidden)?;
frame.render(out, framnow, linenow)?;
print_hidden_frames_message(hidden, width)?;
frame.render(framnow, linenow)?;
hidden = 0;
}
}
print_hidden_frames_message(out, hidden)?;
print_hidden_frames_message(hidden, width)?;

if let Some(panic_info) = &self.panic_info {
panic_info.render(out)?;
panic_info.render()?;
}

writeln!(out)
println!();
Ok(())
}

fn compute_lineno_width(&self) -> usize {
Expand Down Expand Up @@ -81,15 +90,17 @@ pub struct Frame {
}

impl Frame {
fn render(&self, out: &mut StandardStream, framenow: usize, linenow: usize) -> io::Result<()> {
write!(out, "{:>framenow$}: ", self.frameno)?;

out.set_color(ColorSpec::new().set_fg(Some(Color::Green)))?;
writeln!(out, "{}", self.function)?;
out.set_color(&ColorSpec::new())?;
fn render(&self, framenow: usize, linenow: usize) -> io::Result<()> {
anstream::println!(
"{:>framenow$}: {GREEN}{}{RESET}",
self.frameno,
self.function
);

if let Some(source_info) = &self.source_info {
source_info.render(out, framenow, linenow)?;
let padding = Padding(framenow);
anstream::println!("{padding} at {source_info}");
source_info.render_code(padding, linenow)?;
}
Ok(())
}
Expand All @@ -111,34 +122,18 @@ pub struct SourceInfo {
colno: usize,
}

fn print_hidden_frames_message(out: &mut StandardStream, hidden: u32) -> io::Result<()> {
out.set_color(ColorSpec::new().set_fg(Some(Color::Cyan)))?;
match hidden {
0 => {}
1 => {
writeln!(out, " ({hidden} frame hidden)")?;
}
_ => {
writeln!(out, " ({hidden} frames hidden)")?;
}
}
out.set_color(&ColorSpec::new())
fn print_hidden_frames_message(hidden: u32, width: usize) -> io::Result<()> {
let msg = match hidden {
0 => return Ok(()),
1 => format!(" ({hidden} frame hidden) "),
_ => format!(" ({hidden} frames hidden) "),
};
anstream::println!("{CYAN}{msg:┄^width$}{RESET}");
Ok(())
}

impl SourceInfo {
fn render(&self, out: &mut StandardStream, framenow: usize, linenow: usize) -> io::Result<()> {
write!(out, "{:framenow$} at ", "")?;
writeln!(out, "{self}")?;
self.render_code(out, framenow, linenow)?;
Ok(())
}

fn render_code(
&self,
out: &mut StandardStream,
framenow: usize,
linenow: usize,
) -> io::Result<()> {
fn render_code(&self, padding: Padding, linenow: usize) -> io::Result<()> {
let path = Path::new(&self.file);
if path.exists() {
let lineno = self.lineno - 1;
Expand All @@ -150,15 +145,13 @@ impl SourceInfo {
.skip(lineno.saturating_sub(2))
.take(5)
.collect();

for (i, line) in viewport {
if i == lineno {
out.set_color(ColorSpec::new().set_bold(true))?;
anstream::print!("{BOLD}");
}
write!(out, "{:framenow$} {:>linenow$} | ", "", i + 1)?;
writeln!(out, "{}", line?)?;
anstream::println!("{padding} {:>linenow$} | {}", i + 1, line?);
if i == lineno {
out.set_color(ColorSpec::new().set_bold(false))?;
anstream::print!("{RESET}");
}
}
}
Expand All @@ -178,13 +171,24 @@ impl std::fmt::Display for SourceInfo {
}

impl PanicInfo {
fn render(&self, out: &mut StandardStream) -> io::Result<()> {
out.set_color(ColorSpec::new().set_fg(Some(Color::Red)))?;
writeln!(out, "thread '{}' panicked at {}", self.thread, self.at)?;
fn render(&self) -> io::Result<()> {
anstream::print!("{RED}");
anstream::println!("thread '{}' panickd at {}", self.thread, self.at);
for line in &self.message {
writeln!(out, "{}", line)?;
anstream::println!("{line}");
}
anstream::print!("{RESET}");
Ok(())
}
}

struct Padding(usize);

impl std::fmt::Display for Padding {
fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
for _ in 0..self.0 {
write!(f, " ")?;
}
out.set_color(&ColorSpec::new())?;
Ok(())
}
}
Expand Down
49 changes: 33 additions & 16 deletions src/main.rs
Original file line number Diff line number Diff line change
Expand Up @@ -6,22 +6,27 @@ use backtracetk::Frame;
use clap::Parser;
use regex::Regex;
use serde::Deserialize;
use termcolor::{ColorChoice, StandardStream};

/// Print colorized Rust backtraces by capturing the output of an external process.
#[derive(clap::Parser)]
#[command(max_term_width = 110)]
#[command(max_term_width = 110, arg_required_else_help = true)]
struct Args {
#[arg(trailing_var_arg(true))]
cmd: Vec<String>,

/// Set the backtrace style to short (RUST_BACKTRACE=1) or full (RUST_BACKTRACE=full)
/// Set the backtrace style to `short` (RUST_BACKTRACE=1) or `full` (RUST_BACKTRACE=full)
#[arg(long, default_value = "short")]
style: BacktraceStyle,

/// By default, backtracetk sets RUST_LIB_BACKTRACE=0. Set this flag to revert this behavior
#[arg(long)]
enable_lib_backtrace: bool,
/// Enable or disable `Backtrace::capture`. If this flag is set to `no`, backtracetk sets
/// RUST_LIB_BACKTRACE=0, disabling `Backtrace::capture`. If the flag is set to `yes`, no
/// changes are made, and the default behavior of capturing backtraces remains enabled.
#[arg(long, default_value = "no")]
lib_backtrace: YesNo,

/// If this flag is `yes`, set CLICOLOR_FORCE=1. If the flag is `no`, no changes are made.
#[arg(long, default_value = "yes")]
clicolor_force: YesNo,

/// By default, backtracetk prints each captured line as it reads it, providing immediate feedback.
/// If this flag is set, this output is suppressed, and nothing will be printed until the program
Expand All @@ -36,6 +41,22 @@ enum BacktraceStyle {
Full,
}

#[derive(clap::ValueEnum, Copy, Clone, Debug)]
enum YesNo {
Yes,
No,
}

impl YesNo {
fn is_yes(&self) -> bool {
matches!(self, Self::Yes)
}

fn is_no(&self) -> bool {
matches!(self, Self::No)
}
}

impl BacktraceStyle {
fn env_var_str(&self) -> &'static str {
match self {
Expand All @@ -47,16 +68,15 @@ impl BacktraceStyle {
fn main() -> io::Result<()> {
let mut args = Args::parse();

if args.cmd.len() == 0 {
std::process::exit(1);
}

let config = read_config();

let mut env_vars = vec![("RUST_BACKTRACE", args.style.env_var_str())];
if !args.enable_lib_backtrace {
if args.lib_backtrace.is_no() {
env_vars.push(("RUST_LIB_BACKTRACE", "0"));
}
if args.clicolor_force.is_yes() {
env_vars.push(("CLICOLOR_FORCE", "1"));
}

println!("$ {}", args.cmd.join(" "));

Expand All @@ -78,13 +98,11 @@ fn main() -> io::Result<()> {
for line in BufReader::new(stderr).lines() {
let line = line?;
if !args.hide_output {
eprintln!("{line}");
anstream::eprintln!("{line}");
}
parser.parse_line(line);
}

let mut stderr = StandardStream::stderr(ColorChoice::Auto);

let mut filter = |frame: &Frame| {
for regex in &config.hide {
if regex.is_match(&frame.function) {
Expand All @@ -94,7 +112,7 @@ fn main() -> io::Result<()> {
false
};
for backtrace in parser.into_backtraces() {
backtrace.render(&mut stderr, &mut filter)?;
backtrace.render(&mut filter)?;
}

Ok(())
Expand All @@ -119,7 +137,6 @@ fn read_config() -> Config {
}

fn find_config_file() -> Option<std::path::PathBuf> {
// find config file in current or parent directories
let mut path = std::env::current_dir().unwrap();
loop {
for name in ["backtracetk.toml", ".backtracetk.toml"] {
Expand Down

0 comments on commit 4e3f6eb

Please sign in to comment.