Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

0.1.2 with anyhow error formatting #12

Merged
merged 1 commit into from
Mar 19, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/check.yml
Original file line number Diff line number Diff line change
Expand Up @@ -80,7 +80,7 @@ jobs:
# https://docs.github.com/en/actions/learn-github-actions/contexts#context-availability
strategy:
matrix:
msrv: [1.70.0]
msrv: [1.74.0]
name: ubuntu / ${{ matrix.msrv }}
steps:
- uses: actions/checkout@v4
Expand Down
4 changes: 2 additions & 2 deletions Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,11 @@ resolver = "2"
members = ["cling", "cling-derive"]

[workspace.package]
version = "0.1.1"
version = "0.1.2"
edition = "2021"
authors = ["Ahmed Farghal <[email protected]>"]
license = "Apache-2.0 OR MIT"
rust-version = "1.70.0" # MSRV
rust-version = "1.74.0" # MSRV
keywords = ["click", "cli", "framework"]
repository = "https://github.com/AhmedSoliman/cling"

Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ For more details, see:
- [docs.rs](https://docs.rs/cling/latest/cling/)
- [examples](examples/)

*Compiler support: [requires `rustc` 1.70+][msrv]*
*Compiler support: [requires `rustc` 1.74+][msrv]*

[msrv]: #supported-rust-versions

Expand All @@ -56,6 +56,7 @@ edition = "2021"

[dependencies]
clap = { version = "4.4.1", features = ["derive", "env"] }
cling = { version = "0.1" }
tokio = { version = "1.13.0", features = ["full"] }
```

Expand Down Expand Up @@ -341,7 +342,7 @@ Beep beep!

# Supported Rust Versions

Cling's minimum supported rust version is `1.70.0`.
Cling's minimum supported rust version is `1.74.0`.

# License

Expand Down
2 changes: 1 addition & 1 deletion cling-derive/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ version.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
rust-version = "1.70.0"
rust-version.workspace = true
categories = [
"command-line-interface",
"development-tools::procedural-macro-helpers",
Expand Down
19 changes: 12 additions & 7 deletions cling/Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -6,24 +6,25 @@ version.workspace = true
authors.workspace = true
license.workspace = true
repository.workspace = true
rust-version = "1.70.0"
rust-version.workspace = true
categories = ["command-line-interface"]
include = ["**/*.rs", "../README.md", "../LICENSE-*", "../examples"]
readme = "../README.md"

[dependencies]
cling-derive = { path = "../cling-derive", version = "0.1", optional = true }
clap = { version = "4.3.21", default-features = false, features = [
clap = { version = "4", default-features = false, features = [
"std",
"derive",
] }
anyhow = { version = "1.0" }
async-trait = { version = "0.1.70" }
async-trait = { version = "0.1.77" }
indoc = { version = "2.0" }
itertools = { version = "0.12.1" }
rustversion = "1.0.14"
shlex = { version = "1.1.0", optional = true }
shlex = { version = "1.3.0", optional = true }
static_assertions = { workspace = true }
termcolor = { version = "1.2" }
termcolor = { version = "1.4" }
tracing = { version = "0.1.37", features = ["log"] }


Expand All @@ -40,8 +41,8 @@ colored = { version = "2.0" }
# Use clap with default features in tests
clap = { version = "4.3.21", default-features = true, features = ["derive"] }
# For testing collecting external types
clap-verbosity-flag = { version = "2.0.1" }
env_logger = { version = "0.11.0" }
clap-verbosity-flag = { version = "2.2.0" }
env_logger = { version = "0.11.3" }
log = { version = "0.4.20" }

[build-dependencies]
Expand Down Expand Up @@ -82,6 +83,10 @@ required-features = ["shlex"]
name = "many-handlers"
path = "../examples/many_handlers.rs"

[[example]]
name = "errors"
path = "../examples/errors.rs"

[package.metadata.docs.rs]
all-features = true
default-target = "x86_64-unknown-linux-gnu"
Expand Down
43 changes: 39 additions & 4 deletions cling/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
use std::io::Write;

use clap::CommandFactory;
use itertools::{Itertools, Position};
use termcolor::{Color, ColorChoice, ColorSpec, StandardStream, WriteColor};

use crate::prelude::ClingFinished;
Expand Down Expand Up @@ -101,10 +102,10 @@
write!(f, "Error: {}", e)
}
| CliError::Other(e) => {
write!(f, "Error: {}", e)
write!(f, "Error: {:#}", e)
}
| CliError::OtherWithCode(e, _) => {
write!(f, "Error: {}", e)
write!(f, "Error: {:#}", e)
}
| CliError::InputString => {
write!(f, "Input string cannot be parsed as UNIX shell command")
Expand Down Expand Up @@ -154,10 +155,10 @@
print_formatted_error(&mut stderr, "", e)
}
| CliError::Other(e) => {
print_formatted_error(&mut stderr, "Error: ", &e.to_string())
print_anyhow_error(&mut stderr, "Error: ", e)
}
| CliError::OtherWithCode(e, _) => {
print_formatted_error(&mut stderr, "Error: ", &e.to_string())
print_anyhow_error(&mut stderr, "Error: ", e)
}
| e @ CliError::InputString => {
print_formatted_error(&mut stderr, "", &e.to_string())
Expand Down Expand Up @@ -220,6 +221,40 @@
Ok(())
}

fn print_anyhow_error(
f: &mut StandardStream,
heading: &str,
err: &anyhow::Error,
) -> std::io::Result<()> {
f.set_color(ColorSpec::new().set_fg(Some(Color::Red)).set_bold(true))?;
write!(f, "{}", heading)?;
f.reset()?;
writeln!(f, "{}", err)?;
err.chain()
.skip(1)
.with_position()
.for_each(|(position, cause)| {
if position == Position::First {
let _ =
f.set_color(ColorSpec::new().set_fg(Some(Color::Magenta)));
let _ = writeln!(f, "");

Check warning on line 240 in cling/src/error.rs

View workflow job for this annotation

GitHub Actions / clippy

empty string literal in `writeln!`

warning: empty string literal in `writeln!` --> cling/src/error.rs:240:25 | 240 | let _ = writeln!(f, ""); | ^^^^^^^^^^----^ | | | help: remove the empty string | = help: for further information visit https://rust-lang.github.io/rust-clippy/master/index.html#writeln_empty_string = note: `#[warn(clippy::writeln_empty_string)]` on by default
let _ = writeln!(f, "Caused by:");
let _ = f.reset();
}
let symbol = if position == Position::Last {
"└─"
} else {
"├─"
};
let _ =
f.set_color(ColorSpec::new().set_italic(true).set_dimmed(true));
let _ = write!(f, " {} ", symbol);
let _ = f.reset();
let _ = writeln!(f, "{}", cause);
});
Ok(())
}

pub(crate) fn format_clap_error<I: CommandFactory>(
err: clap::Error,
) -> clap::Error {
Expand Down
26 changes: 26 additions & 0 deletions examples/errors.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
use cling::prelude::*;

#[derive(Run, Parser, Debug, Clone)]
#[cling(run = "run")]
pub struct App {}

// handlers can be sync or async, cling will handle this transparently.
async fn run() -> anyhow::Result<()> {
let err1 = std::io::Error::new(
std::io::ErrorKind::BrokenPipe,
"Fatal disk IO Error!",
);
let err2 = anyhow::Error::new(err1).context("Trying to read a file");
let err3 = err2.context("Can't load application");
let err4 = err3.context("App level error");
Err(err4)
}

#[tokio::main]
async fn main() -> ClingFinished<App> {
env_logger::builder().init();
// Cling::parse().run().await
// Or, return ClingFinished<T> to let cling handle error printing and exit
// code in a more convenient way.
Cling::parse_and_run().await
}
Loading