From a1c5725c5503fa76e8ffd60e8af31faf5eb38195 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Mon, 13 May 2024 14:56:47 +0200 Subject: [PATCH 01/66] add tests for the join module #79 --- members/libpt-bintols/tests/join.rs | 55 +++++++++++++++++++++++++++++ 1 file changed, 55 insertions(+) create mode 100644 members/libpt-bintols/tests/join.rs diff --git a/members/libpt-bintols/tests/join.rs b/members/libpt-bintols/tests/join.rs new file mode 100644 index 0000000..35a76c7 --- /dev/null +++ b/members/libpt-bintols/tests/join.rs @@ -0,0 +1,55 @@ +use libpt_bintols::join::*; + +#[test] +fn join_u128() { + let correct = [ + 16, + 255, + 256, + 0, + u128::MAX, + u64::MAX as u128, + u64::MAX as u128 + 1, + ]; + let source = [ + vec![16], + vec![255], + vec![1, 0], + vec![0], + vec![ + 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, 255, + ], + vec![255, 255, 255, 255, 255, 255, 255, 255], + vec![1, 0, 0, 0, 0, 0, 0, 0, 0], + ]; + for (i, n) in source.iter().enumerate() { + assert_eq!(array_to_unsigned::(n).unwrap(), correct[i]); + } +} + +#[test] +fn join_u64() { + let correct = [ + 16, + 255, + 256, + 0, + u64::MAX, + u32::MAX as u64, + 0b1_00000001, + 0b10011011_10110101_11110000_00110011, + ]; + let source = [ + vec![16], + vec![255], + vec![1, 0], + vec![0], + vec![255, 255, 255, 255, 255, 255, 255, 255], + vec![255, 255, 255, 255], + vec![1, 1], + vec![0b10011011, 0b10110101, 0b11110000, 0b00110011], + ]; + for (i, n) in source.iter().enumerate() { + assert_eq!(array_to_unsigned::(n).unwrap(), correct[i]); + } +} -- 2.40.1 From e9c3402aff76d37221ab8d8248dc9be11b9bf3a9 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Mon, 13 May 2024 12:59:44 +0000 Subject: [PATCH 02/66] automatic cargo CI changes --- members/libpt-bintols/src/lib.rs | 2 +- members/libpt-bintols/src/split.rs | 3 +-- 2 files changed, 2 insertions(+), 3 deletions(-) diff --git a/members/libpt-bintols/src/lib.rs b/members/libpt-bintols/src/lib.rs index 1680664..e69706c 100644 --- a/members/libpt-bintols/src/lib.rs +++ b/members/libpt-bintols/src/lib.rs @@ -25,5 +25,5 @@ pub const YOBI: u128 = 2u128.pow(80); // use libpt_core; pub mod datalayout; pub mod display; -pub mod split; pub mod join; +pub mod split; diff --git a/members/libpt-bintols/src/split.rs b/members/libpt-bintols/src/split.rs index c996ef7..3fb3a7d 100644 --- a/members/libpt-bintols/src/split.rs +++ b/members/libpt-bintols/src/split.rs @@ -3,7 +3,6 @@ //! Sometimes, you need a large integer in the form of many bytes, so split into [u8]. //! Rust provides - /// Split unsigned integers into a [Vec] of [u8]s /// /// Say you have the [u32] 1717 (binary: `00000000 00000000 00000110 10110101 `). This number would @@ -23,7 +22,7 @@ /// ``` pub fn unsigned_to_vec(num: T) -> Vec where - u128: std::convert::From + u128: std::convert::From, { let mut num: u128 = num.into(); if num == 0 { -- 2.40.1 From 22d5b3ac07d01cbf44ced0db17ece65a38cf37e3 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Mon, 13 May 2024 15:13:35 +0200 Subject: [PATCH 03/66] refactor(bintols-join): return err if the result cannot be converted to T #80 #79 --- members/libpt-bintols/src/join.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/members/libpt-bintols/src/join.rs b/members/libpt-bintols/src/join.rs index 35efcd7..96c8e6e 100644 --- a/members/libpt-bintols/src/join.rs +++ b/members/libpt-bintols/src/join.rs @@ -27,7 +27,13 @@ pub fn array_to_unsigned(parts: &[u8]) -> anyhow::Result where u128: std::convert::From, T: std::str::FromStr, + T: std::convert::TryFrom, ::Err: std::fmt::Debug, + ::Err: std::error::Error, + >::Error: std::error::Error, + >::Error: std::marker::Send, + >::Error: std::marker::Sync, + >::Error: 'static, { trace!("amount of parts: {}", parts.len()); if parts.len() > (u128::BITS / 8) as usize { @@ -40,5 +46,5 @@ where for (i, e) in parts.iter().rev().enumerate() { ri += (*e as u128) * 256u128.pow(i as u32); } - Ok(ri.to_string().parse().unwrap()) + T::try_from(ri).map_err(anyhow::Error::from) } -- 2.40.1 From 101e20da439b0dc0ecf4458a53f3d888e45c967d Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Thu, 27 Jun 2024 21:10:49 +0200 Subject: [PATCH 04/66] chore: bump version to v0.6.0-alpha.0 --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 354ca92..e9e9ba2 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -10,7 +10,7 @@ default-members = [".", "members/libpt-core"] [workspace.package] publish = true -version = "0.5.1" +version = "0.6.0-alpha.0" edition = "2021" authors = ["Christoph J. Scherr "] license = "MIT" -- 2.40.1 From 940b5ffa1a98b7a130cc12f15c67ebe4c2af499a Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Thu, 27 Jun 2024 21:20:07 +0200 Subject: [PATCH 05/66] fix(cargo): remove path for the libpt version used in libpt-py otherwise, libpt-py always uses the latest libpt version by path that is in the workspace, which is bad if the libpt version is ahead of the libpt-py version. --- members/libpt-py/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/members/libpt-py/Cargo.toml b/members/libpt-py/Cargo.toml index 0422edc..8be932e 100644 --- a/members/libpt-py/Cargo.toml +++ b/members/libpt-py/Cargo.toml @@ -19,7 +19,7 @@ name = "libpt" crate-type = ["cdylib", "rlib"] [dependencies] -libpt = { version = "0.5.0", path = "../.." } +libpt = { version = "0.5.0"} pyo3 = { version = "0.19.0", features = ["full"] } anyhow.workspace = true -- 2.40.1 From 630c50a64a82789dc3718f1368c2c83a1f3bcfaa Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Thu, 27 Jun 2024 21:27:18 +0200 Subject: [PATCH 06/66] feat(cli): add libpt-cli and reexport essential cli deps #84 --- Cargo.toml | 4 ++++ members/libpt-cli/Cargo.toml | 20 ++++++++++++++++++++ members/libpt-cli/src/lib.rs | 6 ++++++ members/libpt-cli/src/repl/mod.rs | 0 4 files changed, 30 insertions(+) create mode 100644 members/libpt-cli/Cargo.toml create mode 100644 members/libpt-cli/src/lib.rs create mode 100644 members/libpt-cli/src/repl/mod.rs diff --git a/Cargo.toml b/Cargo.toml index e9e9ba2..1536913 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,6 +5,7 @@ members = [ "members/libpt-core", "members/libpt-log", "members/libpt-py", + "members/libpt-cli", ] default-members = [".", "members/libpt-core"] @@ -31,6 +32,7 @@ thiserror = "1.0.56" libpt-core = { version = "0.4.0", path = "members/libpt-core" } libpt-bintols = { version = "0.5.1", path = "members/libpt-bintols" } libpt-log = { version = "0.4.2", path = "members/libpt-log" } +libpt-cli = { version = "0.1.0", path = "members/libpt-cli" } [package] name = "libpt" @@ -52,6 +54,7 @@ core = [] full = ["default", "core", "log", "bintols"] log = ["dep:libpt-log"] bintols = ["dep:libpt-bintols", "log"] +cli = ["dep:libpt-cli", "core"] # py = ["dep:libpt-py"] [lib] @@ -66,3 +69,4 @@ crate-type = [ libpt-core = { workspace = true } libpt-bintols = { workspace = true, optional = true } libpt-log = { workspace = true, optional = true } +libpt-cli = { workspace = true, optional = true } diff --git a/members/libpt-cli/Cargo.toml b/members/libpt-cli/Cargo.toml new file mode 100644 index 0000000..c8f9a60 --- /dev/null +++ b/members/libpt-cli/Cargo.toml @@ -0,0 +1,20 @@ +[package] +name = "libpt-cli" +publish.workspace = true +version = "0.1.0" +edition.workspace = true +authors.workspace = true +license.workspace = true +description.workspace = true +readme.workspace = true +homepage.workspace = true +repository.workspace = true +keywords.workspace = true +categories.workspace = true + +[dependencies] +clap = "4.5.7" +comfy-table = "7.1.1" +console = "0.15.8" +dialoguer = "0.11.0" +indicatif = "0.17.8" diff --git a/members/libpt-cli/src/lib.rs b/members/libpt-cli/src/lib.rs new file mode 100644 index 0000000..8d79d47 --- /dev/null +++ b/members/libpt-cli/src/lib.rs @@ -0,0 +1,6 @@ +pub mod repl; + +pub use indicatif; +pub use console; +pub use dialoguer; +pub use comfy_table; diff --git a/members/libpt-cli/src/repl/mod.rs b/members/libpt-cli/src/repl/mod.rs new file mode 100644 index 0000000..e69de29 -- 2.40.1 From 999cf630e8c9ffd3a7a3d74f285a7a35b209f953 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Thu, 27 Jun 2024 19:29:32 +0000 Subject: [PATCH 07/66] automatic cargo CI changes --- members/libpt-cli/src/lib.rs | 4 ++-- members/libpt-cli/src/repl/mod.rs | 1 + 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/members/libpt-cli/src/lib.rs b/members/libpt-cli/src/lib.rs index 8d79d47..1c8bb52 100644 --- a/members/libpt-cli/src/lib.rs +++ b/members/libpt-cli/src/lib.rs @@ -1,6 +1,6 @@ pub mod repl; -pub use indicatif; +pub use comfy_table; pub use console; pub use dialoguer; -pub use comfy_table; +pub use indicatif; diff --git a/members/libpt-cli/src/repl/mod.rs b/members/libpt-cli/src/repl/mod.rs index e69de29..8b13789 100644 --- a/members/libpt-cli/src/repl/mod.rs +++ b/members/libpt-cli/src/repl/mod.rs @@ -0,0 +1 @@ + -- 2.40.1 From 57aa7c256ab6bb9325863a01ec6195d6911de42a Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Thu, 27 Jun 2024 21:29:39 +0200 Subject: [PATCH 08/66] refactor(printing)!: move core::printing to cli #84 --- members/libpt-cli/src/lib.rs | 1 + members/{libpt-core => libpt-cli}/src/printing.rs | 0 members/libpt-core/src/lib.rs | 2 -- 3 files changed, 1 insertion(+), 2 deletions(-) rename members/{libpt-core => libpt-cli}/src/printing.rs (100%) diff --git a/members/libpt-cli/src/lib.rs b/members/libpt-cli/src/lib.rs index 8d79d47..76943d3 100644 --- a/members/libpt-cli/src/lib.rs +++ b/members/libpt-cli/src/lib.rs @@ -1,4 +1,5 @@ pub mod repl; +pub mod printing; pub use indicatif; pub use console; diff --git a/members/libpt-core/src/printing.rs b/members/libpt-cli/src/printing.rs similarity index 100% rename from members/libpt-core/src/printing.rs rename to members/libpt-cli/src/printing.rs diff --git a/members/libpt-core/src/lib.rs b/members/libpt-core/src/lib.rs index 3245b8c..848cd5c 100644 --- a/members/libpt-core/src/lib.rs +++ b/members/libpt-core/src/lib.rs @@ -8,5 +8,3 @@ /// macros to make things faster in your code pub mod macros; -/// some general use printing to stdout tools -pub mod printing; -- 2.40.1 From 128be8d7180a1c3b745264ce22182df506af0377 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Thu, 27 Jun 2024 19:32:09 +0000 Subject: [PATCH 09/66] automatic cargo CI changes --- members/libpt-cli/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/members/libpt-cli/src/lib.rs b/members/libpt-cli/src/lib.rs index af185da..ac0634c 100644 --- a/members/libpt-cli/src/lib.rs +++ b/members/libpt-cli/src/lib.rs @@ -1,5 +1,5 @@ -pub mod repl; pub mod printing; +pub mod repl; pub use comfy_table; pub use console; -- 2.40.1 From 502298d47b526f841122d3b962a4daface863347 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Thu, 27 Jun 2024 22:19:53 +0200 Subject: [PATCH 10/66] feat(cli)!: remove printing::divider, add borderprint borderfmt and borderfmt_advanced divider was removed because it was too simple and code duplication makes therefore more sense for it's usecase. Refs: #84 --- members/libpt-cli/src/printing.rs | 108 ++++++++++++++++++++++++++++-- 1 file changed, 101 insertions(+), 7 deletions(-) diff --git a/members/libpt-cli/src/printing.rs b/members/libpt-cli/src/printing.rs index 808de0b..615b6e9 100644 --- a/members/libpt-cli/src/printing.rs +++ b/members/libpt-cli/src/printing.rs @@ -1,11 +1,105 @@ -//! # tools that make printing stuff better +use comfy_table::presets; +use comfy_table::{CellAlignment, ContentArrangement, Table}; +use console::{style, Color}; -/// Quickly get a one line visual divider -pub fn divider() -> String { - format!("{:=^80}", "=") +/// Prints content with a simple border around it. +/// +/// This function is a convenience wrapper around [blockfmt] and [println]. It automatically +/// formats the content with a border using the specified color and then prints it to the console. +/// +/// # Example +/// +/// ``` +/// use libpt_cli::console::Color; +/// use libpt_cli::printing::blockprint; +/// # fn main() { +/// blockprint("Hello world!".to_string(), Color::Blue); +/// # } +/// ``` +#[inline] +pub fn blockprint(content: impl ToString, color: Color) { + println!("{}", blockfmt(content, color)) } -/// Quickly print a one line visual divider -pub fn print_divider() { - println!("{:=^80}", "=") +/// Formats content with a simple border around it. +/// +/// This function is a convenience wrapper around [blockfmt_advanced] with preset values for +/// border style, content arrangement, and cell alignment. It automatically formats the content +/// with a border as large as possible and centers the content. The resulting cell is colored in +/// the specified color. +/// +/// # Example +/// +/// ``` +/// use libpt_cli::console::Color; +/// use libpt_cli::printing::blockfmt; +/// # fn main() { +/// let formatted_content = blockfmt("Hello world!".to_string(), Color::Blue); +/// println!("{}", formatted_content); +/// # } +/// ``` +#[inline] +pub fn blockfmt(content: impl ToString, color: Color) -> String { + blockfmt_advanced( + content, + color, + presets::UTF8_BORDERS_ONLY, + ContentArrangement::DynamicFullWidth, + CellAlignment::Center + ) +} + +/// Formats content with a border around it. +/// +/// Unless you are looking for something specific, use [blockfmt] or [blockprint]. +/// +/// The border can be created using box-drawing characters, and the content is formatted +/// within the border. The function allows customization of the border's color, preset, +/// content arrangement, and cell alignment. +/// +/// # Example +/// ``` +/// use libpt_cli::comfy_table::{presets, CellAlignment, ContentArrangement}; +/// use libpt_cli::console::Color; +/// use libpt_cli::printing::blockfmt_advanced; +/// # fn main() { +/// println!( +/// "{}", +/// blockfmt_advanced( +/// "Hello world!".to_string(), +/// Color::Blue, +/// presets::UTF8_FULL, +/// ContentArrangement::DynamicFullWidth, +/// CellAlignment::Center +/// ) +/// ); +/// # } +/// ``` +/// ```text +/// ┌────────────────────────────────────────────────────────────────────────────────────────┐ +/// │ Hello world! │ +/// └────────────────────────────────────────────────────────────────────────────────────────┘ +/// ``` +/// +/// # Parameters +/// +/// +pub fn blockfmt_advanced( + content: impl ToString, + color: Color, + preset: &str, + arrangement: ContentArrangement, + alignment: CellAlignment +) -> String { + let mut table = Table::new(); + table + .load_preset(preset) + .set_content_arrangement(arrangement) + .add_row(vec![content.to_string()]); + table + .column_mut(0) + .unwrap() + .set_cell_alignment(alignment); + + format!("{}", style(table).fg(color)) } -- 2.40.1 From 8070f0b100fca951da5e1f3650c69de4d37b4da6 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Thu, 27 Jun 2024 20:21:55 +0000 Subject: [PATCH 11/66] automatic cargo CI changes --- members/libpt-cli/src/printing.rs | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/members/libpt-cli/src/printing.rs b/members/libpt-cli/src/printing.rs index 615b6e9..563a59b 100644 --- a/members/libpt-cli/src/printing.rs +++ b/members/libpt-cli/src/printing.rs @@ -45,7 +45,7 @@ pub fn blockfmt(content: impl ToString, color: Color) -> String { color, presets::UTF8_BORDERS_ONLY, ContentArrangement::DynamicFullWidth, - CellAlignment::Center + CellAlignment::Center, ) } @@ -89,17 +89,14 @@ pub fn blockfmt_advanced( color: Color, preset: &str, arrangement: ContentArrangement, - alignment: CellAlignment + alignment: CellAlignment, ) -> String { let mut table = Table::new(); table .load_preset(preset) .set_content_arrangement(arrangement) .add_row(vec![content.to_string()]); - table - .column_mut(0) - .unwrap() - .set_cell_alignment(alignment); + table.column_mut(0).unwrap().set_cell_alignment(alignment); format!("{}", style(table).fg(color)) } -- 2.40.1 From 08bfc0362899f9854497e18b0b3d8386a009f646 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Thu, 27 Jun 2024 22:22:46 +0200 Subject: [PATCH 12/66] refactor(cli::printing): use Option for blockfmt_advanced Users might just need the cell String, not a colored one. --- members/libpt-cli/src/printing.rs | 20 ++++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/members/libpt-cli/src/printing.rs b/members/libpt-cli/src/printing.rs index 615b6e9..2298c33 100644 --- a/members/libpt-cli/src/printing.rs +++ b/members/libpt-cli/src/printing.rs @@ -42,10 +42,10 @@ pub fn blockprint(content: impl ToString, color: Color) { pub fn blockfmt(content: impl ToString, color: Color) -> String { blockfmt_advanced( content, - color, + Some(color), presets::UTF8_BORDERS_ONLY, ContentArrangement::DynamicFullWidth, - CellAlignment::Center + CellAlignment::Center, ) } @@ -67,7 +67,7 @@ pub fn blockfmt(content: impl ToString, color: Color) -> String { /// "{}", /// blockfmt_advanced( /// "Hello world!".to_string(), -/// Color::Blue, +/// Some(Color::Blue), /// presets::UTF8_FULL, /// ContentArrangement::DynamicFullWidth, /// CellAlignment::Center @@ -86,20 +86,20 @@ pub fn blockfmt(content: impl ToString, color: Color) -> String { /// pub fn blockfmt_advanced( content: impl ToString, - color: Color, + color: Option, preset: &str, arrangement: ContentArrangement, - alignment: CellAlignment + alignment: CellAlignment, ) -> String { let mut table = Table::new(); table .load_preset(preset) .set_content_arrangement(arrangement) .add_row(vec![content.to_string()]); - table - .column_mut(0) - .unwrap() - .set_cell_alignment(alignment); + table.column_mut(0).unwrap().set_cell_alignment(alignment); - format!("{}", style(table).fg(color)) + match color { + Some(c) => format!("{}", style(table).fg(c)), + None => table.to_string(), + } } -- 2.40.1 From 57709e963c324fc53733f18cd2cdbd87f1ae9184 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Thu, 27 Jun 2024 22:26:18 +0200 Subject: [PATCH 13/66] docs(cli::printing): document use of parameters for blockfmt_advanced --- members/libpt-cli/src/printing.rs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/members/libpt-cli/src/printing.rs b/members/libpt-cli/src/printing.rs index 2298c33..b2717e9 100644 --- a/members/libpt-cli/src/printing.rs +++ b/members/libpt-cli/src/printing.rs @@ -83,7 +83,11 @@ pub fn blockfmt(content: impl ToString, color: Color) -> String { /// /// # Parameters /// -/// +/// - `content`: The content to be formatted within the border +/// - `color`: The color of the border and text +/// - `preset`: The preset style for the border +/// - `arrangement`: The arrangement of the the border (e.g., stretch to sides, wrap around ) +/// - `alignment`: The alignment of the content within the cells (e.g., left, center, right) pub fn blockfmt_advanced( content: impl ToString, color: Option, -- 2.40.1 From 1c78801101f7b053d86b4af4c26c16c02b2f2ac8 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Thu, 27 Jun 2024 22:31:36 +0200 Subject: [PATCH 14/66] docs(cli::printing): add module documentation --- members/libpt-cli/src/printing.rs | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/members/libpt-cli/src/printing.rs b/members/libpt-cli/src/printing.rs index b2717e9..2f8766c 100644 --- a/members/libpt-cli/src/printing.rs +++ b/members/libpt-cli/src/printing.rs @@ -1,8 +1,14 @@ +//! Utilities for formatting, bordering, aligning and printing text content. +//! +//! This module provides functions for formatting content with borders and colors, printing them to the console. +//! The functions in this module are designed to simplify the process of creating visually appealing +//! output for CLI applications. + use comfy_table::presets; use comfy_table::{CellAlignment, ContentArrangement, Table}; use console::{style, Color}; -/// Prints content with a simple border around it. +/// Prints content with a simple border around it /// /// This function is a convenience wrapper around [blockfmt] and [println]. It automatically /// formats the content with a border using the specified color and then prints it to the console. @@ -21,7 +27,7 @@ pub fn blockprint(content: impl ToString, color: Color) { println!("{}", blockfmt(content, color)) } -/// Formats content with a simple border around it. +/// Formats content with a simple border around it /// /// This function is a convenience wrapper around [blockfmt_advanced] with preset values for /// border style, content arrangement, and cell alignment. It automatically formats the content @@ -49,7 +55,7 @@ pub fn blockfmt(content: impl ToString, color: Color) -> String { ) } -/// Formats content with a border around it. +/// Formats content with a border around it /// /// Unless you are looking for something specific, use [blockfmt] or [blockprint]. /// -- 2.40.1 From 3fd04a695d6f7b8c57ceb961663773c6adc814e7 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Thu, 27 Jun 2024 22:33:12 +0200 Subject: [PATCH 15/66] feat(cli): add cli crate export to libpt --- src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/src/lib.rs b/src/lib.rs index 02b30f7..2c85a70 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,9 +6,12 @@ //! `pt` is a project consisting of multiple smaller crates, all bundled together in this //! "main crate". Most crates will only show up if you activate their feature. #[cfg_attr(docsrs, doc(cfg(feature = "full")))] + #[cfg(feature = "bintols")] pub use libpt_bintols as bintols; #[cfg(feature = "core")] pub use libpt_core as core; #[cfg(feature = "log")] pub use libpt_log as log; +#[cfg(feature = "cli")] +pub use libpt_cli as cli; -- 2.40.1 From a92f5f308dc238990066e8cd422471c3651c23f6 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Thu, 27 Jun 2024 22:36:13 +0200 Subject: [PATCH 16/66] chore: add clap derive feature --- members/libpt-cli/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/members/libpt-cli/Cargo.toml b/members/libpt-cli/Cargo.toml index c8f9a60..55c020c 100644 --- a/members/libpt-cli/Cargo.toml +++ b/members/libpt-cli/Cargo.toml @@ -13,7 +13,7 @@ keywords.workspace = true categories.workspace = true [dependencies] -clap = "4.5.7" +clap = { version = "4.5.7", features = ["derive"] } comfy-table = "7.1.1" console = "0.15.8" dialoguer = "0.11.0" -- 2.40.1 From 6a179a6bd5a51694ce9fb2e7ff0c7b626d0d8de8 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Thu, 27 Jun 2024 20:38:15 +0000 Subject: [PATCH 17/66] automatic cargo CI changes --- src/lib.rs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/lib.rs b/src/lib.rs index 2c85a70..5b41b90 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -6,12 +6,11 @@ //! `pt` is a project consisting of multiple smaller crates, all bundled together in this //! "main crate". Most crates will only show up if you activate their feature. #[cfg_attr(docsrs, doc(cfg(feature = "full")))] - #[cfg(feature = "bintols")] pub use libpt_bintols as bintols; +#[cfg(feature = "cli")] +pub use libpt_cli as cli; #[cfg(feature = "core")] pub use libpt_core as core; #[cfg(feature = "log")] pub use libpt_log as log; -#[cfg(feature = "cli")] -pub use libpt_cli as cli; -- 2.40.1 From 798cb19865e372bc59c6da39349d8e12b58a3b4d Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Fri, 28 Jun 2024 00:05:54 +0200 Subject: [PATCH 18/66] feat(cli::args): verbosity flags for log level --- Cargo.toml | 2 +- members/libpt-cli/Cargo.toml | 6 ++ members/libpt-cli/src/args.rs | 135 ++++++++++++++++++++++++++++++++++ members/libpt-cli/src/lib.rs | 2 + 4 files changed, 144 insertions(+), 1 deletion(-) create mode 100644 members/libpt-cli/src/args.rs diff --git a/Cargo.toml b/Cargo.toml index 1536913..2db2588 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -54,7 +54,7 @@ core = [] full = ["default", "core", "log", "bintols"] log = ["dep:libpt-log"] bintols = ["dep:libpt-bintols", "log"] -cli = ["dep:libpt-cli", "core"] +cli = ["dep:libpt-cli", "core", "log"] # py = ["dep:libpt-py"] [lib] diff --git a/members/libpt-cli/Cargo.toml b/members/libpt-cli/Cargo.toml index 55c020c..66ef180 100644 --- a/members/libpt-cli/Cargo.toml +++ b/members/libpt-cli/Cargo.toml @@ -12,9 +12,15 @@ repository.workspace = true keywords.workspace = true categories.workspace = true +[features] +default = ["log"] +log = ["dep:libpt-log"] + [dependencies] +anyhow.workspace = true clap = { version = "4.5.7", features = ["derive"] } comfy-table = "7.1.1" console = "0.15.8" dialoguer = "0.11.0" indicatif = "0.17.8" +libpt-log = {workspace = true, optional = true} diff --git a/members/libpt-cli/src/args.rs b/members/libpt-cli/src/args.rs new file mode 100644 index 0000000..9eba7b2 --- /dev/null +++ b/members/libpt-cli/src/args.rs @@ -0,0 +1,135 @@ +use clap::Parser; +use libpt_log::Level; + +/// Custom help template for displaying command-line usage information +/// +/// This template modifies the default template provided by Clap to include additional information +/// and customize the layout of the help message. +/// +/// Differences from the default template: +/// - Includes the application version and author information at the end +/// +/// Apply like this: +/// ``` +/// # use libpt_cli::args::HELP_TEMPLATE; +/// use clap::Parser; +/// #[derive(Parser, Debug, Clone, PartialEq, Eq, Hash)] +/// #[command(help_template = HELP_TEMPLATE)] +/// pub struct MyArgs { +/// /// show more details +/// #[arg(short, long)] +/// pub verbose: bool, +/// } +/// ``` +/// +/// ## Example +/// +/// Don't forget to set `authors` in your `Cargo.toml`! +/// +/// ```bash +/// $ cargo run -- -h +/// about: short +/// +/// Usage: aaa [OPTIONS] +/// +/// Options: +/// -v, --verbose show more details +/// -h, --help Print help (see more with '--help') +/// -V, --version Print version +/// +/// aaa: 0.1.0 +/// Author: Christoph J. Scherr +/// +/// ``` +pub const HELP_TEMPLATE: &str = r#"{about-section} +{usage-heading} {usage} + +{all-args}{tab} + +{name}: {version} +Author: {author-with-newline} +"#; + +#[derive(Parser, Debug, Clone, PartialEq, Eq, Hash)] +#[command(help_template = HELP_TEMPLATE)] +pub struct DefaultArguments { + /// get a [tracing] log level + /// + /// set the verbosity with repeated '-q' and '-v' flags + #[command(flatten)] + verbose: VerbosityLevel, +} + +#[derive(Parser, Clone, PartialEq, Eq, Hash)] +pub struct VerbosityLevel { + /// make the output more verbose + #[arg( + long, + short = 'v', + action = clap::ArgAction::Count, + global = true, + // help = L::verbose_help(), + // long_help = L::verbose_long_help(), + )] + verbose: u8, + + /// make the output less verbose + /// + /// ( -qqq for completely quiet) + #[arg( + long, + short = 'q', + action = clap::ArgAction::Count, + global = true, + conflicts_with = "verbose", + )] + quiet: u8, +} + +impl VerbosityLevel { + /// true only if no verbose and no quiet was set (user is using defaults) + #[inline] + pub fn changed(&self) -> bool { + self.verbose != 0 || self.quiet != 0 + } + #[inline] + fn value(&self) -> i8 { + let v = Self::level_value(Level::INFO) - (self.quiet as i8) + (self.verbose as i8); + if v > Self::level_value(Level::TRACE) { + Self::level_value(Level::TRACE) + } else { + v + } + } + + /// get the [Level] for that VerbosityLevel + /// + /// [None] means that absolutely no output is wanted (completely quiet) + #[inline] + pub fn level(&self) -> Option { + Some(match self.value() { + 0 => Level::ERROR, + 1 => Level::WARN, + 2 => Level::INFO, + 3 => Level::DEBUG, + 4 => Level::TRACE, + _ => return None, + }) + } + #[inline] + fn level_value(level: Level) -> i8 { + match level { + Level::TRACE => 4, + Level::DEBUG => 3, + Level::INFO => 2, + Level::WARN => 1, + Level::ERROR => 0, + } + } +} + +impl std::fmt::Debug for VerbosityLevel { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!(f, "{:?}", self.level()) + } +} diff --git a/members/libpt-cli/src/lib.rs b/members/libpt-cli/src/lib.rs index ac0634c..f99ccb1 100644 --- a/members/libpt-cli/src/lib.rs +++ b/members/libpt-cli/src/lib.rs @@ -1,7 +1,9 @@ pub mod printing; pub mod repl; +pub mod args; pub use comfy_table; pub use console; pub use dialoguer; pub use indicatif; +pub use clap; -- 2.40.1 From 6074ba899d96a2210d9502e1aeac4ce394b37197 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Thu, 27 Jun 2024 22:08:16 +0000 Subject: [PATCH 19/66] automatic cargo CI changes --- members/libpt-cli/src/lib.rs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/members/libpt-cli/src/lib.rs b/members/libpt-cli/src/lib.rs index f99ccb1..1a886da 100644 --- a/members/libpt-cli/src/lib.rs +++ b/members/libpt-cli/src/lib.rs @@ -1,9 +1,9 @@ +pub mod args; pub mod printing; pub mod repl; -pub mod args; +pub use clap; pub use comfy_table; pub use console; pub use dialoguer; pub use indicatif; -pub use clap; -- 2.40.1 From 1e912d9be85d51cff02167046c0156a0a110155a Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Fri, 28 Jun 2024 00:17:13 +0200 Subject: [PATCH 20/66] refactor: reexport tracing from libpt-log --- members/libpt-log/src/lib.rs | 1 + 1 file changed, 1 insertion(+) diff --git a/members/libpt-log/src/lib.rs b/members/libpt-log/src/lib.rs index e794b8b..47493dc 100644 --- a/members/libpt-log/src/lib.rs +++ b/members/libpt-log/src/lib.rs @@ -23,6 +23,7 @@ use std::{ pub mod error; use error::*; +pub use tracing; pub use tracing::{debug, error, info, trace, warn, Level}; use tracing_appender::{self, non_blocking::NonBlocking}; use tracing_subscriber::fmt::{format::FmtSpan, time}; -- 2.40.1 From 2e26983fb6fb5f395bc91246f7120201fc9a1fd3 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Fri, 28 Jun 2024 00:17:36 +0200 Subject: [PATCH 21/66] chore: bump version of libpt-log to v0.4.3 --- members/libpt-log/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/members/libpt-log/Cargo.toml b/members/libpt-log/Cargo.toml index 1a73ddd..e1645ab 100644 --- a/members/libpt-log/Cargo.toml +++ b/members/libpt-log/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "libpt-log" publish.workspace = true -version = "0.4.2" +version = "0.4.3" edition.workspace = true authors.workspace = true license.workspace = true -- 2.40.1 From 90cf678ddec46a64e12593b292083f6c5c52f1b4 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Fri, 28 Jun 2024 00:40:52 +0200 Subject: [PATCH 22/66] feat(cli::args): VerbosityLevel VerbosityLevel lets you easily get a loglevel from repeated -v and -q flags. Documentation included. Refs: #84 --- members/libpt-cli/Cargo.toml | 5 +- members/libpt-cli/src/args.rs | 78 +++++++++++++++++++++++++++---- members/libpt-cli/src/printing.rs | 2 +- 3 files changed, 72 insertions(+), 13 deletions(-) diff --git a/members/libpt-cli/Cargo.toml b/members/libpt-cli/Cargo.toml index 66ef180..8ed9716 100644 --- a/members/libpt-cli/Cargo.toml +++ b/members/libpt-cli/Cargo.toml @@ -14,7 +14,7 @@ categories.workspace = true [features] default = ["log"] -log = ["dep:libpt-log"] +log = ["dep:libpt-log", "dep:log"] [dependencies] anyhow.workspace = true @@ -23,4 +23,5 @@ comfy-table = "7.1.1" console = "0.15.8" dialoguer = "0.11.0" indicatif = "0.17.8" -libpt-log = {workspace = true, optional = true} +libpt-log = { workspace = true, optional = true } +log = { version = "0.4.21", optional = true } diff --git a/members/libpt-cli/src/args.rs b/members/libpt-cli/src/args.rs index 9eba7b2..7ebdacd 100644 --- a/members/libpt-cli/src/args.rs +++ b/members/libpt-cli/src/args.rs @@ -1,5 +1,10 @@ +//! Utilities for parsing options and arguments on the start of a CLI application + use clap::Parser; +#[cfg(feature = "log")] use libpt_log::Level; +#[cfg(feature = "log")] +use log; /// Custom help template for displaying command-line usage information /// @@ -50,17 +55,52 @@ pub const HELP_TEMPLATE: &str = r#"{about-section} Author: {author-with-newline} "#; -#[derive(Parser, Debug, Clone, PartialEq, Eq, Hash)] -#[command(help_template = HELP_TEMPLATE)] -pub struct DefaultArguments { - /// get a [tracing] log level - /// - /// set the verbosity with repeated '-q' and '-v' flags - #[command(flatten)] - verbose: VerbosityLevel, -} - +/// Transform -v and -q flags to some kind of loglevel +/// +/// # Example +/// +/// Include this into your [clap] derive struct like this: +/// +/// ``` +/// use libpt_cli::args::VerbosityLevel; +/// use clap::Parser; +/// +/// #[derive(Parser, Debug)] +/// pub struct Opts { +/// #[command(flatten)] +/// pub verbose: VerbosityLevel, +/// #[arg(short, long)] +/// pub mynum: usize, +/// } +/// +/// ``` +/// +/// Get the loglevel like this: +/// +/// ```no_run +/// # use libpt_cli::args::VerbosityLevel; +/// use libpt_log::Level; +/// # use clap::Parser; +/// use log; +/// +/// # #[derive(Parser, Debug)] +/// # pub struct Opts { +/// # #[command(flatten)] +/// # pub verbose: VerbosityLevel, +/// # } +/// +/// fn main() { +/// let opts = Opts::parse(); +/// +/// // Level might be None if the user wants no output at all. +/// // for the 'tracing' level: +/// let level: Option = opts.verbose.level(); +/// // for the 'log' level: +/// let llevel: Option = opts.verbose.level_for_log_crate(); +/// } +/// ``` #[derive(Parser, Clone, PartialEq, Eq, Hash)] +#[cfg(feature = "log")] pub struct VerbosityLevel { /// make the output more verbose #[arg( @@ -116,6 +156,23 @@ impl VerbosityLevel { _ => return None, }) } + + /// get the [log::Level] for that VerbosityLevel + /// + /// This is the method for the [log] crate, which I use less often. + /// + /// [None] means that absolutely no output is wanted (completely quiet) + #[inline] + pub fn level_for_log_crate(&self) -> Option { + self.level().map(|ll| match ll { + Level::TRACE => log::Level::Trace, + Level::DEBUG => log::Level::Debug, + Level::INFO => log::Level::Info, + Level::WARN => log::Level::Warn, + Level::ERROR => log::Level::Error, + }) + } + #[inline] fn level_value(level: Level) -> i8 { match level { @@ -129,6 +186,7 @@ impl VerbosityLevel { } impl std::fmt::Debug for VerbosityLevel { + #[inline] fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!(f, "{:?}", self.level()) } diff --git a/members/libpt-cli/src/printing.rs b/members/libpt-cli/src/printing.rs index 2f8766c..bcb1bd4 100644 --- a/members/libpt-cli/src/printing.rs +++ b/members/libpt-cli/src/printing.rs @@ -1,4 +1,4 @@ -//! Utilities for formatting, bordering, aligning and printing text content. +//! Utilities for formatting, bordering, aligning and printing text content //! //! This module provides functions for formatting content with borders and colors, printing them to the console. //! The functions in this module are designed to simplify the process of creating visually appealing -- 2.40.1 From 0b3610e7c5d626d49e9451375d6ab7a4e5e8c8c9 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Fri, 28 Jun 2024 08:56:21 +0200 Subject: [PATCH 23/66] chore: change LICENSE to GPL-3.0-or-later --- Cargo.toml | 2 +- LICENSE | 688 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 672 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 2db2588..c0277fa 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -14,7 +14,7 @@ publish = true version = "0.6.0-alpha.0" edition = "2021" authors = ["Christoph J. Scherr "] -license = "MIT" +license = "GPL-3.0-or-later" description = "Personal multitool" readme = "README.md" homepage = "https://git.cscherr.de/PlexSheep/pt" diff --git a/LICENSE b/LICENSE index 2ed085e..53d1f3d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,675 @@ -MIT License + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 -Copyright (c) 2024 Christoph Johannes Scherr + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + Preamble -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -- 2.40.1 From c9e5a3f43d190e05a4cd7e17a17c289a87eeafeb Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Fri, 28 Jun 2024 08:56:21 +0200 Subject: [PATCH 24/66] chore: change LICENSE to GPL-3.0-or-later --- Cargo.toml | 2 +- LICENSE | 688 +++++++++++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 672 insertions(+), 18 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 354ca92..e3d08f1 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -13,7 +13,7 @@ publish = true version = "0.5.1" edition = "2021" authors = ["Christoph J. Scherr "] -license = "MIT" +license = "GPL-3.0-or-later" description = "Personal multitool" readme = "README.md" homepage = "https://git.cscherr.de/PlexSheep/pt" diff --git a/LICENSE b/LICENSE index 2ed085e..53d1f3d 100644 --- a/LICENSE +++ b/LICENSE @@ -1,21 +1,675 @@ -MIT License + GNU GENERAL PUBLIC LICENSE + Version 3, 29 June 2007 -Copyright (c) 2024 Christoph Johannes Scherr + Copyright (C) 2007 Free Software Foundation, Inc. + Everyone is permitted to copy and distribute verbatim copies + of this license document, but changing it is not allowed. -Permission is hereby granted, free of charge, to any person obtaining a copy -of this software and associated documentation files (the "Software"), to deal -in the Software without restriction, including without limitation the rights -to use, copy, modify, merge, publish, distribute, sublicense, and/or sell -copies of the Software, and to permit persons to whom the Software is -furnished to do so, subject to the following conditions: + Preamble -The above copyright notice and this permission notice shall be included in all -copies or substantial portions of the Software. + The GNU General Public License is a free, copyleft license for +software and other kinds of works. + + The licenses for most software and other practical works are designed +to take away your freedom to share and change the works. By contrast, +the GNU General Public License is intended to guarantee your freedom to +share and change all versions of a program--to make sure it remains free +software for all its users. We, the Free Software Foundation, use the +GNU General Public License for most of our software; it applies also to +any other work released this way by its authors. You can apply it to +your programs, too. + + When we speak of free software, we are referring to freedom, not +price. Our General Public Licenses are designed to make sure that you +have the freedom to distribute copies of free software (and charge for +them if you wish), that you receive source code or can get it if you +want it, that you can change the software or use pieces of it in new +free programs, and that you know you can do these things. + + To protect your rights, we need to prevent others from denying you +these rights or asking you to surrender the rights. Therefore, you have +certain responsibilities if you distribute copies of the software, or if +you modify it: responsibilities to respect the freedom of others. + + For example, if you distribute copies of such a program, whether +gratis or for a fee, you must pass on to the recipients the same +freedoms that you received. You must make sure that they, too, receive +or can get the source code. And you must show them these terms so they +know their rights. + + Developers that use the GNU GPL protect your rights with two steps: +(1) assert copyright on the software, and (2) offer you this License +giving you legal permission to copy, distribute and/or modify it. + + For the developers' and authors' protection, the GPL clearly explains +that there is no warranty for this free software. For both users' and +authors' sake, the GPL requires that modified versions be marked as +changed, so that their problems will not be attributed erroneously to +authors of previous versions. + + Some devices are designed to deny users access to install or run +modified versions of the software inside them, although the manufacturer +can do so. This is fundamentally incompatible with the aim of +protecting users' freedom to change the software. The systematic +pattern of such abuse occurs in the area of products for individuals to +use, which is precisely where it is most unacceptable. Therefore, we +have designed this version of the GPL to prohibit the practice for those +products. If such problems arise substantially in other domains, we +stand ready to extend this provision to those domains in future versions +of the GPL, as needed to protect the freedom of users. + + Finally, every program is threatened constantly by software patents. +States should not allow patents to restrict development and use of +software on general-purpose computers, but in those that do, we wish to +avoid the special danger that patents applied to a free program could +make it effectively proprietary. To prevent this, the GPL assures that +patents cannot be used to render the program non-free. + + The precise terms and conditions for copying, distribution and +modification follow. + + TERMS AND CONDITIONS + + 0. Definitions. + + "This License" refers to version 3 of the GNU General Public License. + + "Copyright" also means copyright-like laws that apply to other kinds of +works, such as semiconductor masks. + + "The Program" refers to any copyrightable work licensed under this +License. Each licensee is addressed as "you". "Licensees" and +"recipients" may be individuals or organizations. + + To "modify" a work means to copy from or adapt all or part of the work +in a fashion requiring copyright permission, other than the making of an +exact copy. The resulting work is called a "modified version" of the +earlier work or a work "based on" the earlier work. + + A "covered work" means either the unmodified Program or a work based +on the Program. + + To "propagate" a work means to do anything with it that, without +permission, would make you directly or secondarily liable for +infringement under applicable copyright law, except executing it on a +computer or modifying a private copy. Propagation includes copying, +distribution (with or without modification), making available to the +public, and in some countries other activities as well. + + To "convey" a work means any kind of propagation that enables other +parties to make or receive copies. Mere interaction with a user through +a computer network, with no transfer of a copy, is not conveying. + + An interactive user interface displays "Appropriate Legal Notices" +to the extent that it includes a convenient and prominently visible +feature that (1) displays an appropriate copyright notice, and (2) +tells the user that there is no warranty for the work (except to the +extent that warranties are provided), that licensees may convey the +work under this License, and how to view a copy of this License. If +the interface presents a list of user commands or options, such as a +menu, a prominent item in the list meets this criterion. + + 1. Source Code. + + The "source code" for a work means the preferred form of the work +for making modifications to it. "Object code" means any non-source +form of a work. + + A "Standard Interface" means an interface that either is an official +standard defined by a recognized standards body, or, in the case of +interfaces specified for a particular programming language, one that +is widely used among developers working in that language. + + The "System Libraries" of an executable work include anything, other +than the work as a whole, that (a) is included in the normal form of +packaging a Major Component, but which is not part of that Major +Component, and (b) serves only to enable use of the work with that +Major Component, or to implement a Standard Interface for which an +implementation is available to the public in source code form. A +"Major Component", in this context, means a major essential component +(kernel, window system, and so on) of the specific operating system +(if any) on which the executable work runs, or a compiler used to +produce the work, or an object code interpreter used to run it. + + The "Corresponding Source" for a work in object code form means all +the source code needed to generate, install, and (for an executable +work) run the object code and to modify the work, including scripts to +control those activities. However, it does not include the work's +System Libraries, or general-purpose tools or generally available free +programs which are used unmodified in performing those activities but +which are not part of the work. For example, Corresponding Source +includes interface definition files associated with source files for +the work, and the source code for shared libraries and dynamically +linked subprograms that the work is specifically designed to require, +such as by intimate data communication or control flow between those +subprograms and other parts of the work. + + The Corresponding Source need not include anything that users +can regenerate automatically from other parts of the Corresponding +Source. + + The Corresponding Source for a work in source code form is that +same work. + + 2. Basic Permissions. + + All rights granted under this License are granted for the term of +copyright on the Program, and are irrevocable provided the stated +conditions are met. This License explicitly affirms your unlimited +permission to run the unmodified Program. The output from running a +covered work is covered by this License only if the output, given its +content, constitutes a covered work. This License acknowledges your +rights of fair use or other equivalent, as provided by copyright law. + + You may make, run and propagate covered works that you do not +convey, without conditions so long as your license otherwise remains +in force. You may convey covered works to others for the sole purpose +of having them make modifications exclusively for you, or provide you +with facilities for running those works, provided that you comply with +the terms of this License in conveying all material for which you do +not control copyright. Those thus making or running the covered works +for you must do so exclusively on your behalf, under your direction +and control, on terms that prohibit them from making any copies of +your copyrighted material outside their relationship with you. + + Conveying under any other circumstances is permitted solely under +the conditions stated below. Sublicensing is not allowed; section 10 +makes it unnecessary. + + 3. Protecting Users' Legal Rights From Anti-Circumvention Law. + + No covered work shall be deemed part of an effective technological +measure under any applicable law fulfilling obligations under article +11 of the WIPO copyright treaty adopted on 20 December 1996, or +similar laws prohibiting or restricting circumvention of such +measures. + + When you convey a covered work, you waive any legal power to forbid +circumvention of technological measures to the extent such circumvention +is effected by exercising rights under this License with respect to +the covered work, and you disclaim any intention to limit operation or +modification of the work as a means of enforcing, against the work's +users, your or third parties' legal rights to forbid circumvention of +technological measures. + + 4. Conveying Verbatim Copies. + + You may convey verbatim copies of the Program's source code as you +receive it, in any medium, provided that you conspicuously and +appropriately publish on each copy an appropriate copyright notice; +keep intact all notices stating that this License and any +non-permissive terms added in accord with section 7 apply to the code; +keep intact all notices of the absence of any warranty; and give all +recipients a copy of this License along with the Program. + + You may charge any price or no price for each copy that you convey, +and you may offer support or warranty protection for a fee. + + 5. Conveying Modified Source Versions. + + You may convey a work based on the Program, or the modifications to +produce it from the Program, in the form of source code under the +terms of section 4, provided that you also meet all of these conditions: + + a) The work must carry prominent notices stating that you modified + it, and giving a relevant date. + + b) The work must carry prominent notices stating that it is + released under this License and any conditions added under section + 7. This requirement modifies the requirement in section 4 to + "keep intact all notices". + + c) You must license the entire work, as a whole, under this + License to anyone who comes into possession of a copy. This + License will therefore apply, along with any applicable section 7 + additional terms, to the whole of the work, and all its parts, + regardless of how they are packaged. This License gives no + permission to license the work in any other way, but it does not + invalidate such permission if you have separately received it. + + d) If the work has interactive user interfaces, each must display + Appropriate Legal Notices; however, if the Program has interactive + interfaces that do not display Appropriate Legal Notices, your + work need not make them do so. + + A compilation of a covered work with other separate and independent +works, which are not by their nature extensions of the covered work, +and which are not combined with it such as to form a larger program, +in or on a volume of a storage or distribution medium, is called an +"aggregate" if the compilation and its resulting copyright are not +used to limit the access or legal rights of the compilation's users +beyond what the individual works permit. Inclusion of a covered work +in an aggregate does not cause this License to apply to the other +parts of the aggregate. + + 6. Conveying Non-Source Forms. + + You may convey a covered work in object code form under the terms +of sections 4 and 5, provided that you also convey the +machine-readable Corresponding Source under the terms of this License, +in one of these ways: + + a) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by the + Corresponding Source fixed on a durable physical medium + customarily used for software interchange. + + b) Convey the object code in, or embodied in, a physical product + (including a physical distribution medium), accompanied by a + written offer, valid for at least three years and valid for as + long as you offer spare parts or customer support for that product + model, to give anyone who possesses the object code either (1) a + copy of the Corresponding Source for all the software in the + product that is covered by this License, on a durable physical + medium customarily used for software interchange, for a price no + more than your reasonable cost of physically performing this + conveying of source, or (2) access to copy the + Corresponding Source from a network server at no charge. + + c) Convey individual copies of the object code with a copy of the + written offer to provide the Corresponding Source. This + alternative is allowed only occasionally and noncommercially, and + only if you received the object code with such an offer, in accord + with subsection 6b. + + d) Convey the object code by offering access from a designated + place (gratis or for a charge), and offer equivalent access to the + Corresponding Source in the same way through the same place at no + further charge. You need not require recipients to copy the + Corresponding Source along with the object code. If the place to + copy the object code is a network server, the Corresponding Source + may be on a different server (operated by you or a third party) + that supports equivalent copying facilities, provided you maintain + clear directions next to the object code saying where to find the + Corresponding Source. Regardless of what server hosts the + Corresponding Source, you remain obligated to ensure that it is + available for as long as needed to satisfy these requirements. + + e) Convey the object code using peer-to-peer transmission, provided + you inform other peers where the object code and Corresponding + Source of the work are being offered to the general public at no + charge under subsection 6d. + + A separable portion of the object code, whose source code is excluded +from the Corresponding Source as a System Library, need not be +included in conveying the object code work. + + A "User Product" is either (1) a "consumer product", which means any +tangible personal property which is normally used for personal, family, +or household purposes, or (2) anything designed or sold for incorporation +into a dwelling. In determining whether a product is a consumer product, +doubtful cases shall be resolved in favor of coverage. For a particular +product received by a particular user, "normally used" refers to a +typical or common use of that class of product, regardless of the status +of the particular user or of the way in which the particular user +actually uses, or expects or is expected to use, the product. A product +is a consumer product regardless of whether the product has substantial +commercial, industrial or non-consumer uses, unless such uses represent +the only significant mode of use of the product. + + "Installation Information" for a User Product means any methods, +procedures, authorization keys, or other information required to install +and execute modified versions of a covered work in that User Product from +a modified version of its Corresponding Source. The information must +suffice to ensure that the continued functioning of the modified object +code is in no case prevented or interfered with solely because +modification has been made. + + If you convey an object code work under this section in, or with, or +specifically for use in, a User Product, and the conveying occurs as +part of a transaction in which the right of possession and use of the +User Product is transferred to the recipient in perpetuity or for a +fixed term (regardless of how the transaction is characterized), the +Corresponding Source conveyed under this section must be accompanied +by the Installation Information. But this requirement does not apply +if neither you nor any third party retains the ability to install +modified object code on the User Product (for example, the work has +been installed in ROM). + + The requirement to provide Installation Information does not include a +requirement to continue to provide support service, warranty, or updates +for a work that has been modified or installed by the recipient, or for +the User Product in which it has been modified or installed. Access to a +network may be denied when the modification itself materially and +adversely affects the operation of the network or violates the rules and +protocols for communication across the network. + + Corresponding Source conveyed, and Installation Information provided, +in accord with this section must be in a format that is publicly +documented (and with an implementation available to the public in +source code form), and must require no special password or key for +unpacking, reading or copying. + + 7. Additional Terms. + + "Additional permissions" are terms that supplement the terms of this +License by making exceptions from one or more of its conditions. +Additional permissions that are applicable to the entire Program shall +be treated as though they were included in this License, to the extent +that they are valid under applicable law. If additional permissions +apply only to part of the Program, that part may be used separately +under those permissions, but the entire Program remains governed by +this License without regard to the additional permissions. + + When you convey a copy of a covered work, you may at your option +remove any additional permissions from that copy, or from any part of +it. (Additional permissions may be written to require their own +removal in certain cases when you modify the work.) You may place +additional permissions on material, added by you to a covered work, +for which you have or can give appropriate copyright permission. + + Notwithstanding any other provision of this License, for material you +add to a covered work, you may (if authorized by the copyright holders of +that material) supplement the terms of this License with terms: + + a) Disclaiming warranty or limiting liability differently from the + terms of sections 15 and 16 of this License; or + + b) Requiring preservation of specified reasonable legal notices or + author attributions in that material or in the Appropriate Legal + Notices displayed by works containing it; or + + c) Prohibiting misrepresentation of the origin of that material, or + requiring that modified versions of such material be marked in + reasonable ways as different from the original version; or + + d) Limiting the use for publicity purposes of names of licensors or + authors of the material; or + + e) Declining to grant rights under trademark law for use of some + trade names, trademarks, or service marks; or + + f) Requiring indemnification of licensors and authors of that + material by anyone who conveys the material (or modified versions of + it) with contractual assumptions of liability to the recipient, for + any liability that these contractual assumptions directly impose on + those licensors and authors. + + All other non-permissive additional terms are considered "further +restrictions" within the meaning of section 10. If the Program as you +received it, or any part of it, contains a notice stating that it is +governed by this License along with a term that is a further +restriction, you may remove that term. If a license document contains +a further restriction but permits relicensing or conveying under this +License, you may add to a covered work material governed by the terms +of that license document, provided that the further restriction does +not survive such relicensing or conveying. + + If you add terms to a covered work in accord with this section, you +must place, in the relevant source files, a statement of the +additional terms that apply to those files, or a notice indicating +where to find the applicable terms. + + Additional terms, permissive or non-permissive, may be stated in the +form of a separately written license, or stated as exceptions; +the above requirements apply either way. + + 8. Termination. + + You may not propagate or modify a covered work except as expressly +provided under this License. Any attempt otherwise to propagate or +modify it is void, and will automatically terminate your rights under +this License (including any patent licenses granted under the third +paragraph of section 11). + + However, if you cease all violation of this License, then your +license from a particular copyright holder is reinstated (a) +provisionally, unless and until the copyright holder explicitly and +finally terminates your license, and (b) permanently, if the copyright +holder fails to notify you of the violation by some reasonable means +prior to 60 days after the cessation. + + Moreover, your license from a particular copyright holder is +reinstated permanently if the copyright holder notifies you of the +violation by some reasonable means, this is the first time you have +received notice of violation of this License (for any work) from that +copyright holder, and you cure the violation prior to 30 days after +your receipt of the notice. + + Termination of your rights under this section does not terminate the +licenses of parties who have received copies or rights from you under +this License. If your rights have been terminated and not permanently +reinstated, you do not qualify to receive new licenses for the same +material under section 10. + + 9. Acceptance Not Required for Having Copies. + + You are not required to accept this License in order to receive or +run a copy of the Program. Ancillary propagation of a covered work +occurring solely as a consequence of using peer-to-peer transmission +to receive a copy likewise does not require acceptance. However, +nothing other than this License grants you permission to propagate or +modify any covered work. These actions infringe copyright if you do +not accept this License. Therefore, by modifying or propagating a +covered work, you indicate your acceptance of this License to do so. + + 10. Automatic Licensing of Downstream Recipients. + + Each time you convey a covered work, the recipient automatically +receives a license from the original licensors, to run, modify and +propagate that work, subject to this License. You are not responsible +for enforcing compliance by third parties with this License. + + An "entity transaction" is a transaction transferring control of an +organization, or substantially all assets of one, or subdividing an +organization, or merging organizations. If propagation of a covered +work results from an entity transaction, each party to that +transaction who receives a copy of the work also receives whatever +licenses to the work the party's predecessor in interest had or could +give under the previous paragraph, plus a right to possession of the +Corresponding Source of the work from the predecessor in interest, if +the predecessor has it or can get it with reasonable efforts. + + You may not impose any further restrictions on the exercise of the +rights granted or affirmed under this License. For example, you may +not impose a license fee, royalty, or other charge for exercise of +rights granted under this License, and you may not initiate litigation +(including a cross-claim or counterclaim in a lawsuit) alleging that +any patent claim is infringed by making, using, selling, offering for +sale, or importing the Program or any portion of it. + + 11. Patents. + + A "contributor" is a copyright holder who authorizes use under this +License of the Program or a work on which the Program is based. The +work thus licensed is called the contributor's "contributor version". + + A contributor's "essential patent claims" are all patent claims +owned or controlled by the contributor, whether already acquired or +hereafter acquired, that would be infringed by some manner, permitted +by this License, of making, using, or selling its contributor version, +but do not include claims that would be infringed only as a +consequence of further modification of the contributor version. For +purposes of this definition, "control" includes the right to grant +patent sublicenses in a manner consistent with the requirements of +this License. + + Each contributor grants you a non-exclusive, worldwide, royalty-free +patent license under the contributor's essential patent claims, to +make, use, sell, offer for sale, import and otherwise run, modify and +propagate the contents of its contributor version. + + In the following three paragraphs, a "patent license" is any express +agreement or commitment, however denominated, not to enforce a patent +(such as an express permission to practice a patent or covenant not to +sue for patent infringement). To "grant" such a patent license to a +party means to make such an agreement or commitment not to enforce a +patent against the party. + + If you convey a covered work, knowingly relying on a patent license, +and the Corresponding Source of the work is not available for anyone +to copy, free of charge and under the terms of this License, through a +publicly available network server or other readily accessible means, +then you must either (1) cause the Corresponding Source to be so +available, or (2) arrange to deprive yourself of the benefit of the +patent license for this particular work, or (3) arrange, in a manner +consistent with the requirements of this License, to extend the patent +license to downstream recipients. "Knowingly relying" means you have +actual knowledge that, but for the patent license, your conveying the +covered work in a country, or your recipient's use of the covered work +in a country, would infringe one or more identifiable patents in that +country that you have reason to believe are valid. + + If, pursuant to or in connection with a single transaction or +arrangement, you convey, or propagate by procuring conveyance of, a +covered work, and grant a patent license to some of the parties +receiving the covered work authorizing them to use, propagate, modify +or convey a specific copy of the covered work, then the patent license +you grant is automatically extended to all recipients of the covered +work and works based on it. + + A patent license is "discriminatory" if it does not include within +the scope of its coverage, prohibits the exercise of, or is +conditioned on the non-exercise of one or more of the rights that are +specifically granted under this License. You may not convey a covered +work if you are a party to an arrangement with a third party that is +in the business of distributing software, under which you make payment +to the third party based on the extent of your activity of conveying +the work, and under which the third party grants, to any of the +parties who would receive the covered work from you, a discriminatory +patent license (a) in connection with copies of the covered work +conveyed by you (or copies made from those copies), or (b) primarily +for and in connection with specific products or compilations that +contain the covered work, unless you entered into that arrangement, +or that patent license was granted, prior to 28 March 2007. + + Nothing in this License shall be construed as excluding or limiting +any implied license or other defenses to infringement that may +otherwise be available to you under applicable patent law. + + 12. No Surrender of Others' Freedom. + + If conditions are imposed on you (whether by court order, agreement or +otherwise) that contradict the conditions of this License, they do not +excuse you from the conditions of this License. If you cannot convey a +covered work so as to satisfy simultaneously your obligations under this +License and any other pertinent obligations, then as a consequence you may +not convey it at all. For example, if you agree to terms that obligate you +to collect a royalty for further conveying from those to whom you convey +the Program, the only way you could satisfy both those terms and this +License would be to refrain entirely from conveying the Program. + + 13. Use with the GNU Affero General Public License. + + Notwithstanding any other provision of this License, you have +permission to link or combine any covered work with a work licensed +under version 3 of the GNU Affero General Public License into a single +combined work, and to convey the resulting work. The terms of this +License will continue to apply to the part which is the covered work, +but the special requirements of the GNU Affero General Public License, +section 13, concerning interaction through a network will apply to the +combination as such. + + 14. Revised Versions of this License. + + The Free Software Foundation may publish revised and/or new versions of +the GNU General Public License from time to time. Such new versions will +be similar in spirit to the present version, but may differ in detail to +address new problems or concerns. + + Each version is given a distinguishing version number. If the +Program specifies that a certain numbered version of the GNU General +Public License "or any later version" applies to it, you have the +option of following the terms and conditions either of that numbered +version or of any later version published by the Free Software +Foundation. If the Program does not specify a version number of the +GNU General Public License, you may choose any version ever published +by the Free Software Foundation. + + If the Program specifies that a proxy can decide which future +versions of the GNU General Public License can be used, that proxy's +public statement of acceptance of a version permanently authorizes you +to choose that version for the Program. + + Later license versions may give you additional or different +permissions. However, no additional obligations are imposed on any +author or copyright holder as a result of your choosing to follow a +later version. + + 15. Disclaimer of Warranty. + + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + + 16. Limitation of Liability. + + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. + + 17. Interpretation of Sections 15 and 16. + + If the disclaimer of warranty and limitation of liability provided +above cannot be given local legal effect according to their terms, +reviewing courts shall apply local law that most closely approximates +an absolute waiver of all civil liability in connection with the +Program, unless a warranty or assumption of liability accompanies a +copy of the Program in return for a fee. + + END OF TERMS AND CONDITIONS + + How to Apply These Terms to Your New Programs + + If you develop a new program, and you want it to be of the greatest +possible use to the public, the best way to achieve this is to make it +free software which everyone can redistribute and change under these terms. + + To do so, attach the following notices to the program. It is safest +to attach them to the start of each source file to most effectively +state the exclusion of warranty; and each file should have at least +the "copyright" line and a pointer to where the full notice is found. + + + Copyright (C) + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 3 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . + +Also add information on how to contact you by electronic and paper mail. + + If the program does terminal interaction, make it output a short +notice like this when it starts in an interactive mode: + + Copyright (C) + This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. + This is free software, and you are welcome to redistribute it + under certain conditions; type `show c' for details. + +The hypothetical commands `show w' and `show c' should show the appropriate +parts of the General Public License. Of course, your program's commands +might be different; for a GUI interface, you would use an "about box". + + You should also get your employer (if you work as a programmer) or school, +if any, to sign a "copyright disclaimer" for the program, if necessary. +For more information on this, and how to apply and follow the GNU GPL, see +. + + The GNU General Public License does not permit incorporating your program +into proprietary programs. If your program is a subroutine library, you +may consider it more useful to permit linking proprietary applications with +the library. If this is what you want to do, use the GNU Lesser General +Public License instead of this License. But first, please read +. -THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE -AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, -OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE -SOFTWARE. -- 2.40.1 From a79513d1db554b21bbb33f9b001dd2cd608e533a Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Fri, 28 Jun 2024 14:36:18 +0200 Subject: [PATCH 25/66] feat(cli): reexport human-panic and exitcode --- members/libpt-cli/Cargo.toml | 2 ++ members/libpt-cli/src/lib.rs | 2 ++ 2 files changed, 4 insertions(+) diff --git a/members/libpt-cli/Cargo.toml b/members/libpt-cli/Cargo.toml index 8ed9716..7426ae7 100644 --- a/members/libpt-cli/Cargo.toml +++ b/members/libpt-cli/Cargo.toml @@ -22,6 +22,8 @@ clap = { version = "4.5.7", features = ["derive"] } comfy-table = "7.1.1" console = "0.15.8" dialoguer = "0.11.0" +exitcode = "1.1.2" +human-panic = "2.0.0" indicatif = "0.17.8" libpt-log = { workspace = true, optional = true } log = { version = "0.4.21", optional = true } diff --git a/members/libpt-cli/src/lib.rs b/members/libpt-cli/src/lib.rs index 1a886da..1740407 100644 --- a/members/libpt-cli/src/lib.rs +++ b/members/libpt-cli/src/lib.rs @@ -7,3 +7,5 @@ pub use comfy_table; pub use console; pub use dialoguer; pub use indicatif; +pub use exitcode; +pub use human_panic; -- 2.40.1 From 880f2a7b897027c08d579493060ed7336cb6ce87 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Fri, 28 Jun 2024 12:38:39 +0000 Subject: [PATCH 26/66] automatic cargo CI changes --- members/libpt-cli/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/members/libpt-cli/src/lib.rs b/members/libpt-cli/src/lib.rs index 1740407..60c20ce 100644 --- a/members/libpt-cli/src/lib.rs +++ b/members/libpt-cli/src/lib.rs @@ -6,6 +6,6 @@ pub use clap; pub use comfy_table; pub use console; pub use dialoguer; -pub use indicatif; pub use exitcode; pub use human_panic; +pub use indicatif; -- 2.40.1 From 4278f4b5a7c5195f677d53cb60ec67a89f760868 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Fri, 28 Jun 2024 14:40:50 +0200 Subject: [PATCH 27/66] docs(cli): mention IsTerminal in cli::printing --- members/libpt-cli/src/lib.rs | 1 + members/libpt-cli/src/printing.rs | 4 ++++ 2 files changed, 5 insertions(+) diff --git a/members/libpt-cli/src/lib.rs b/members/libpt-cli/src/lib.rs index 1740407..ce9ade6 100644 --- a/members/libpt-cli/src/lib.rs +++ b/members/libpt-cli/src/lib.rs @@ -9,3 +9,4 @@ pub use dialoguer; pub use indicatif; pub use exitcode; pub use human_panic; + diff --git a/members/libpt-cli/src/printing.rs b/members/libpt-cli/src/printing.rs index bcb1bd4..aed4fc7 100644 --- a/members/libpt-cli/src/printing.rs +++ b/members/libpt-cli/src/printing.rs @@ -3,6 +3,10 @@ //! This module provides functions for formatting content with borders and colors, printing them to the console. //! The functions in this module are designed to simplify the process of creating visually appealing //! output for CLI applications. +//! +//! Note that most of the utilities in this module are focused on communication with humans, not +//! with machines. Consider evaluating [std::io::IsTerminal] before using colorful, dynamic and bordered +//! printing. use comfy_table::presets; use comfy_table::{CellAlignment, ContentArrangement, Table}; -- 2.40.1 From db2fd9f60ec0cdb97579aa17d8d7649d03e062cd Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Fri, 28 Jun 2024 14:44:04 +0200 Subject: [PATCH 28/66] docs: fix redundand link binding --- members/libpt-py/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/members/libpt-py/src/lib.rs b/members/libpt-py/src/lib.rs index 36463b0..2d648a1 100644 --- a/members/libpt-py/src/lib.rs +++ b/members/libpt-py/src/lib.rs @@ -1,4 +1,4 @@ -//! Python bindings for [`libpt`](libpt) +//! Python bindings for [`libpt`] #[cfg(feature = "bintols")] mod bintols; -- 2.40.1 From 3e5174dab6764614a52d2971d82298ead7c7b017 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Fri, 28 Jun 2024 14:50:13 +0200 Subject: [PATCH 29/66] docs(cli): expand on IsTerminal differences --- members/libpt-cli/src/printing.rs | 24 +++++++++++++++++++++++- 1 file changed, 23 insertions(+), 1 deletion(-) diff --git a/members/libpt-cli/src/printing.rs b/members/libpt-cli/src/printing.rs index aed4fc7..e9d0c35 100644 --- a/members/libpt-cli/src/printing.rs +++ b/members/libpt-cli/src/printing.rs @@ -6,7 +6,29 @@ //! //! Note that most of the utilities in this module are focused on communication with humans, not //! with machines. Consider evaluating [std::io::IsTerminal] before using colorful, dynamic and bordered -//! printing. +//! printing. If you are talking to a machine, it might be useful to not add extra space, add a +//! newline per output or even output JSON. An example that does this well is `ls`: +//! +//! ```bash +//! $ ls +//! Cargo.lock Cargo.toml data LICENSE members README.md scripts src target +//! ``` +//! +//! ```bash +//! $ ls | cat +//! Cargo.lock +//! Cargo.toml +//! data +//! LICENSE +//! members +//! README.md +//! scripts +//! src +//! target +//! ``` +//! +//! See the [CLI Rustbook](https://rust-cli.github.io/book/in-depth/machine-communication.html) for +//! more information on the topic. use comfy_table::presets; use comfy_table::{CellAlignment, ContentArrangement, Table}; -- 2.40.1 From a9fe9d21ec1ddcddb48c5e06d42fdd4df3e95245 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Fri, 28 Jun 2024 18:09:36 +0200 Subject: [PATCH 30/66] chore: reexport shlex and add dialoguer features --- members/libpt-cli/Cargo.toml | 3 ++- members/libpt-cli/src/lib.rs | 1 + 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/members/libpt-cli/Cargo.toml b/members/libpt-cli/Cargo.toml index 7426ae7..cf023a7 100644 --- a/members/libpt-cli/Cargo.toml +++ b/members/libpt-cli/Cargo.toml @@ -21,9 +21,10 @@ anyhow.workspace = true clap = { version = "4.5.7", features = ["derive"] } comfy-table = "7.1.1" console = "0.15.8" -dialoguer = "0.11.0" +dialoguer = { version = "0.11.0", features = ["completion", "history"] } exitcode = "1.1.2" human-panic = "2.0.0" indicatif = "0.17.8" libpt-log = { workspace = true, optional = true } log = { version = "0.4.21", optional = true } +shlex = "1.3.0" diff --git a/members/libpt-cli/src/lib.rs b/members/libpt-cli/src/lib.rs index 60c20ce..6abc3cd 100644 --- a/members/libpt-cli/src/lib.rs +++ b/members/libpt-cli/src/lib.rs @@ -9,3 +9,4 @@ pub use dialoguer; pub use exitcode; pub use human_panic; pub use indicatif; +pub use shlex; -- 2.40.1 From d413b74d45493919091168b6d1e6074553f3ea2f Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Fri, 28 Jun 2024 18:10:08 +0200 Subject: [PATCH 31/66] feat(cli): add repl example without using library items --- members/libpt-cli/examples/repl.rs | 133 +++++++++++++++++++++++++++++ 1 file changed, 133 insertions(+) create mode 100644 members/libpt-cli/examples/repl.rs diff --git a/members/libpt-cli/examples/repl.rs b/members/libpt-cli/examples/repl.rs new file mode 100644 index 0000000..106813e --- /dev/null +++ b/members/libpt-cli/examples/repl.rs @@ -0,0 +1,133 @@ +use libpt_cli::repl::REPL_HELP_TEMPLATE; +use libpt_cli::{clap, dialoguer, printing}; +use libpt_log::{debug, trace, Level, Logger}; + +use clap::{Parser, Subcommand}; + +/// This is the help menu of the repl +/// +/// More text here +#[derive(Parser, Debug)] +#[command(multicall = true)] +pub struct Repl { + /// the command you want to execute, along with its args + #[command(subcommand)] + command: ReplCommand, +} + +#[derive(Subcommand, Debug)] +enum ReplCommand { + /// wait for LEN seconds + Wait { + /// wait so long + len: u64, + }, + /// echo the given texts + Echo { + /// the text you want to print + text: Vec, + /// print with a fancy border and colors + #[arg(short, long)] + fancy: bool, + }, + /// hello world + Hello, + /// leave the repl + Exit, +} + +// TODO: somehow autogenerate this!!! +pub struct MyCompletion { + options: Vec, +} +impl Default for MyCompletion { + fn default() -> Self { + MyCompletion { + options: vec![ + "help".to_string(), + "?".to_string(), + "list".to_string(), + "publish".to_string(), + "unpublish".to_string(), + "delete".to_string(), + "read".to_string(), + "show".to_string(), + "new".to_string(), + "ls".to_string(), + ], + } + } +} + +impl dialoguer::Completion for MyCompletion { + /// Simple completion implementation based on substring + fn get(&self, input: &str) -> Option { + let matches = self + .options + .iter() + .filter(|option| option.starts_with(input)) + .collect::>(); + + if matches.len() == 1 { + Some(matches[0].to_string()) + } else { + None + } + } +} + +fn main() -> anyhow::Result<()> { + let _logger = Logger::builder() + .show_time(false) + .max_level(Level::DEBUG) + .build(); + + let mut buf: String = String::new(); + let mut buf_preparsed: Vec; + let completion = MyCompletion::default(); + let mut history = dialoguer::BasicHistory::new(); + + debug!("entering the repl"); + loop { + buf.clear(); + + buf = dialoguer::Input::with_theme(&dialoguer::theme::ColorfulTheme::default()) + .completion_with(&completion) + .history_with(&mut history) + .interact_text()?; + + buf_preparsed = Vec::new(); + buf_preparsed.extend(shlex::split(&buf).unwrap_or_default()); + + trace!("read input: {buf_preparsed:?}"); + + let options = match Repl::try_parse_from(buf_preparsed) { + Ok(c) => c, + Err(e) => { + println!("{e}"); + continue; + } + }; + + match options.command { + ReplCommand::Exit => break, + ReplCommand::Wait { len } => { + debug!("len: {len}"); + let spinner = indicatif::ProgressBar::new_spinner(); + spinner.enable_steady_tick(std::time::Duration::from_millis(100)); + std::thread::sleep(std::time::Duration::from_secs(len)); + spinner.finish(); + } + ReplCommand::Hello => println!("Hello!"), + ReplCommand::Echo { text, fancy } => { + if !fancy { + println!("{}", text.concat()) + } + else { + printing::blockprint(text.concat(), console::Color::Cyan) + } + } + } + } + Ok(()) +} -- 2.40.1 From 471364e71167158266eb86069f9375e63c48b2e6 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Fri, 28 Jun 2024 23:05:16 +0200 Subject: [PATCH 32/66] feat(cli::repl): #84 #86 working and nice repl structure --- members/libpt-cli/Cargo.toml | 1 + members/libpt-cli/examples/repl.rs | 93 +++----------- members/libpt-cli/src/lib.rs | 1 + members/libpt-cli/src/repl/mod.rs | 190 +++++++++++++++++++++++++++++ 4 files changed, 209 insertions(+), 76 deletions(-) diff --git a/members/libpt-cli/Cargo.toml b/members/libpt-cli/Cargo.toml index cf023a7..fbf5f6e 100644 --- a/members/libpt-cli/Cargo.toml +++ b/members/libpt-cli/Cargo.toml @@ -28,3 +28,4 @@ indicatif = "0.17.8" libpt-log = { workspace = true, optional = true } log = { version = "0.4.21", optional = true } shlex = "1.3.0" +strum = { version = "0.26.3", features = ["derive"] } diff --git a/members/libpt-cli/examples/repl.rs b/members/libpt-cli/examples/repl.rs index 106813e..0289149 100644 --- a/members/libpt-cli/examples/repl.rs +++ b/members/libpt-cli/examples/repl.rs @@ -1,21 +1,11 @@ -use libpt_cli::repl::REPL_HELP_TEMPLATE; -use libpt_cli::{clap, dialoguer, printing}; -use libpt_log::{debug, trace, Level, Logger}; +use libpt_cli::repl::{DefaultRepl, Repl}; +use libpt_cli::{clap, printing, strum}; +use libpt_log::{debug, Level, Logger}; -use clap::{Parser, Subcommand}; +use clap::Subcommand; +use strum::EnumIter; -/// This is the help menu of the repl -/// -/// More text here -#[derive(Parser, Debug)] -#[command(multicall = true)] -pub struct Repl { - /// the command you want to execute, along with its args - #[command(subcommand)] - command: ReplCommand, -} - -#[derive(Subcommand, Debug)] +#[derive(Subcommand, Debug, EnumIter, Clone)] enum ReplCommand { /// wait for LEN seconds Wait { @@ -36,72 +26,21 @@ enum ReplCommand { Exit, } -// TODO: somehow autogenerate this!!! -pub struct MyCompletion { - options: Vec, -} -impl Default for MyCompletion { - fn default() -> Self { - MyCompletion { - options: vec![ - "help".to_string(), - "?".to_string(), - "list".to_string(), - "publish".to_string(), - "unpublish".to_string(), - "delete".to_string(), - "read".to_string(), - "show".to_string(), - "new".to_string(), - "ls".to_string(), - ], - } - } -} - -impl dialoguer::Completion for MyCompletion { - /// Simple completion implementation based on substring - fn get(&self, input: &str) -> Option { - let matches = self - .options - .iter() - .filter(|option| option.starts_with(input)) - .collect::>(); - - if matches.len() == 1 { - Some(matches[0].to_string()) - } else { - None - } - } -} - fn main() -> anyhow::Result<()> { + // You would normally make a proper cli interface with clap before entering the repl. This is + // omitted here for brevity let _logger = Logger::builder() .show_time(false) .max_level(Level::DEBUG) .build(); - let mut buf: String = String::new(); - let mut buf_preparsed: Vec; - let completion = MyCompletion::default(); - let mut history = dialoguer::BasicHistory::new(); + // the compiler can infer that we want to use the ReplCommand enum. + let mut repl = DefaultRepl::::new(); debug!("entering the repl"); loop { - buf.clear(); - - buf = dialoguer::Input::with_theme(&dialoguer::theme::ColorfulTheme::default()) - .completion_with(&completion) - .history_with(&mut history) - .interact_text()?; - - buf_preparsed = Vec::new(); - buf_preparsed.extend(shlex::split(&buf).unwrap_or_default()); - - trace!("read input: {buf_preparsed:?}"); - - let options = match Repl::try_parse_from(buf_preparsed) { + // repl.step() should be at the start of your loop + match repl.step() { Ok(c) => c, Err(e) => { println!("{e}"); @@ -109,7 +48,10 @@ fn main() -> anyhow::Result<()> { } }; - match options.command { + // now we can match our defined commands + // + // only None if the repl has not stepped yet + match repl.command().to_owned().unwrap() { ReplCommand::Exit => break, ReplCommand::Wait { len } => { debug!("len: {len}"); @@ -122,8 +64,7 @@ fn main() -> anyhow::Result<()> { ReplCommand::Echo { text, fancy } => { if !fancy { println!("{}", text.concat()) - } - else { + } else { printing::blockprint(text.concat(), console::Color::Cyan) } } diff --git a/members/libpt-cli/src/lib.rs b/members/libpt-cli/src/lib.rs index 6abc3cd..df6a305 100644 --- a/members/libpt-cli/src/lib.rs +++ b/members/libpt-cli/src/lib.rs @@ -10,3 +10,4 @@ pub use exitcode; pub use human_panic; pub use indicatif; pub use shlex; +pub use strum; diff --git a/members/libpt-cli/src/repl/mod.rs b/members/libpt-cli/src/repl/mod.rs index 8b13789..52dcc6d 100644 --- a/members/libpt-cli/src/repl/mod.rs +++ b/members/libpt-cli/src/repl/mod.rs @@ -1 +1,191 @@ +use std::fmt::Debug; +use clap::{Parser, Subcommand}; +use dialoguer::{BasicHistory, Completion}; +use libpt_log::trace; + +#[derive(Parser)] +#[command(multicall = true)] +pub struct DefaultRepl +where + C: Debug, + C: Subcommand, + C: strum::IntoEnumIterator, +{ + /// the command you want to execute, along with its arguments + #[command(subcommand)] + command: Option, + + // the following fields are not to be parsed from a command, but used for the internal workings + // of the repl + #[clap(skip)] + buf: String, + #[clap(skip)] + buf_preparsed: Vec, + #[clap(skip)] + completion: DefaultReplCompletion, + #[clap(skip)] + history: BasicHistory, +} + +impl Debug for DefaultRepl +where + C: Debug, + C: Subcommand, + C: strum::IntoEnumIterator, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DefaultRepl") + .field("command", &self.command) + .field("buf", &self.buf) + .field("buf_preparsed", &self.buf_preparsed) + .field("completion", &self.completion) + .field("history", &"(no debug)") + .finish() + } +} + +#[derive(Debug)] +pub struct DefaultReplCompletion +where + C: Debug, + C: Subcommand, + C: strum::IntoEnumIterator, +{ + commands: std::marker::PhantomData, +} + +impl DefaultReplCompletion +where + C: Debug, + C: Subcommand, + C: strum::IntoEnumIterator, +{ + pub fn new() -> Self { + Self { + commands: std::marker::PhantomData::, + } + } + fn commands(&self) -> Vec { + let mut buf = Vec::new(); + // every crate has the help command, but it is not part of the enum + buf.push("help".to_string()); + for c in C::iter() { + // HACK: this is a horrible way to do this + // I just need the names of the commands + buf.push( + format!("{c:?}") + .split_whitespace() + .map(|e| e.to_lowercase()) + .next() + .unwrap() + .to_string(), + ) + } + trace!("commands: {buf:?}"); + buf + } +} + +impl Default for DefaultReplCompletion +where + C: Debug, + C: Subcommand, + C: strum::IntoEnumIterator, +{ + fn default() -> Self { + Self::new() + } +} + +pub trait Repl: Parser + Debug +where + C: Debug, + C: Subcommand, + C: strum::IntoEnumIterator, +{ + /// create a new repl + fn new() -> Self; + /// get the command that was parsed from user input + /// + /// Will only be [None] if the repl has not had [step] executed yet. + fn command(&self) -> &Option; + /// return all possible commands in this repl + fn completion() -> impl Completion; + /// advance the repl to the next iteration of the main loop + /// + /// This should be used at the start of your loop + fn step(&mut self) -> anyhow::Result<()>; +} + +impl Completion for DefaultReplCompletion +where + C: Debug, + C: Subcommand, + C: strum::IntoEnumIterator, +{ + /// Simple completion implementation based on substring + fn get(&self, input: &str) -> Option { + let matches = self + .commands() + .into_iter() + .filter(|option| option.starts_with(input)) + .collect::>(); + + trace!("\nmatches: {matches:#?}"); + if matches.len() == 1 { + Some(matches[0].to_string()) + } else { + None + } + } +} + +impl Repl for DefaultRepl +where + C: Debug, + C: Subcommand, + C: strum::IntoEnumIterator, +{ + fn new() -> Self { + Self { + command: None, + buf_preparsed: Vec::new(), + buf: String::new(), + history: BasicHistory::new(), + completion: DefaultReplCompletion::new(), + } + } + fn command(&self) -> &Option { + &self.command + } + #[allow(refining_impl_trait)] + fn completion() -> DefaultReplCompletion { + DefaultReplCompletion { + commands: std::marker::PhantomData::, + } + } + fn step(&mut self) -> anyhow::Result<()> { + self.buf.clear(); + + // NOTE: display::Input requires some kind of lifetime that would be a bother to store in + // our struct. It's documentation also uses it in place, so it should be fine to do it like + // this. + self.buf = dialoguer::Input::with_theme(&dialoguer::theme::ColorfulTheme::default()) + .completion_with(&self.completion) + .history_with(&mut self.history) + .interact_text()?; + + self.buf_preparsed = Vec::new(); + self.buf_preparsed + .extend(shlex::split(&self.buf).unwrap_or_default()); + + trace!("read input: {:?}", self.buf_preparsed); + trace!("repl after step: {:#?}", self); + + // HACK: find a way to not allocate a new struct for this + let cmds = Self::try_parse_from(&self.buf_preparsed)?; + self.command = cmds.command; + Ok(()) + } +} -- 2.40.1 From 0488b2f497276c5fbc6b6ac0e4699346a3a63baf Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Fri, 28 Jun 2024 23:53:00 +0200 Subject: [PATCH 33/66] refactor(cli::repl): introduce custom error type, modularize --- members/libpt-cli/Cargo.toml | 1 + members/libpt-cli/examples/repl.rs | 23 +++- members/libpt-cli/src/repl/default.rs | 190 ++++++++++++++++++++++++++ members/libpt-cli/src/repl/error.rs | 9 ++ members/libpt-cli/src/repl/mod.rs | 176 +----------------------- 5 files changed, 224 insertions(+), 175 deletions(-) create mode 100644 members/libpt-cli/src/repl/default.rs create mode 100644 members/libpt-cli/src/repl/error.rs diff --git a/members/libpt-cli/Cargo.toml b/members/libpt-cli/Cargo.toml index fbf5f6e..0900f7d 100644 --- a/members/libpt-cli/Cargo.toml +++ b/members/libpt-cli/Cargo.toml @@ -29,3 +29,4 @@ libpt-log = { workspace = true, optional = true } log = { version = "0.4.21", optional = true } shlex = "1.3.0" strum = { version = "0.26.3", features = ["derive"] } +thiserror.workspace = true diff --git a/members/libpt-cli/examples/repl.rs b/members/libpt-cli/examples/repl.rs index 0289149..34bf4fa 100644 --- a/members/libpt-cli/examples/repl.rs +++ b/members/libpt-cli/examples/repl.rs @@ -1,3 +1,4 @@ +use console::style; use libpt_cli::repl::{DefaultRepl, Repl}; use libpt_cli::{clap, printing, strum}; use libpt_log::{debug, Level, Logger}; @@ -5,6 +6,7 @@ use libpt_log::{debug, Level, Logger}; use clap::Subcommand; use strum::EnumIter; +// this is where you define what data/commands/arguments the REPL accepts #[derive(Subcommand, Debug, EnumIter, Clone)] enum ReplCommand { /// wait for LEN seconds @@ -31,19 +33,28 @@ fn main() -> anyhow::Result<()> { // omitted here for brevity let _logger = Logger::builder() .show_time(false) - .max_level(Level::DEBUG) - .build(); + .max_level(Level::INFO) + .build()?; // the compiler can infer that we want to use the ReplCommand enum. - let mut repl = DefaultRepl::::new(); + let mut repl = DefaultRepl::::default(); debug!("entering the repl"); loop { // repl.step() should be at the start of your loop + // It is here that the repl will get the user input, validate it, and so on match repl.step() { Ok(c) => c, Err(e) => { - println!("{e}"); + // if the user requested the help, print in blue, otherwise in red as it's just an + // error + if let libpt_cli::repl::error::ReplError::Parsing(e) = &e { + if e.kind() == clap::error::ErrorKind::DisplayHelp { + println!("{}", style(e).cyan()); + continue; + } + } + println!("{}", style(e).red().bold()); continue; } }; @@ -63,9 +74,9 @@ fn main() -> anyhow::Result<()> { ReplCommand::Hello => println!("Hello!"), ReplCommand::Echo { text, fancy } => { if !fancy { - println!("{}", text.concat()) + println!("{}", text.join(" ")) } else { - printing::blockprint(text.concat(), console::Color::Cyan) + printing::blockprint(text.join(" "), console::Color::Cyan) } } } diff --git a/members/libpt-cli/src/repl/default.rs b/members/libpt-cli/src/repl/default.rs new file mode 100644 index 0000000..3096167 --- /dev/null +++ b/members/libpt-cli/src/repl/default.rs @@ -0,0 +1,190 @@ +use std::fmt::Debug; + +use super::Repl; + +use clap::{Parser, Subcommand}; +use dialoguer::{BasicHistory, Completion}; +use libpt_log::trace; + +#[derive(Parser)] +#[command(multicall = true)] +pub struct DefaultRepl +where + C: Debug, + C: Subcommand, + C: strum::IntoEnumIterator, +{ + /// the command you want to execute, along with its arguments + #[command(subcommand)] + command: Option, + + // the following fields are not to be parsed from a command, but used for the internal workings + // of the repl + #[clap(skip)] + buf: String, + #[clap(skip)] + buf_preparsed: Vec, + #[clap(skip)] + completion: DefaultReplCompletion, + #[clap(skip)] + history: BasicHistory, +} + +#[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Ord)] +pub struct DefaultReplCompletion +where + C: Debug, + C: Subcommand, + C: strum::IntoEnumIterator, +{ + commands: std::marker::PhantomData, +} + +impl Repl for DefaultRepl +where + C: Debug, + C: Subcommand, + C: strum::IntoEnumIterator, +{ + fn new() -> Self { + Self { + command: None, + buf_preparsed: Vec::new(), + buf: String::new(), + history: BasicHistory::new(), + completion: DefaultReplCompletion::new(), + } + } + fn command(&self) -> &Option { + &self.command + } + #[allow(refining_impl_trait)] + fn completion() -> DefaultReplCompletion { + DefaultReplCompletion { + commands: std::marker::PhantomData::, + } + } + fn step(&mut self) -> Result<(), super::error::ReplError> { + self.buf.clear(); + + // NOTE: display::Input requires some kind of lifetime that would be a bother to store in + // our struct. It's documentation also uses it in place, so it should be fine to do it like + // this. + // + // NOTE: It would be nice if we could use the Validator mechanism of dialoguer, but + // unfortunately we can only process our input after we've preparsed it and we need an + // actual output. If we could set a status after the Input is over that would be amazing, + // but that is currently not supported by dialoguer. + // Therefore, every prompt will show as success regardless. + self.buf = dialoguer::Input::with_theme(&dialoguer::theme::ColorfulTheme::default()) + .completion_with(&self.completion) + .history_with(&mut self.history) + .interact_text()?; + + self.buf_preparsed = Vec::new(); + self.buf_preparsed + .extend(shlex::split(&self.buf).unwrap_or_default()); + + trace!("read input: {:?}", self.buf_preparsed); + trace!("repl after step: {:#?}", self); + + // HACK: find a way to not allocate a new struct for this + let cmds = Self::try_parse_from(&self.buf_preparsed)?; + self.command = cmds.command; + Ok(()) + } +} + +impl Default for DefaultRepl +where + C: Debug, + C: Subcommand, + C: strum::IntoEnumIterator, +{ + fn default() -> Self { + Self::new() + } +} + +impl Debug for DefaultRepl +where + C: Debug, + C: Subcommand, + C: strum::IntoEnumIterator, +{ + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + f.debug_struct("DefaultRepl") + .field("command", &self.command) + .field("buf", &self.buf) + .field("buf_preparsed", &self.buf_preparsed) + .field("completion", &self.completion) + .field("history", &"(no debug)") + .finish() + } +} + +impl DefaultReplCompletion +where + C: Debug, + C: Subcommand, + C: strum::IntoEnumIterator, +{ + pub fn new() -> Self { + Self { + commands: std::marker::PhantomData::, + } + } + fn commands(&self) -> Vec { + let mut buf = Vec::new(); + // every crate has the help command, but it is not part of the enum + buf.push("help".to_string()); + for c in C::iter() { + // HACK: this is a horrible way to do this + // I just need the names of the commands + buf.push( + format!("{c:?}") + .split_whitespace() + .map(|e| e.to_lowercase()) + .next() + .unwrap() + .to_string(), + ) + } + trace!("commands: {buf:?}"); + buf + } +} + +impl Default for DefaultReplCompletion +where + C: Debug, + C: Subcommand, + C: strum::IntoEnumIterator, +{ + fn default() -> Self { + Self::new() + } +} + +impl Completion for DefaultReplCompletion +where + C: Debug, + C: Subcommand, + C: strum::IntoEnumIterator, +{ + /// Simple completion implementation based on substring + fn get(&self, input: &str) -> Option { + let matches = self + .commands() + .into_iter() + .filter(|option| option.starts_with(input)) + .collect::>(); + + trace!("\nmatches: {matches:#?}"); + if matches.len() == 1 { + Some(matches[0].to_string()) + } else { + None + } + } +} diff --git a/members/libpt-cli/src/repl/error.rs b/members/libpt-cli/src/repl/error.rs new file mode 100644 index 0000000..c4416a3 --- /dev/null +++ b/members/libpt-cli/src/repl/error.rs @@ -0,0 +1,9 @@ +use thiserror::Error; + +#[derive(Error, Debug)] +pub enum ReplError { + #[error(transparent)] + Parsing(#[from] clap::Error), + #[error(transparent)] + Input(#[from] dialoguer::Error) +} diff --git a/members/libpt-cli/src/repl/mod.rs b/members/libpt-cli/src/repl/mod.rs index 52dcc6d..87a95eb 100644 --- a/members/libpt-cli/src/repl/mod.rs +++ b/members/libpt-cli/src/repl/mod.rs @@ -1,102 +1,12 @@ use std::fmt::Debug; +pub mod error; +use error::ReplError; +mod default; +pub use default::*; + use clap::{Parser, Subcommand}; -use dialoguer::{BasicHistory, Completion}; -use libpt_log::trace; - -#[derive(Parser)] -#[command(multicall = true)] -pub struct DefaultRepl -where - C: Debug, - C: Subcommand, - C: strum::IntoEnumIterator, -{ - /// the command you want to execute, along with its arguments - #[command(subcommand)] - command: Option, - - // the following fields are not to be parsed from a command, but used for the internal workings - // of the repl - #[clap(skip)] - buf: String, - #[clap(skip)] - buf_preparsed: Vec, - #[clap(skip)] - completion: DefaultReplCompletion, - #[clap(skip)] - history: BasicHistory, -} - -impl Debug for DefaultRepl -where - C: Debug, - C: Subcommand, - C: strum::IntoEnumIterator, -{ - fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { - f.debug_struct("DefaultRepl") - .field("command", &self.command) - .field("buf", &self.buf) - .field("buf_preparsed", &self.buf_preparsed) - .field("completion", &self.completion) - .field("history", &"(no debug)") - .finish() - } -} - -#[derive(Debug)] -pub struct DefaultReplCompletion -where - C: Debug, - C: Subcommand, - C: strum::IntoEnumIterator, -{ - commands: std::marker::PhantomData, -} - -impl DefaultReplCompletion -where - C: Debug, - C: Subcommand, - C: strum::IntoEnumIterator, -{ - pub fn new() -> Self { - Self { - commands: std::marker::PhantomData::, - } - } - fn commands(&self) -> Vec { - let mut buf = Vec::new(); - // every crate has the help command, but it is not part of the enum - buf.push("help".to_string()); - for c in C::iter() { - // HACK: this is a horrible way to do this - // I just need the names of the commands - buf.push( - format!("{c:?}") - .split_whitespace() - .map(|e| e.to_lowercase()) - .next() - .unwrap() - .to_string(), - ) - } - trace!("commands: {buf:?}"); - buf - } -} - -impl Default for DefaultReplCompletion -where - C: Debug, - C: Subcommand, - C: strum::IntoEnumIterator, -{ - fn default() -> Self { - Self::new() - } -} +use dialoguer::Completion; pub trait Repl: Parser + Debug where @@ -115,77 +25,5 @@ where /// advance the repl to the next iteration of the main loop /// /// This should be used at the start of your loop - fn step(&mut self) -> anyhow::Result<()>; -} - -impl Completion for DefaultReplCompletion -where - C: Debug, - C: Subcommand, - C: strum::IntoEnumIterator, -{ - /// Simple completion implementation based on substring - fn get(&self, input: &str) -> Option { - let matches = self - .commands() - .into_iter() - .filter(|option| option.starts_with(input)) - .collect::>(); - - trace!("\nmatches: {matches:#?}"); - if matches.len() == 1 { - Some(matches[0].to_string()) - } else { - None - } - } -} - -impl Repl for DefaultRepl -where - C: Debug, - C: Subcommand, - C: strum::IntoEnumIterator, -{ - fn new() -> Self { - Self { - command: None, - buf_preparsed: Vec::new(), - buf: String::new(), - history: BasicHistory::new(), - completion: DefaultReplCompletion::new(), - } - } - fn command(&self) -> &Option { - &self.command - } - #[allow(refining_impl_trait)] - fn completion() -> DefaultReplCompletion { - DefaultReplCompletion { - commands: std::marker::PhantomData::, - } - } - fn step(&mut self) -> anyhow::Result<()> { - self.buf.clear(); - - // NOTE: display::Input requires some kind of lifetime that would be a bother to store in - // our struct. It's documentation also uses it in place, so it should be fine to do it like - // this. - self.buf = dialoguer::Input::with_theme(&dialoguer::theme::ColorfulTheme::default()) - .completion_with(&self.completion) - .history_with(&mut self.history) - .interact_text()?; - - self.buf_preparsed = Vec::new(); - self.buf_preparsed - .extend(shlex::split(&self.buf).unwrap_or_default()); - - trace!("read input: {:?}", self.buf_preparsed); - trace!("repl after step: {:#?}", self); - - // HACK: find a way to not allocate a new struct for this - let cmds = Self::try_parse_from(&self.buf_preparsed)?; - self.command = cmds.command; - Ok(()) - } + fn step(&mut self) -> Result<(), ReplError>; } -- 2.40.1 From 216b89606747bcc224fac7464203afe08877a643 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Fri, 28 Jun 2024 21:55:12 +0000 Subject: [PATCH 34/66] automatic cargo CI changes --- members/libpt-cli/src/repl/error.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/members/libpt-cli/src/repl/error.rs b/members/libpt-cli/src/repl/error.rs index c4416a3..1439aae 100644 --- a/members/libpt-cli/src/repl/error.rs +++ b/members/libpt-cli/src/repl/error.rs @@ -5,5 +5,5 @@ pub enum ReplError { #[error(transparent)] Parsing(#[from] clap::Error), #[error(transparent)] - Input(#[from] dialoguer::Error) + Input(#[from] dialoguer::Error), } -- 2.40.1 From bfebb5327b7c6eb130b7d7d036a0016d611fd770 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Sat, 29 Jun 2024 00:49:28 +0200 Subject: [PATCH 35/66] docs(cli::repl): repl documentation --- members/libpt-cli/Cargo.toml | 4 ++ members/libpt-cli/data/media/repl.png | Bin 0 -> 145738 bytes members/libpt-cli/src/repl/default.rs | 58 ++++++++++++++++++++++---- members/libpt-cli/src/repl/error.rs | 2 + members/libpt-cli/src/repl/mod.rs | 25 ++++++++--- 5 files changed, 76 insertions(+), 13 deletions(-) create mode 100644 members/libpt-cli/data/media/repl.png diff --git a/members/libpt-cli/Cargo.toml b/members/libpt-cli/Cargo.toml index 0900f7d..9ec6de5 100644 --- a/members/libpt-cli/Cargo.toml +++ b/members/libpt-cli/Cargo.toml @@ -12,6 +12,9 @@ repository.workspace = true keywords.workspace = true categories.workspace = true +[package.metadata.docs.rs] +cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] + [features] default = ["log"] log = ["dep:libpt-log", "dep:log"] @@ -22,6 +25,7 @@ clap = { version = "4.5.7", features = ["derive"] } comfy-table = "7.1.1" console = "0.15.8" dialoguer = { version = "0.11.0", features = ["completion", "history"] } +embed-doc-image = "0.1.4" exitcode = "1.1.2" human-panic = "2.0.0" indicatif = "0.17.8" diff --git a/members/libpt-cli/data/media/repl.png b/members/libpt-cli/data/media/repl.png new file mode 100644 index 0000000000000000000000000000000000000000..7121227c77f410b0def3af9412348b2e64c33572 GIT binary patch literal 145738 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Πp+V=?xa1YqwVL23(4t+cAJEAvQ0mQ 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::default(); +/// loop { +/// repl.step().unwrap(); +/// match repl.command().to_owned().unwrap() { +/// ReplCommand::Hello => println!("Hello"), +/// ReplCommand::Exit => break, +/// _ => (), +/// } +/// } +/// } +/// ``` +/// **Screenshot** +/// +/// ![Screenshot of an example program with a REPL][repl_screenshot] +#[embed_doc_image("repl_screenshot", "data/media/repl.png")] #[derive(Parser)] -#[command(multicall = true)] +#[command(multicall = true, help_template = REPL_HELP_TEMPLATE)] pub struct DefaultRepl where C: Debug, @@ -31,7 +79,7 @@ where } #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Ord)] -pub struct DefaultReplCompletion +struct DefaultReplCompletion where C: Debug, C: Subcommand, @@ -58,12 +106,6 @@ where fn command(&self) -> &Option { &self.command } - #[allow(refining_impl_trait)] - fn completion() -> DefaultReplCompletion { - DefaultReplCompletion { - commands: std::marker::PhantomData::, - } - } fn step(&mut self) -> Result<(), super::error::ReplError> { self.buf.clear(); diff --git a/members/libpt-cli/src/repl/error.rs b/members/libpt-cli/src/repl/error.rs index c4416a3..d78aca2 100644 --- a/members/libpt-cli/src/repl/error.rs +++ b/members/libpt-cli/src/repl/error.rs @@ -1,3 +1,5 @@ +//! Errors for the Repl module + use thiserror::Error; #[derive(Error, Debug)] diff --git a/members/libpt-cli/src/repl/mod.rs b/members/libpt-cli/src/repl/mod.rs index 87a95eb..5c0d343 100644 --- a/members/libpt-cli/src/repl/mod.rs +++ b/members/libpt-cli/src/repl/mod.rs @@ -1,3 +1,16 @@ +//! Create easy and well defined REPLs +//! +//! A REPL is a [Read-Eval-Print-Loop](https://en.wikipedia.org/wiki/Read%E2%80%93eval%E2%80%93print_loop). +//! Well known examples for REPLs are shells (like bash). +//! +//! This module offers a convenient way to create a well-defined REPL without a lot of complicated +//! code and with a visually pleasing aesthetic. An example REPL implementation can be found in the +//! examples. +//! +//! The basic idea is that the user defines the commands with an enum and uses [claps](clap) +//! `#[derive(Subcommand)]`. A loop is then used to read from the stdin into a buffer, that buffer +//! is put to [clap] for parsing, similar to how [clap] would parse commandline arguments. + use std::fmt::Debug; pub mod error; @@ -6,8 +19,10 @@ mod default; pub use default::*; use clap::{Parser, Subcommand}; -use dialoguer::Completion; +/// Common Trait for repl objects +/// +/// Unless you want to implement custom features (not just commands), just use [DefaultRepl]. pub trait Repl: Parser + Debug where C: Debug, @@ -18,12 +33,12 @@ where fn new() -> Self; /// get the command that was parsed from user input /// - /// Will only be [None] if the repl has not had [step] executed yet. + /// Will only be [None] if the repl has not had [step](Repl::step) executed yet. fn command(&self) -> &Option; - /// return all possible commands in this repl - fn completion() -> impl Completion; /// advance the repl to the next iteration of the main loop /// - /// This should be used at the start of your loop + /// This should be used at the start of your loop. + /// + /// Note that the help menu is an Error: [clap::error::ErrorKind::DisplayHelp] fn step(&mut self) -> Result<(), ReplError>; } -- 2.40.1 From 74aaeb0ec2ccb703dc49ee5a042c9d8c618b5219 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Sat, 29 Jun 2024 17:05:37 +0200 Subject: [PATCH 36/66] refactor(cli): verbosity level no option anymore, prevent verbosity overflows --- members/libpt-cli/src/args.rs | 27 ++++++++++----------------- 1 file changed, 10 insertions(+), 17 deletions(-) diff --git a/members/libpt-cli/src/args.rs b/members/libpt-cli/src/args.rs index 7ebdacd..d947a3e 100644 --- a/members/libpt-cli/src/args.rs +++ b/members/libpt-cli/src/args.rs @@ -94,9 +94,9 @@ Author: {author-with-newline} /// /// // Level might be None if the user wants no output at all. /// // for the 'tracing' level: -/// let level: Option = opts.verbose.level(); +/// let level: Level = opts.verbose.level(); /// // for the 'log' level: -/// let llevel: Option = opts.verbose.level_for_log_crate(); +/// let llevel: log::Level = opts.verbose.level_for_log_crate(); /// } /// ``` #[derive(Parser, Clone, PartialEq, Eq, Hash)] @@ -134,27 +134,20 @@ impl VerbosityLevel { } #[inline] fn value(&self) -> i8 { - let v = Self::level_value(Level::INFO) - (self.quiet as i8) + (self.verbose as i8); - if v > Self::level_value(Level::TRACE) { - Self::level_value(Level::TRACE) - } else { - v - } + Self::level_value(Level::INFO) - (self.quiet as i8).min(10) + (self.verbose as i8).min(10) } /// get the [Level] for that VerbosityLevel - /// - /// [None] means that absolutely no output is wanted (completely quiet) #[inline] - pub fn level(&self) -> Option { - Some(match self.value() { + pub fn level(&self) -> Level { + match self.value() { 0 => Level::ERROR, 1 => Level::WARN, 2 => Level::INFO, 3 => Level::DEBUG, 4 => Level::TRACE, - _ => return None, - }) + _ => Level::ERROR, + } } /// get the [log::Level] for that VerbosityLevel @@ -163,14 +156,14 @@ impl VerbosityLevel { /// /// [None] means that absolutely no output is wanted (completely quiet) #[inline] - pub fn level_for_log_crate(&self) -> Option { - self.level().map(|ll| match ll { + pub fn level_for_log_crate(&self) -> log::Level { + match self.level() { Level::TRACE => log::Level::Trace, Level::DEBUG => log::Level::Debug, Level::INFO => log::Level::Info, Level::WARN => log::Level::Warn, Level::ERROR => log::Level::Error, - }) + } } #[inline] -- 2.40.1 From c6afa063ef8f877b28494f3c41de73afc99e1826 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Sat, 29 Jun 2024 17:05:50 +0200 Subject: [PATCH 37/66] docs(cli): example for a simple cli --- members/libpt-cli/examples/cli.rs | 41 +++++++++++++++++++++++++++++++ 1 file changed, 41 insertions(+) create mode 100644 members/libpt-cli/examples/cli.rs diff --git a/members/libpt-cli/examples/cli.rs b/members/libpt-cli/examples/cli.rs new file mode 100644 index 0000000..91fdcad --- /dev/null +++ b/members/libpt-cli/examples/cli.rs @@ -0,0 +1,41 @@ +use clap::Parser; +use libpt_cli::args::VerbosityLevel; +use libpt_cli::{clap, printing}; +use libpt_log::{debug, Logger}; + +/// This is the help +/// +/// This is more help +#[derive(Parser, Debug)] +struct Cli { + // already has documentation + #[command(flatten)] + verbosity: VerbosityLevel, + + /// texts to be echoed + #[arg(required = true)] + text: Vec, + + /// try to be more machine readable + #[arg(short, long)] + machine: bool, +} + +fn main() { + let cli = Cli::parse(); + let _logger = Logger::builder() + .max_level(cli.verbosity.level()) + .show_time(false) + .build(); + + debug!("logger initialized with level: {}", cli.verbosity.level()); + + if !cli.machine { + let text = cli.text.join(" "); + printing::blockprint(text, console::Color::Green); + } else { + for text in cli.text { + println!("{text}") + } + } +} -- 2.40.1 From c81952002fd9d1080e31e0e8c16cf59a3f527c67 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Sat, 29 Jun 2024 17:35:45 +0200 Subject: [PATCH 38/66] fix(cli): loglevel values were not calculated correctly --- members/libpt-cli/src/args.rs | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/members/libpt-cli/src/args.rs b/members/libpt-cli/src/args.rs index d947a3e..e4bc491 100644 --- a/members/libpt-cli/src/args.rs +++ b/members/libpt-cli/src/args.rs @@ -140,13 +140,21 @@ impl VerbosityLevel { /// get the [Level] for that VerbosityLevel #[inline] pub fn level(&self) -> Level { - match self.value() { + let v = self.value(); + match v { 0 => Level::ERROR, 1 => Level::WARN, 2 => Level::INFO, 3 => Level::DEBUG, 4 => Level::TRACE, - _ => Level::ERROR, + _ => { + if v > 4 { + Level::TRACE + } else { + /* v < 0 */ + Level::ERROR + } + } } } -- 2.40.1 From 921387b13eb97581d21bdae025889e524f059f41 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 17:36:16 +0200 Subject: [PATCH 39/66] chore: add more clippy lints and fix some --- members/libpt-cli/src/lib.rs | 1 + src/lib.rs | 1 + 2 files changed, 2 insertions(+) diff --git a/members/libpt-cli/src/lib.rs b/members/libpt-cli/src/lib.rs index df6a305..7ffceab 100644 --- a/members/libpt-cli/src/lib.rs +++ b/members/libpt-cli/src/lib.rs @@ -1,3 +1,4 @@ +#![warn(clippy::pedantic, clippy::style, clippy::nursery)] pub mod args; pub mod printing; pub mod repl; diff --git a/src/lib.rs b/src/lib.rs index 5b41b90..307f723 100644 --- a/src/lib.rs +++ b/src/lib.rs @@ -5,6 +5,7 @@ //! //! `pt` is a project consisting of multiple smaller crates, all bundled together in this //! "main crate". Most crates will only show up if you activate their feature. +#![warn(clippy::pedantic, clippy::style, clippy::nursery)] #[cfg_attr(docsrs, doc(cfg(feature = "full")))] #[cfg(feature = "bintols")] pub use libpt_bintols as bintols; -- 2.40.1 From 9ea146aabfc6425da5dea0a105c4032e06ceb398 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 17:36:45 +0200 Subject: [PATCH 40/66] refactor(cli): apply some clippy lints and add constants for VerbosityLevel --- members/libpt-cli/src/args.rs | 94 ++++++++++++++++++++++++++++++++--- 1 file changed, 88 insertions(+), 6 deletions(-) diff --git a/members/libpt-cli/src/args.rs b/members/libpt-cli/src/args.rs index e4bc491..31ae1b7 100644 --- a/members/libpt-cli/src/args.rs +++ b/members/libpt-cli/src/args.rs @@ -46,14 +46,14 @@ use log; /// Author: Christoph J. Scherr /// /// ``` -pub const HELP_TEMPLATE: &str = r#"{about-section} +pub const HELP_TEMPLATE: &str = r"{about-section} {usage-heading} {usage} {all-args}{tab} {name}: {version} Author: {author-with-newline} -"#; +"; /// Transform -v and -q flags to some kind of loglevel /// @@ -111,7 +111,7 @@ pub struct VerbosityLevel { // help = L::verbose_help(), // long_help = L::verbose_long_help(), )] - verbose: u8, + verbose: i8, /// make the output less verbose /// @@ -123,22 +123,37 @@ pub struct VerbosityLevel { global = true, conflicts_with = "verbose", )] - quiet: u8, + quiet: i8, } impl VerbosityLevel { /// true only if no verbose and no quiet was set (user is using defaults) #[inline] + #[must_use] + #[allow(clippy::missing_const_for_fn)] // the values of self can change pub fn changed(&self) -> bool { self.verbose != 0 || self.quiet != 0 } #[inline] fn value(&self) -> i8 { - Self::level_value(Level::INFO) - (self.quiet as i8).min(10) + (self.verbose as i8).min(10) + Self::level_value(Level::INFO) + .saturating_sub((self.quiet).min(10)) + .saturating_add((self.verbose).min(10)) } - /// get the [Level] for that VerbosityLevel + /// get the [Level] for that [`VerbosityLevel`] + /// + /// # Examples + /// + /// ``` + /// use libpt_log::Level; // reexport: tracing + /// use libpt_cli::args::VerbosityLevel; + /// + /// let verbosity_level = VerbosityLevel::INFO; + /// assert_eq!(verbosity_level.level(), Level::INFO); + /// ``` #[inline] + #[must_use] pub fn level(&self) -> Level { let v = self.value(); match v { @@ -164,6 +179,7 @@ impl VerbosityLevel { /// /// [None] means that absolutely no output is wanted (completely quiet) #[inline] + #[must_use] pub fn level_for_log_crate(&self) -> log::Level { match self.level() { Level::TRACE => log::Level::Trace, @@ -184,6 +200,72 @@ impl VerbosityLevel { Level::ERROR => 0, } } + + /// # Examples + /// + /// ``` + /// use libpt_log::Level; // reexport: tracing + /// use libpt_cli::args::VerbosityLevel; + /// + /// let verbosity_level = VerbosityLevel::TRACE; + /// assert_eq!(verbosity_level.level(), Level::TRACE); + /// ``` + pub const TRACE: Self = Self { + verbose: 2, + quiet: 0, + }; + /// # Examples + /// + /// ``` + /// use libpt_log::Level; // reexport: tracing + /// use libpt_cli::args::VerbosityLevel; + /// + /// let verbosity_level = VerbosityLevel::DEBUG; + /// assert_eq!(verbosity_level.level(), Level::DEBUG); + /// ``` + pub const DEBUG: Self = Self { + verbose: 1, + quiet: 0, + }; + /// # Examples + /// + /// ``` + /// use libpt_log::Level; // reexport: tracing + /// use libpt_cli::args::VerbosityLevel; + /// + /// let verbosity_level = VerbosityLevel::INFO; + /// assert_eq!(verbosity_level.level(), Level::INFO); + /// ``` + pub const INFO: Self = Self { + verbose: 0, + quiet: 0, + }; + /// # Examples + /// + /// ``` + /// use libpt_log::Level; // reexport: tracing + /// use libpt_cli::args::VerbosityLevel; + /// + /// let verbosity_level = VerbosityLevel::WARN; + /// assert_eq!(verbosity_level.level(), Level::WARN); + /// ``` + pub const WARN: Self = Self { + verbose: 0, + quiet: 1, + }; + /// # Examples + /// + /// ``` + /// use libpt_log::Level; // reexport: tracing + /// use libpt_cli::args::VerbosityLevel; + /// + /// let verbosity_level = VerbosityLevel::ERROR; + /// assert_eq!(verbosity_level.level(), Level::ERROR); + /// ``` + pub const ERROR: Self = Self { + verbose: 0, + quiet: 2, + }; } impl std::fmt::Debug for VerbosityLevel { -- 2.40.1 From c9c835188b1b0d0392afb7aaab8b21ab4257d5b8 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 15:39:06 +0000 Subject: [PATCH 41/66] automatic cargo CI changes --- members/libpt-cli/src/args.rs | 2 +- members/libpt-cli/src/printing.rs | 6 +++--- members/libpt-cli/src/repl/default.rs | 8 ++++---- members/libpt-cli/src/repl/mod.rs | 4 ++-- 4 files changed, 10 insertions(+), 10 deletions(-) diff --git a/members/libpt-cli/src/args.rs b/members/libpt-cli/src/args.rs index 31ae1b7..20131cb 100644 --- a/members/libpt-cli/src/args.rs +++ b/members/libpt-cli/src/args.rs @@ -173,7 +173,7 @@ impl VerbosityLevel { } } - /// get the [log::Level] for that VerbosityLevel + /// get the [`log::Level`] for that `VerbosityLevel` /// /// This is the method for the [log] crate, which I use less often. /// diff --git a/members/libpt-cli/src/printing.rs b/members/libpt-cli/src/printing.rs index e9d0c35..dff8192 100644 --- a/members/libpt-cli/src/printing.rs +++ b/members/libpt-cli/src/printing.rs @@ -5,7 +5,7 @@ //! output for CLI applications. //! //! Note that most of the utilities in this module are focused on communication with humans, not -//! with machines. Consider evaluating [std::io::IsTerminal] before using colorful, dynamic and bordered +//! with machines. Consider evaluating [`std::io::IsTerminal`] before using colorful, dynamic and bordered //! printing. If you are talking to a machine, it might be useful to not add extra space, add a //! newline per output or even output JSON. An example that does this well is `ls`: //! @@ -50,12 +50,12 @@ use console::{style, Color}; /// ``` #[inline] pub fn blockprint(content: impl ToString, color: Color) { - println!("{}", blockfmt(content, color)) + println!("{}", blockfmt(content, color)); } /// Formats content with a simple border around it /// -/// This function is a convenience wrapper around [blockfmt_advanced] with preset values for +/// This function is a convenience wrapper around [`blockfmt_advanced`] with preset values for /// border style, content arrangement, and cell alignment. It automatically formats the content /// with a border as large as possible and centers the content. The resulting cell is colored in /// the specified color. diff --git a/members/libpt-cli/src/repl/default.rs b/members/libpt-cli/src/repl/default.rs index 7e540a7..28b249f 100644 --- a/members/libpt-cli/src/repl/default.rs +++ b/members/libpt-cli/src/repl/default.rs @@ -9,10 +9,10 @@ use super::Repl; use embed_doc_image::embed_doc_image; /// [clap] help template with only usage and commands/options -pub const REPL_HELP_TEMPLATE: &str = r#"{usage-heading} {usage} +pub const REPL_HELP_TEMPLATE: &str = r"{usage-heading} {usage} {all-args}{tab} -"#; +"; use clap::{Parser, Subcommand}; use dialoguer::{BasicHistory, Completion}; @@ -186,11 +186,11 @@ where buf.push( format!("{c:?}") .split_whitespace() - .map(|e| e.to_lowercase()) + .map(str::to_lowercase) .next() .unwrap() .to_string(), - ) + ); } trace!("commands: {buf:?}"); buf diff --git a/members/libpt-cli/src/repl/mod.rs b/members/libpt-cli/src/repl/mod.rs index 5c0d343..cea67dc 100644 --- a/members/libpt-cli/src/repl/mod.rs +++ b/members/libpt-cli/src/repl/mod.rs @@ -22,7 +22,7 @@ use clap::{Parser, Subcommand}; /// Common Trait for repl objects /// -/// Unless you want to implement custom features (not just commands), just use [DefaultRepl]. +/// Unless you want to implement custom features (not just commands), just use [`DefaultRepl`]. pub trait Repl: Parser + Debug where C: Debug, @@ -39,6 +39,6 @@ where /// /// This should be used at the start of your loop. /// - /// Note that the help menu is an Error: [clap::error::ErrorKind::DisplayHelp] + /// Note that the help menu is an Error: [`clap::error::ErrorKind::DisplayHelp`] fn step(&mut self) -> Result<(), ReplError>; } -- 2.40.1 From 4f15f4b63949ad7b0612620f3c2e6b58c740cbe2 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 17:41:55 +0200 Subject: [PATCH 42/66] refactor(cli): change log feature and impl default for VerbosityLevel --- members/libpt-cli/Cargo.toml | 6 +++--- members/libpt-cli/src/args.rs | 13 ++++++++++--- 2 files changed, 13 insertions(+), 6 deletions(-) diff --git a/members/libpt-cli/Cargo.toml b/members/libpt-cli/Cargo.toml index 9ec6de5..12176b3 100644 --- a/members/libpt-cli/Cargo.toml +++ b/members/libpt-cli/Cargo.toml @@ -16,8 +16,8 @@ categories.workspace = true cargo-args = ["-Zunstable-options", "-Zrustdoc-scrape-examples"] [features] -default = ["log"] -log = ["dep:libpt-log", "dep:log"] +default = [] +log = ["dep:log"] [dependencies] anyhow.workspace = true @@ -29,7 +29,7 @@ embed-doc-image = "0.1.4" exitcode = "1.1.2" human-panic = "2.0.0" indicatif = "0.17.8" -libpt-log = { workspace = true, optional = true } +libpt-log = { workspace = true, optional = false } log = { version = "0.4.21", optional = true } shlex = "1.3.0" strum = { version = "0.26.3", features = ["derive"] } diff --git a/members/libpt-cli/src/args.rs b/members/libpt-cli/src/args.rs index 31ae1b7..ba8a57d 100644 --- a/members/libpt-cli/src/args.rs +++ b/members/libpt-cli/src/args.rs @@ -1,7 +1,6 @@ //! Utilities for parsing options and arguments on the start of a CLI application use clap::Parser; -#[cfg(feature = "log")] use libpt_log::Level; #[cfg(feature = "log")] use log; @@ -100,7 +99,6 @@ Author: {author-with-newline} /// } /// ``` #[derive(Parser, Clone, PartialEq, Eq, Hash)] -#[cfg(feature = "log")] pub struct VerbosityLevel { /// make the output more verbose #[arg( @@ -135,6 +133,7 @@ impl VerbosityLevel { self.verbose != 0 || self.quiet != 0 } #[inline] + #[must_use] fn value(&self) -> i8 { Self::level_value(Level::INFO) .saturating_sub((self.quiet).min(10)) @@ -180,6 +179,7 @@ impl VerbosityLevel { /// [None] means that absolutely no output is wanted (completely quiet) #[inline] #[must_use] + #[cfg(feature = "log")] pub fn level_for_log_crate(&self) -> log::Level { match self.level() { Level::TRACE => log::Level::Trace, @@ -191,7 +191,8 @@ impl VerbosityLevel { } #[inline] - fn level_value(level: Level) -> i8 { + #[must_use] + const fn level_value(level: Level) -> i8 { match level { Level::TRACE => 4, Level::DEBUG => 3, @@ -274,3 +275,9 @@ impl std::fmt::Debug for VerbosityLevel { write!(f, "{:?}", self.level()) } } + +impl Default for VerbosityLevel { + fn default() -> Self { + Self::INFO + } +} -- 2.40.1 From 476efb85c8bd5c9e9927c96d2f9a247163e5c865 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 18:04:47 +0200 Subject: [PATCH 43/66] refactor(cli): apply all clippy findings --- members/libpt-cli/examples/repl.rs | 4 +-- members/libpt-cli/src/args.rs | 3 -- members/libpt-cli/src/printing.rs | 10 +++++-- members/libpt-cli/src/repl/default.rs | 43 +++++++++------------------ members/libpt-cli/src/repl/error.rs | 4 ++- members/libpt-cli/src/repl/mod.rs | 14 +++++---- 6 files changed, 35 insertions(+), 43 deletions(-) diff --git a/members/libpt-cli/examples/repl.rs b/members/libpt-cli/examples/repl.rs index 34bf4fa..28caf93 100644 --- a/members/libpt-cli/examples/repl.rs +++ b/members/libpt-cli/examples/repl.rs @@ -48,7 +48,7 @@ fn main() -> anyhow::Result<()> { Err(e) => { // if the user requested the help, print in blue, otherwise in red as it's just an // error - if let libpt_cli::repl::error::ReplError::Parsing(e) = &e { + if let libpt_cli::repl::error::Error::Parsing(e) = &e { if e.kind() == clap::error::ErrorKind::DisplayHelp { println!("{}", style(e).cyan()); continue; @@ -76,7 +76,7 @@ fn main() -> anyhow::Result<()> { if !fancy { println!("{}", text.join(" ")) } else { - printing::blockprint(text.join(" "), console::Color::Cyan) + printing::blockprint(&text.join(" "), console::Color::Cyan) } } } diff --git a/members/libpt-cli/src/args.rs b/members/libpt-cli/src/args.rs index 9e990f4..34dd0fa 100644 --- a/members/libpt-cli/src/args.rs +++ b/members/libpt-cli/src/args.rs @@ -80,7 +80,6 @@ Author: {author-with-newline} /// # use libpt_cli::args::VerbosityLevel; /// use libpt_log::Level; /// # use clap::Parser; -/// use log; /// /// # #[derive(Parser, Debug)] /// # pub struct Opts { @@ -94,8 +93,6 @@ Author: {author-with-newline} /// // Level might be None if the user wants no output at all. /// // for the 'tracing' level: /// let level: Level = opts.verbose.level(); -/// // for the 'log' level: -/// let llevel: log::Level = opts.verbose.level_for_log_crate(); /// } /// ``` #[derive(Parser, Clone, PartialEq, Eq, Hash)] diff --git a/members/libpt-cli/src/printing.rs b/members/libpt-cli/src/printing.rs index dff8192..6cf3c01 100644 --- a/members/libpt-cli/src/printing.rs +++ b/members/libpt-cli/src/printing.rs @@ -45,10 +45,11 @@ use console::{style, Color}; /// use libpt_cli::console::Color; /// use libpt_cli::printing::blockprint; /// # fn main() { -/// blockprint("Hello world!".to_string(), Color::Blue); +/// blockprint("Hello world!", Color::Blue); /// # } /// ``` #[inline] +#[allow(clippy::needless_pass_by_value)] // we just take an impl, using a &impl is much less ergonomic pub fn blockprint(content: impl ToString, color: Color) { println!("{}", blockfmt(content, color)); } @@ -66,11 +67,12 @@ pub fn blockprint(content: impl ToString, color: Color) { /// use libpt_cli::console::Color; /// use libpt_cli::printing::blockfmt; /// # fn main() { -/// let formatted_content = blockfmt("Hello world!".to_string(), Color::Blue); +/// let formatted_content = blockfmt("Hello world!", Color::Blue); /// println!("{}", formatted_content); /// # } /// ``` #[inline] +#[allow(clippy::needless_pass_by_value)] // we just take an impl, using a &impl is much less ergonomic pub fn blockfmt(content: impl ToString, color: Color) -> String { blockfmt_advanced( content, @@ -98,7 +100,7 @@ pub fn blockfmt(content: impl ToString, color: Color) -> String { /// println!( /// "{}", /// blockfmt_advanced( -/// "Hello world!".to_string(), +/// "Hello world!", /// Some(Color::Blue), /// presets::UTF8_FULL, /// ContentArrangement::DynamicFullWidth, @@ -120,6 +122,8 @@ pub fn blockfmt(content: impl ToString, color: Color) -> String { /// - `preset`: The preset style for the border /// - `arrangement`: The arrangement of the the border (e.g., stretch to sides, wrap around ) /// - `alignment`: The alignment of the content within the cells (e.g., left, center, right) +#[allow(clippy::missing_panics_doc)] // we add a row then unwrap it, no panic should be possible +#[allow(clippy::needless_pass_by_value)] // we just take an impl, using a &impl is much less ergonomic pub fn blockfmt_advanced( content: impl ToString, color: Option, diff --git a/members/libpt-cli/src/repl/default.rs b/members/libpt-cli/src/repl/default.rs index 28b249f..ad22db9 100644 --- a/members/libpt-cli/src/repl/default.rs +++ b/members/libpt-cli/src/repl/default.rs @@ -56,11 +56,10 @@ use libpt_log::trace; #[embed_doc_image("repl_screenshot", "data/media/repl.png")] #[derive(Parser)] #[command(multicall = true, help_template = REPL_HELP_TEMPLATE)] +#[allow(clippy::module_name_repetitions)] // we can't just name it `Default`, that's part of std pub struct DefaultRepl where - C: Debug, - C: Subcommand, - C: strum::IntoEnumIterator, + C: Debug + Subcommand + strum::IntoEnumIterator, { /// the command you want to execute, along with its arguments #[command(subcommand)] @@ -81,18 +80,14 @@ where #[derive(Debug, Clone, Copy, Hash, Eq, PartialEq, PartialOrd, Ord)] struct DefaultReplCompletion where - C: Debug, - C: Subcommand, - C: strum::IntoEnumIterator, + C: Debug + Subcommand + strum::IntoEnumIterator, { commands: std::marker::PhantomData, } impl Repl for DefaultRepl where - C: Debug, - C: Subcommand, - C: strum::IntoEnumIterator, + C: Debug + Subcommand + strum::IntoEnumIterator, { fn new() -> Self { Self { @@ -106,7 +101,7 @@ where fn command(&self) -> &Option { &self.command } - fn step(&mut self) -> Result<(), super::error::ReplError> { + fn step(&mut self) -> Result<(), super::error::Error> { self.buf.clear(); // NOTE: display::Input requires some kind of lifetime that would be a bother to store in @@ -139,9 +134,7 @@ where impl Default for DefaultRepl where - C: Debug, - C: Subcommand, - C: strum::IntoEnumIterator, + C: Debug + Subcommand + strum::IntoEnumIterator, { fn default() -> Self { Self::new() @@ -150,9 +143,7 @@ where impl Debug for DefaultRepl where - C: Debug, - C: Subcommand, - C: strum::IntoEnumIterator, + C: Debug + Subcommand + strum::IntoEnumIterator, { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { f.debug_struct("DefaultRepl") @@ -167,16 +158,15 @@ where impl DefaultReplCompletion where - C: Debug, - C: Subcommand, - C: strum::IntoEnumIterator, + C: Debug + Subcommand + strum::IntoEnumIterator, { - pub fn new() -> Self { + /// Make a new [`DefaultReplCompletion`] for the type `C` + pub const fn new() -> Self { Self { commands: std::marker::PhantomData::, } } - fn commands(&self) -> Vec { + fn commands() -> Vec { let mut buf = Vec::new(); // every crate has the help command, but it is not part of the enum buf.push("help".to_string()); @@ -199,9 +189,7 @@ where impl Default for DefaultReplCompletion where - C: Debug, - C: Subcommand, - C: strum::IntoEnumIterator, + C: Debug + Subcommand + strum::IntoEnumIterator, { fn default() -> Self { Self::new() @@ -210,14 +198,11 @@ where impl Completion for DefaultReplCompletion where - C: Debug, - C: Subcommand, - C: strum::IntoEnumIterator, + C: Debug + Subcommand + strum::IntoEnumIterator, { /// Simple completion implementation based on substring fn get(&self, input: &str) -> Option { - let matches = self - .commands() + let matches = Self::commands() .into_iter() .filter(|option| option.starts_with(input)) .collect::>(); diff --git a/members/libpt-cli/src/repl/error.rs b/members/libpt-cli/src/repl/error.rs index f229e7e..3cb9546 100644 --- a/members/libpt-cli/src/repl/error.rs +++ b/members/libpt-cli/src/repl/error.rs @@ -3,9 +3,11 @@ use thiserror::Error; #[derive(Error, Debug)] -pub enum ReplError { +pub enum Error { #[error(transparent)] Parsing(#[from] clap::Error), #[error(transparent)] Input(#[from] dialoguer::Error), + #[error(transparent)] + Other(#[from] anyhow::Error), } diff --git a/members/libpt-cli/src/repl/mod.rs b/members/libpt-cli/src/repl/mod.rs index cea67dc..f4e9cae 100644 --- a/members/libpt-cli/src/repl/mod.rs +++ b/members/libpt-cli/src/repl/mod.rs @@ -14,7 +14,7 @@ use std::fmt::Debug; pub mod error; -use error::ReplError; +use error::Error; mod default; pub use default::*; @@ -25,9 +25,7 @@ use clap::{Parser, Subcommand}; /// Unless you want to implement custom features (not just commands), just use [`DefaultRepl`]. pub trait Repl: Parser + Debug where - C: Debug, - C: Subcommand, - C: strum::IntoEnumIterator, + C: Debug + Subcommand + strum::IntoEnumIterator, { /// create a new repl fn new() -> Self; @@ -40,5 +38,11 @@ where /// This should be used at the start of your loop. /// /// Note that the help menu is an Error: [`clap::error::ErrorKind::DisplayHelp`] - fn step(&mut self) -> Result<(), ReplError>; + /// + /// # Errors + /// + /// * [`Error::Input`] – [dialoguer] User Input had some kind of I/O Error + /// * [`Error::Parsing`] – [clap] could not parse the user input, or user requested help + /// * [`Error::Other`] – Any other error with [anyhow], [`DefaultRepl`] does not use this but custom implementations might + fn step(&mut self) -> Result<(), Error>; } -- 2.40.1 From 9b94c2523460046d84680c6852c5b3a45552d3f7 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 18:22:41 +0200 Subject: [PATCH 44/66] refactor(log): apply clippy findings --- members/libpt-log/src/lib.rs | 57 +++++++++++++++++++----------------- 1 file changed, 30 insertions(+), 27 deletions(-) diff --git a/members/libpt-log/src/lib.rs b/members/libpt-log/src/lib.rs index 47493dc..9248e98 100644 --- a/members/libpt-log/src/lib.rs +++ b/members/libpt-log/src/lib.rs @@ -13,6 +13,7 @@ //! - [`tracing`]: base logging crate //! - [`tracing_appender`]: Used to log to files //! - [`tracing_subscriber`]: Used to do actual logging, formatting, to stdout +#![warn(clippy::pedantic, clippy::style, clippy::nursery)] use std::{ fmt, @@ -21,7 +22,7 @@ use std::{ }; pub mod error; -use error::*; +use error::Error; pub use tracing; pub use tracing::{debug, error, info, trace, warn, Level}; @@ -56,6 +57,8 @@ static INITIALIZED: AtomicBool = AtomicBool::new(false); /// /// ``` #[derive(PartialEq, Eq, PartialOrd, Ord, Hash, Debug)] +#[allow(clippy::struct_excessive_bools)] // it's just true/false values, not states, and I don't + // need to reinvent the wheel pub struct LoggerBuilder { /// create and log to logfiles log_to_file: bool, @@ -179,7 +182,7 @@ impl LoggerBuilder { tracing::subscriber::set_global_default(subscriber)?; } (true, false, false, _) => { - let file_appender = tracing_appender::rolling::daily(self.log_dir.clone(), "log"); + let file_appender = tracing_appender::rolling::daily(self.log_dir, "log"); let (file_writer, _guard) = tracing_appender::non_blocking(file_appender); let subscriber = subscriber.with_writer(file_writer).without_time().finish(); tracing::subscriber::set_global_default(subscriber)?; @@ -214,16 +217,18 @@ impl LoggerBuilder { } /// enable or disable logging to and creating of logfiles - pub fn log_to_file(mut self, log_to_file: bool) -> Self { + #[must_use] + pub const fn log_to_file(mut self, log_to_file: bool) -> Self { self.log_to_file = log_to_file; self } /// set a directory where logfiles would be created in /// - /// Enable or disable creation and logging to logfiles with [log_to_file](Self::log_to_file). + /// Enable or disable creation and logging to logfiles with [`log_to_file`](Self::log_to_file). /// - /// The default logdir is [DEFAULT_LOG_DIR]. + /// The default logdir is [`DEFAULT_LOG_DIR`]. + #[must_use] pub fn log_dir(mut self, log_dir: PathBuf) -> Self { self.log_dir = log_dir; self @@ -235,67 +240,64 @@ impl LoggerBuilder { /// are displayed by a program that does not interpret them. /// /// Keeping ANSI control sequences enabled has the disadvantage of added colors for the logs. - pub fn ansi(mut self, ansi: bool) -> Self { + #[must_use] + pub const fn ansiconst(mut self, ansi: bool) -> Self { self.ansi = ansi; self } /// when making a log, display the source file in which a log was crated in - pub fn display_filename(mut self, display_filename: bool) -> Self { + #[must_use] + pub const fn display_filename(mut self, display_filename: bool) -> Self { self.display_filename = display_filename; self } /// when making a log, display the log level of the message - pub fn display_level(mut self, display_level: bool) -> Self { + #[must_use] + pub const fn display_level(mut self, display_level: bool) -> Self { self.display_level = display_level; self } /// show target context - pub fn display_target(mut self, display_target: bool) -> Self { + #[must_use] + pub const fn display_target(mut self, display_target: bool) -> Self { self.display_target = display_target; self } - /// set the maximum verbosity level. - pub fn max_level(mut self, max_level: Level) -> Self { - self.max_level = max_level; - self - } - /// show the id of the thread that created this message - pub fn display_thread_ids(mut self, display_thread_ids: bool) -> Self { + #[must_use] + pub const fn display_thread_ids(mut self, display_thread_ids: bool) -> Self { self.display_thread_ids = display_thread_ids; self } /// show the name of the thread that created this message - pub fn display_thread_names(mut self, display_thread_names: bool) -> Self { + #[must_use] + pub const fn display_thread_names(mut self, display_thread_names: bool) -> Self { self.display_thread_names = display_thread_names; self } /// show which line in the source file produces a log - pub fn display_line_number(mut self, display_line_number: bool) -> Self { + #[must_use] + pub const fn display_line_number(mut self, display_line_number: bool) -> Self { self.display_line_number = display_line_number; self } /// splits a log over multiple lines, looks like a python traceback - pub fn pretty(mut self, pretty: bool) -> Self { + #[must_use] + pub const fn pretty(mut self, pretty: bool) -> Self { self.pretty = pretty; self } - /// show a timestamp describing when the log was created - pub fn show_time(mut self, show_time: bool) -> Self { - self.show_time = show_time; - self - } - /// show timestamps as uptime (duration since the logger was initialized) - pub fn uptime(mut self, uptime: bool) -> Self { + #[must_use] + pub const fn uptime(mut self, uptime: bool) -> Self { self.uptime = uptime; self } @@ -365,7 +367,8 @@ pub struct Logger; /// ## Main implementation impl Logger { - /// Get a new [LoggerBuilder] + /// Get a new [`LoggerBuilder`] + #[must_use] pub fn builder() -> LoggerBuilder { LoggerBuilder::default() } -- 2.40.1 From 73f935d9b2a64dc9338cda431f62d29c2fe5e2ca Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 18:23:09 +0200 Subject: [PATCH 45/66] refactor(log): remove depereciated Logger::build() --- members/libpt-log/src/lib.rs | 161 ----------------------------------- 1 file changed, 161 deletions(-) diff --git a/members/libpt-log/src/lib.rs b/members/libpt-log/src/lib.rs index 9248e98..0dabf7e 100644 --- a/members/libpt-log/src/lib.rs +++ b/members/libpt-log/src/lib.rs @@ -373,167 +373,6 @@ impl Logger { LoggerBuilder::default() } - /// ## initializes the logger - /// - /// Will enable the logger to be used. - /// - /// Assumes some defaults, use [`init_customized`](Self::init_customized) for more control - #[deprecated(since = "0.4.1", note = "use Logger::builder() instead")] - pub fn build(log_dir: Option, max_level: Option, uptime: bool) -> Result { - #[allow(deprecated)] - Self::build_customized( - log_dir.is_some(), - log_dir.unwrap_or(PathBuf::from(DEFAULT_LOG_DIR)), - true, - false, - true, - false, - max_level.unwrap_or(DEFAULT_LOG_LEVEL), - false, - false, - false, - false, - true, - uptime, - ) - } - - /// ## initializes the logger - /// - /// Will enable the logger to be used. This is a version that shows less information, - /// useful in cases with only one sender to the logging framework. - /// - /// Assumes some defaults, use [`init_customized`](Self::init_customized) for more control - #[deprecated(since = "0.4.1", note = "use Logger::builder() instead")] - pub fn build_mini(max_level: Option) -> Result { - #[allow(deprecated)] - Self::build_customized( - false, - PathBuf::from(DEFAULT_LOG_DIR), - true, - false, - true, - false, - max_level.unwrap_or(DEFAULT_LOG_LEVEL), - false, - false, - false, - false, - false, - false, - ) - } - - /// ## initializes the logger - /// - /// Will enable the logger to be used. - #[deprecated(since = "0.4.1", note = "use Logger::builder() instead")] - #[allow(clippy::too_many_arguments)] - pub fn build_customized( - log_to_file: bool, - log_dir: PathBuf, - ansi: bool, - display_filename: bool, - display_level: bool, - display_target: bool, - max_level: Level, - display_thread_ids: bool, - display_thread_names: bool, - display_line_number: bool, - pretty: bool, - show_time: bool, - uptime: bool, // uptime instead of system time - ) -> Result { - // only init if no init has been performed yet - if INITIALIZED.load(Ordering::Relaxed) { - warn!("trying to reinitialize the logger, ignoring"); - bail!(Error::Usage("logging is already initialized".to_string())); - } - let subscriber = tracing_subscriber::fmt::Subscriber::builder() - .with_level(display_level) - .with_max_level(max_level) - .with_ansi(ansi) - .with_target(display_target) - .with_file(display_filename) - .with_thread_ids(display_thread_ids) - .with_line_number(display_line_number) - .with_thread_names(display_thread_names) - .with_span_events(FmtSpan::FULL); - // I know this is hacky, but I couldn't get it any other way. I couldn't even find a - // project that could do it any other way. You can't apply one after another, because the - // type is changed every time. When using Box, some methods complain about - // not being in trait bounds. - // TODO: somehow find a better solution for this - match (log_to_file, show_time, pretty, uptime) { - (true, true, true, true) => { - let subscriber = subscriber - .with_writer(new_file_appender(log_dir)) - .with_timer(time::uptime()) - .pretty() - .finish(); - tracing::subscriber::set_global_default(subscriber)?; - } - (true, true, true, false) => { - let subscriber = subscriber - .with_writer(new_file_appender(log_dir)) - .pretty() - .finish(); - tracing::subscriber::set_global_default(subscriber)?; - } - (true, false, true, _) => { - let subscriber = subscriber - .with_writer(new_file_appender(log_dir)) - .without_time() - .pretty() - .finish(); - tracing::subscriber::set_global_default(subscriber)?; - } - (true, true, false, true) => { - let subscriber = subscriber - .with_writer(new_file_appender(log_dir)) - .with_timer(time::uptime()) - .finish(); - tracing::subscriber::set_global_default(subscriber)?; - } - (true, true, false, false) => { - let subscriber = subscriber.with_writer(new_file_appender(log_dir)).finish(); - tracing::subscriber::set_global_default(subscriber)?; - } - (true, false, false, _) => { - let file_appender = tracing_appender::rolling::daily(log_dir.clone(), "log"); - let (file_writer, _guard) = tracing_appender::non_blocking(file_appender); - let subscriber = subscriber.with_writer(file_writer).without_time().finish(); - tracing::subscriber::set_global_default(subscriber)?; - } - (false, true, true, true) => { - let subscriber = subscriber.pretty().with_timer(time::uptime()).finish(); - tracing::subscriber::set_global_default(subscriber)?; - } - (false, true, true, false) => { - let subscriber = subscriber.pretty().with_timer(time::uptime()).finish(); - tracing::subscriber::set_global_default(subscriber)?; - } - (false, false, true, _) => { - let subscriber = subscriber.without_time().pretty().finish(); - tracing::subscriber::set_global_default(subscriber)?; - } - (false, true, false, true) => { - let subscriber = subscriber.with_timer(time::uptime()).finish(); - tracing::subscriber::set_global_default(subscriber)?; - } - (false, true, false, false) => { - let subscriber = subscriber.finish(); - tracing::subscriber::set_global_default(subscriber)?; - } - (false, false, false, _) => { - let subscriber = subscriber.without_time().finish(); - tracing::subscriber::set_global_default(subscriber)?; - } - } - INITIALIZED.store(true, Ordering::Relaxed); - Ok(Logger {}) - } - /// ## logging at [`Level::ERROR`] pub fn error(&self, printable: T) where -- 2.40.1 From b2bf00db46c86f90f5776ef7ceb271170e149874 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 18:25:50 +0200 Subject: [PATCH 46/66] refactor(log): apply more clippy findings --- members/libpt-cli/examples/cli.rs | 15 +++++++++++---- members/libpt-cli/examples/repl.rs | 15 +++++++++++---- members/libpt-log/src/lib.rs | 12 ++++++------ 3 files changed, 28 insertions(+), 14 deletions(-) diff --git a/members/libpt-cli/examples/cli.rs b/members/libpt-cli/examples/cli.rs index 91fdcad..345a365 100644 --- a/members/libpt-cli/examples/cli.rs +++ b/members/libpt-cli/examples/cli.rs @@ -23,10 +23,17 @@ struct Cli { fn main() { let cli = Cli::parse(); - let _logger = Logger::builder() - .max_level(cli.verbosity.level()) - .show_time(false) - .build(); + let _logger = { + let mut this = { + let mut this = Logger::builder(); + let max_level = cli.verbosity.level(); + this.max_level = max_level; + this + }; + this.show_time = false; + this + } + .build(); debug!("logger initialized with level: {}", cli.verbosity.level()); diff --git a/members/libpt-cli/examples/repl.rs b/members/libpt-cli/examples/repl.rs index 28caf93..dc8c357 100644 --- a/members/libpt-cli/examples/repl.rs +++ b/members/libpt-cli/examples/repl.rs @@ -31,10 +31,17 @@ enum ReplCommand { fn main() -> anyhow::Result<()> { // You would normally make a proper cli interface with clap before entering the repl. This is // omitted here for brevity - let _logger = Logger::builder() - .show_time(false) - .max_level(Level::INFO) - .build()?; + let _logger = { + let mut this = { + let mut this = Logger::builder(); + this.show_time = false; + this + }; + let max_level = Level::INFO; + this.max_level = max_level; + this + } + .build()?; // the compiler can infer that we want to use the ReplCommand enum. let mut repl = DefaultRepl::::default(); diff --git a/members/libpt-log/src/lib.rs b/members/libpt-log/src/lib.rs index 0dabf7e..8a4a32f 100644 --- a/members/libpt-log/src/lib.rs +++ b/members/libpt-log/src/lib.rs @@ -378,35 +378,35 @@ impl Logger { where T: fmt::Display, { - error!("{}", printable) + error!("{}", printable); } /// ## logging at [`Level::WARN`] pub fn warn(&self, printable: T) where T: fmt::Display, { - warn!("{}", printable) + warn!("{}", printable); } /// ## logging at [`Level::INFO`] pub fn info(&self, printable: T) where T: fmt::Display, { - info!("{}", printable) + info!("{}", printable); } /// ## logging at [`Level::DEBUG`] pub fn debug(&self, printable: T) where T: fmt::Display, { - debug!("{}", printable) + debug!("{}", printable); } /// ## logging at [`Level::TRACE`] pub fn trace(&self, printable: T) where T: fmt::Display, { - trace!("{}", printable) + trace!("{}", printable); } } @@ -430,6 +430,6 @@ impl Default for Logger { } fn new_file_appender(log_dir: PathBuf) -> NonBlocking { - let file_appender = tracing_appender::rolling::daily(log_dir.clone(), "log"); + let file_appender = tracing_appender::rolling::daily(log_dir, "log"); tracing_appender::non_blocking(file_appender).0 } -- 2.40.1 From edb6342b0ccf0f8a82e08457e81dcbd99e044588 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 18:31:30 +0200 Subject: [PATCH 47/66] docs(log): add defaults to builder methods #82 --- members/libpt-log/src/lib.rs | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/members/libpt-log/src/lib.rs b/members/libpt-log/src/lib.rs index 8a4a32f..7978fb3 100644 --- a/members/libpt-log/src/lib.rs +++ b/members/libpt-log/src/lib.rs @@ -139,11 +139,11 @@ impl LoggerBuilder { .with_line_number(self.display_line_number) .with_thread_names(self.display_thread_names) .with_span_events(FmtSpan::FULL); + // HACK: somehow find a better solution for this // I know this is hacky, but I couldn't get it any other way. I couldn't even find a // project that could do it any other way. You can't apply one after another, because the // type is changed every time. When using `Box`, some methods complain about // not being in trait bounds. - // TODO: somehow find a better solution for this match (self.log_to_file, self.show_time, self.pretty, self.uptime) { (true, true, true, true) => { let subscriber = subscriber @@ -217,6 +217,8 @@ impl LoggerBuilder { } /// enable or disable logging to and creating of logfiles + /// + /// Default: false #[must_use] pub const fn log_to_file(mut self, log_to_file: bool) -> Self { self.log_to_file = log_to_file; @@ -227,7 +229,7 @@ impl LoggerBuilder { /// /// Enable or disable creation and logging to logfiles with [`log_to_file`](Self::log_to_file). /// - /// The default logdir is [`DEFAULT_LOG_DIR`]. + /// Default: [`DEFAULT_LOG_DIR`] (/dev/null) #[must_use] pub fn log_dir(mut self, log_dir: PathBuf) -> Self { self.log_dir = log_dir; @@ -240,6 +242,8 @@ impl LoggerBuilder { /// are displayed by a program that does not interpret them. /// /// Keeping ANSI control sequences enabled has the disadvantage of added colors for the logs. + /// + /// Default: true #[must_use] pub const fn ansiconst(mut self, ansi: bool) -> Self { self.ansi = ansi; @@ -247,6 +251,8 @@ impl LoggerBuilder { } /// when making a log, display the source file in which a log was crated in + /// + /// Default: false #[must_use] pub const fn display_filename(mut self, display_filename: bool) -> Self { self.display_filename = display_filename; @@ -254,6 +260,8 @@ impl LoggerBuilder { } /// when making a log, display the log level of the message + /// + /// Default: true #[must_use] pub const fn display_level(mut self, display_level: bool) -> Self { self.display_level = display_level; @@ -261,6 +269,8 @@ impl LoggerBuilder { } /// show target context + /// + /// Default: false #[must_use] pub const fn display_target(mut self, display_target: bool) -> Self { self.display_target = display_target; @@ -268,6 +278,8 @@ impl LoggerBuilder { } /// show the id of the thread that created this message + /// + /// Default: false #[must_use] pub const fn display_thread_ids(mut self, display_thread_ids: bool) -> Self { self.display_thread_ids = display_thread_ids; @@ -275,6 +287,8 @@ impl LoggerBuilder { } /// show the name of the thread that created this message + /// + /// Default: false #[must_use] pub const fn display_thread_names(mut self, display_thread_names: bool) -> Self { self.display_thread_names = display_thread_names; @@ -282,6 +296,8 @@ impl LoggerBuilder { } /// show which line in the source file produces a log + /// + /// Default: false #[must_use] pub const fn display_line_number(mut self, display_line_number: bool) -> Self { self.display_line_number = display_line_number; @@ -289,6 +305,8 @@ impl LoggerBuilder { } /// splits a log over multiple lines, looks like a python traceback + /// + /// Default: false #[must_use] pub const fn pretty(mut self, pretty: bool) -> Self { self.pretty = pretty; @@ -296,6 +314,8 @@ impl LoggerBuilder { } /// show timestamps as uptime (duration since the logger was initialized) + /// + /// Default: false #[must_use] pub const fn uptime(mut self, uptime: bool) -> Self { self.uptime = uptime; -- 2.40.1 From 87dc2871d7de982d92a10ff1ddd182754df21e61 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 18:33:31 +0200 Subject: [PATCH 48/66] refactor(log)!: rename ansiconst -> ansi idk why it was called that, seems dumb, better break now than later in case I actually get users --- members/libpt-log/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/members/libpt-log/src/lib.rs b/members/libpt-log/src/lib.rs index 7978fb3..6ea8f94 100644 --- a/members/libpt-log/src/lib.rs +++ b/members/libpt-log/src/lib.rs @@ -245,7 +245,7 @@ impl LoggerBuilder { /// /// Default: true #[must_use] - pub const fn ansiconst(mut self, ansi: bool) -> Self { + pub const fn ansi(mut self, ansi: bool) -> Self { self.ansi = ansi; self } -- 2.40.1 From b382b3e5019e4d105929d96a50861e5f9e8a65e8 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 19:29:20 +0200 Subject: [PATCH 49/66] refactor(cli): cli example was weird --- members/libpt-cli/examples/cli.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/members/libpt-cli/examples/cli.rs b/members/libpt-cli/examples/cli.rs index 345a365..7dc4509 100644 --- a/members/libpt-cli/examples/cli.rs +++ b/members/libpt-cli/examples/cli.rs @@ -23,17 +23,7 @@ struct Cli { fn main() { let cli = Cli::parse(); - let _logger = { - let mut this = { - let mut this = Logger::builder(); - let max_level = cli.verbosity.level(); - this.max_level = max_level; - this - }; - this.show_time = false; - this - } - .build(); + let _logger = Logger::builder().set_level(cli.verbosity.level()).build(); debug!("logger initialized with level: {}", cli.verbosity.level()); -- 2.40.1 From 673eb691e99629543f3b196908a5785b095d6e68 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 19:34:46 +0200 Subject: [PATCH 50/66] fix(cli): clap count parser only works with u8, so revert the change to i8 back --- members/libpt-cli/src/args.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/members/libpt-cli/src/args.rs b/members/libpt-cli/src/args.rs index 34dd0fa..90edbf5 100644 --- a/members/libpt-cli/src/args.rs +++ b/members/libpt-cli/src/args.rs @@ -101,12 +101,12 @@ pub struct VerbosityLevel { #[arg( long, short = 'v', - action = clap::ArgAction::Count, + action = clap::ArgAction::Count, // NOTE: this forces u8 type for some reason global = true, // help = L::verbose_help(), // long_help = L::verbose_long_help(), )] - verbose: i8, + verbose: u8, /// make the output less verbose /// @@ -118,7 +118,7 @@ pub struct VerbosityLevel { global = true, conflicts_with = "verbose", )] - quiet: i8, + quiet: u8, } impl VerbosityLevel { @@ -131,7 +131,7 @@ impl VerbosityLevel { } #[inline] #[must_use] - fn value(&self) -> i8 { + fn value(&self) -> u8 { Self::level_value(Level::INFO) .saturating_sub((self.quiet).min(10)) .saturating_add((self.verbose).min(10)) @@ -189,7 +189,7 @@ impl VerbosityLevel { #[inline] #[must_use] - const fn level_value(level: Level) -> i8 { + const fn level_value(level: Level) -> u8 { match level { Level::TRACE => 4, Level::DEBUG => 3, -- 2.40.1 From 1c92f95901e499e14bd3940485d3dbe17ed327cc Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 19:37:13 +0200 Subject: [PATCH 51/66] docs(log): add an example for using a logfile --- members/libpt-log/examples/logfile.rs | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 members/libpt-log/examples/logfile.rs diff --git a/members/libpt-log/examples/logfile.rs b/members/libpt-log/examples/logfile.rs new file mode 100644 index 0000000..fadc7bc --- /dev/null +++ b/members/libpt-log/examples/logfile.rs @@ -0,0 +1,11 @@ +use libpt_log::Logger; +use tracing::info; + +fn main() { + let _logger = Logger::builder() + .log_to_file(true) + .log_dir("/tmp/llll".into()) + .build() + .unwrap(); + info!("foo bar qux"); +} -- 2.40.1 From 729c4e3a4e947a72f104e60385260adcd9905936 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 19:37:56 +0200 Subject: [PATCH 52/66] fix(log): logfile works again #90 --- members/libpt-log/src/lib.rs | 41 +++++++++++++++++++++++------------- 1 file changed, 26 insertions(+), 15 deletions(-) diff --git a/members/libpt-log/src/lib.rs b/members/libpt-log/src/lib.rs index 6ea8f94..74fca05 100644 --- a/members/libpt-log/src/lib.rs +++ b/members/libpt-log/src/lib.rs @@ -6,8 +6,10 @@ //! For the library version, only the basic [`tracing`] is used, so that it is possible for //! the end user to use the [`tracing`] frontend they desire. //! -//! I did however decide to create a [`Logger`] struct. This struct is mainly intended to be used -//! with the python module of [`pt`](../libpt/index.html), but is still just as usable in other contexts. +//! I did decide to create a [`Logger`] struct. This struct is mainly intended to be used with the +//! python module of [`pt`](../libpt/index.html), but is still just as usable in other contexts. +//! You can use this struct when use of the macros is not possible, but the macros should generally +//! be preferred. //! //! ## Technologies used for logging: //! - [`tracing`]: base logging crate @@ -24,6 +26,9 @@ use std::{ pub mod error; use error::Error; +/// This is the magic dependency where the cool stuff happens +/// +/// I'm just repackaging it a little to make it more ergonomic pub use tracing; pub use tracing::{debug, error, info, trace, warn, Level}; use tracing_appender::{self, non_blocking::NonBlocking}; @@ -182,9 +187,10 @@ impl LoggerBuilder { tracing::subscriber::set_global_default(subscriber)?; } (true, false, false, _) => { - let file_appender = tracing_appender::rolling::daily(self.log_dir, "log"); - let (file_writer, _guard) = tracing_appender::non_blocking(file_appender); - let subscriber = subscriber.with_writer(file_writer).without_time().finish(); + let subscriber = subscriber + .with_writer(new_file_appender(self.log_dir)) + .without_time() + .finish(); tracing::subscriber::set_global_default(subscriber)?; } (false, true, true, true) => { @@ -218,6 +224,8 @@ impl LoggerBuilder { /// enable or disable logging to and creating of logfiles /// + /// If you want to log to a file, don't forget to set [`Self::log_dir`]! + /// /// Default: false #[must_use] pub const fn log_to_file(mut self, log_to_file: bool) -> Self { @@ -355,19 +363,23 @@ impl Default for LoggerBuilder { /// /// ## Levels /// -/// TODO: add levels desc and ascii art +/// * [ERROR](Level::ERROR) – Something broke +/// * [WARN](Level::WARN) – Something is bad +/// * [INFO](Level::INFO) – Useful information for users +/// * [DEBUG](Level::DEBUG) – Useful information for developers +/// * [TRACE](Level::TRACE) – Very verbose information for developers (often for libraries) /// /// ## Usage /// /// You don't need to use the [Logger] struct, it's better to use the macros instead: /// -/// * `error!` -/// * `warn!` -/// * `info!` -/// * `debug!` -/// * `trace!` +/// * [`error!`] +/// * [`warn!`] +/// * [`info!`] +/// * [`debug!`] +/// * [`trace!`] /// -/// You can however use the [Logger] struct in cases where usage of a macro is bad or +/// You can however use the [Logger] struct in cases where usage of a macro is impossible or /// you are somehow working with multiple loggers. The macros offer additional functionalities, /// suck as full `format!` support and context, see [`tracing`], which we use as backend. /// @@ -449,7 +461,6 @@ impl Default for Logger { } } -fn new_file_appender(log_dir: PathBuf) -> NonBlocking { - let file_appender = tracing_appender::rolling::daily(log_dir, "log"); - tracing_appender::non_blocking(file_appender).0 +fn new_file_appender(log_dir: PathBuf) -> tracing_appender::rolling::RollingFileAppender { + tracing_appender::rolling::daily(log_dir, format!("{}.log", env!("CARGO_CRATE_NAME"))) } -- 2.40.1 From 8226d74fb91de41deffceab0bdc207fb6ecc5dca Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 19:39:33 +0200 Subject: [PATCH 53/66] feat(log): apperently, it wasn't possible to set a min log level? --- members/libpt-log/src/lib.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/members/libpt-log/src/lib.rs b/members/libpt-log/src/lib.rs index 74fca05..e307ff7 100644 --- a/members/libpt-log/src/lib.rs +++ b/members/libpt-log/src/lib.rs @@ -329,6 +329,15 @@ impl LoggerBuilder { self.uptime = uptime; self } + + /// set the lowest loglevel to be displayed + /// + /// Default: [`Level::INFO`] + #[must_use] + pub const fn set_level(mut self, max_level: Level) -> Self { + self.max_level = max_level; + self + } } impl Default for LoggerBuilder { -- 2.40.1 From 087e6dad8c1d50ab768d2e25ded77018eb08e5b6 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 19:41:34 +0200 Subject: [PATCH 54/66] refactor(py)!: remove libpt-py #87 --- Cargo.toml | 15 +---- members/libpt-py/.gitignore | 72 --------------------- members/libpt-py/Cargo.toml | 31 --------- members/libpt-py/pyproject.toml | 16 ----- members/libpt-py/src/bintols/mod.rs | 76 ---------------------- members/libpt-py/src/core/mod.rs | 11 ---- members/libpt-py/src/core/printing.rs | 24 ------- members/libpt-py/src/lib.rs | 30 --------- members/libpt-py/src/log/mod.rs | 91 --------------------------- members/libpt-py/src/net/mod.rs | 0 10 files changed, 2 insertions(+), 364 deletions(-) delete mode 100644 members/libpt-py/.gitignore delete mode 100644 members/libpt-py/Cargo.toml delete mode 100644 members/libpt-py/pyproject.toml delete mode 100644 members/libpt-py/src/bintols/mod.rs delete mode 100644 members/libpt-py/src/core/mod.rs delete mode 100644 members/libpt-py/src/core/printing.rs delete mode 100644 members/libpt-py/src/lib.rs delete mode 100644 members/libpt-py/src/log/mod.rs delete mode 100644 members/libpt-py/src/net/mod.rs diff --git a/Cargo.toml b/Cargo.toml index c0277fa..1b9d809 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -1,12 +1,6 @@ [workspace] resolver = "2" -members = [ - ".", - "members/libpt-core", - "members/libpt-log", - "members/libpt-py", - "members/libpt-cli", -] +members = [".", "members/libpt-core", "members/libpt-log", "members/libpt-cli"] default-members = [".", "members/libpt-core"] [workspace.package] @@ -20,11 +14,7 @@ readme = "README.md" homepage = "https://git.cscherr.de/PlexSheep/pt" repository = "https://git.cscherr.de/PlexSheep/pt" keywords = ["library"] -categories = [ - "command-line-utilities", - "development-tools", - "development-tools::ffi", -] +categories = ["command-line-utilities", "development-tools"] [workspace.dependencies] anyhow = "1.0.79" @@ -55,7 +45,6 @@ full = ["default", "core", "log", "bintols"] log = ["dep:libpt-log"] bintols = ["dep:libpt-bintols", "log"] cli = ["dep:libpt-cli", "core", "log"] -# py = ["dep:libpt-py"] [lib] name = "libpt" diff --git a/members/libpt-py/.gitignore b/members/libpt-py/.gitignore deleted file mode 100644 index c8f0442..0000000 --- a/members/libpt-py/.gitignore +++ /dev/null @@ -1,72 +0,0 @@ -/target - -# Byte-compiled / optimized / DLL files -__pycache__/ -.pytest_cache/ -*.py[cod] - -# C extensions -*.so - -# Distribution / packaging -.Python -.venv/ -env/ -bin/ -build/ -develop-eggs/ -dist/ -eggs/ -lib/ -lib64/ -parts/ -sdist/ -var/ -include/ -man/ -venv/ -*.egg-info/ -.installed.cfg -*.egg - -# Installer logs -pip-log.txt -pip-delete-this-directory.txt -pip-selfcheck.json - -# Unit test / coverage reports -htmlcov/ -.tox/ -.coverage -.cache -nosetests.xml -coverage.xml - -# Translations -*.mo - -# Mr Developer -.mr.developer.cfg -.project -.pydevproject - -# Rope -.ropeproject - -# Django stuff: -*.log -*.pot - -.DS_Store - -# Sphinx documentation -docs/_build/ - -# PyCharm -.idea/ - -# VSCode -.vscode/ - -# Pyenv -.python-version diff --git a/members/libpt-py/Cargo.toml b/members/libpt-py/Cargo.toml deleted file mode 100644 index 8be932e..0000000 --- a/members/libpt-py/Cargo.toml +++ /dev/null @@ -1,31 +0,0 @@ -[package] -name = "libpt-py" -version.workspace = true -edition.workspace = true -authors.workspace = true -license.workspace = true -description.workspace = true -readme.workspace = true -homepage.workspace = true -repository.workspace = true -keywords.workspace = true -categories.workspace = true - -[package.metadata.maturin] -name = "libpt" - -# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html -[lib] -crate-type = ["cdylib", "rlib"] - -[dependencies] -libpt = { version = "0.5.0"} -pyo3 = { version = "0.19.0", features = ["full"] } -anyhow.workspace = true - -[features] -default = ["log", "core", "full"] -core = [] -full = ["default", "core", "log", "bintols"] -log = ["libpt/log"] -bintols = ["libpt/bintols", "log"] diff --git a/members/libpt-py/pyproject.toml b/members/libpt-py/pyproject.toml deleted file mode 100644 index 0b1145a..0000000 --- a/members/libpt-py/pyproject.toml +++ /dev/null @@ -1,16 +0,0 @@ -[build-system] -requires = ["maturin>=1.4,<2.0"] -build-backend = "maturin" - -[project] -name = "libpt" -requires-python = ">=3.8" -classifiers = [ - "Programming Language :: Rust", - "Programming Language :: Python :: Implementation :: CPython", - "Programming Language :: Python :: Implementation :: PyPy", -] -dynamic = ["version"] - -[tool.maturin] -features = ["pyo3/extension-module"] diff --git a/members/libpt-py/src/bintols/mod.rs b/members/libpt-py/src/bintols/mod.rs deleted file mode 100644 index 72f19be..0000000 --- a/members/libpt-py/src/bintols/mod.rs +++ /dev/null @@ -1,76 +0,0 @@ -use pyo3::prelude::*; - -use libpt::bintols as origin; - -mod split { - use libpt::bintols::split as origin; - use pyo3::prelude::*; - - #[pyfunction] - pub fn split_int(data: u128) -> Vec { - origin::unsigned_to_vec(data) - } - - /// implement a python module in Rust - pub fn submodule(py: Python, parent: &PyModule) -> PyResult<()> { - let module = PyModule::new(py, "split")?; - - module.add_function(wrap_pyfunction!(split_int, module)?)?; - - parent.add_submodule(module)?; - Ok(()) - } -} - -mod display { - use libpt::bintols::display as origin; - use pyo3::prelude::*; - - #[pyfunction] - pub fn bytes_to_bin(data: &[u8]) -> String { - origin::bytes_to_bin(data) - } - - #[pyfunction] - pub fn byte_bit_display(data: usize) -> String { - origin::byte_bit_display(data) - } - - #[pyfunction] - pub fn humanbytes(total: u128) -> String { - origin::humanbytes(total) - } - - /// implement a python module in Rust - pub fn submodule(py: Python, parent: &PyModule) -> PyResult<()> { - let module = PyModule::new(py, "display")?; - - module.add_function(wrap_pyfunction!(bytes_to_bin, module)?)?; - module.add_function(wrap_pyfunction!(byte_bit_display, module)?)?; - module.add_function(wrap_pyfunction!(humanbytes, module)?)?; - - parent.add_submodule(module)?; - Ok(()) - } -} - -/// implement a python module in Rust -pub fn submodule(py: Python, parent: &PyModule) -> PyResult<()> { - let module = PyModule::new(py, "bintols")?; - - // binary constants - module.add("KIBI", origin::KIBI)?; - module.add("MEBI", origin::MEBI)?; - module.add("GIBI", origin::GIBI)?; - module.add("TEBI", origin::TEBI)?; - module.add("PEBI", origin::PEBI)?; - module.add("EXBI", origin::EXBI)?; - module.add("ZEBI", origin::ZEBI)?; - module.add("YOBI", origin::YOBI)?; - - display::submodule(py, module)?; - split::submodule(py, module)?; - - parent.add_submodule(module)?; - Ok(()) -} diff --git a/members/libpt-py/src/core/mod.rs b/members/libpt-py/src/core/mod.rs deleted file mode 100644 index 901d86f..0000000 --- a/members/libpt-py/src/core/mod.rs +++ /dev/null @@ -1,11 +0,0 @@ -use pyo3::prelude::*; - -mod printing; - -/// implement a python module in Rust -pub fn submodule(py: Python, parent: &PyModule) -> PyResult<()> { - let module = PyModule::new(py, "core")?; - printing::submodule(py, module)?; - parent.add_submodule(module)?; - Ok(()) -} diff --git a/members/libpt-py/src/core/printing.rs b/members/libpt-py/src/core/printing.rs deleted file mode 100644 index 146451c..0000000 --- a/members/libpt-py/src/core/printing.rs +++ /dev/null @@ -1,24 +0,0 @@ -use pyo3::prelude::*; - -use libpt::core::printing as origin; - -/// Quickly get a one line visual divider -#[pyfunction] -pub fn divider() -> String { - origin::divider() -} - -/// Quickly print a one line visual divider -#[pyfunction] -pub fn print_divider() { - origin::print_divider() -} - -/// implement a python module in Rust -pub fn submodule(py: Python, parent: &PyModule) -> PyResult<()> { - let module = PyModule::new(py, "printing")?; - module.add_function(wrap_pyfunction!(divider, module)?)?; - module.add_function(wrap_pyfunction!(print_divider, module)?)?; - parent.add_submodule(module)?; - Ok(()) -} diff --git a/members/libpt-py/src/lib.rs b/members/libpt-py/src/lib.rs deleted file mode 100644 index 2d648a1..0000000 --- a/members/libpt-py/src/lib.rs +++ /dev/null @@ -1,30 +0,0 @@ -//! Python bindings for [`libpt`] - -#[cfg(feature = "bintols")] -mod bintols; -#[cfg(feature = "core")] -mod core; -#[cfg(feature = "log")] -mod log; - -use pyo3::prelude::*; - -/// return the version of libpt -#[pyfunction] -fn version() -> String { - env!("CARGO_PKG_VERSION").to_string() -} - -/// implement a python module in Rust -#[pymodule] -#[pyo3(name = "libpt")] -fn libpt_py(py: Python, m: &PyModule) -> PyResult<()> { - m.add_function(wrap_pyfunction!(version, m)?)?; - #[cfg(feature = "core")] - core::submodule(py, m)?; - #[cfg(feature = "log")] - log::submodule(py, m)?; - #[cfg(feature = "bintols")] - bintols::submodule(py, m)?; - Ok(()) -} diff --git a/members/libpt-py/src/log/mod.rs b/members/libpt-py/src/log/mod.rs deleted file mode 100644 index 6cc817f..0000000 --- a/members/libpt-py/src/log/mod.rs +++ /dev/null @@ -1,91 +0,0 @@ -use std::path::PathBuf; - -use pyo3::prelude::*; - -use libpt::log as origin; - -#[derive(Clone)] -#[pyclass] -pub enum Level { - Error, - Warn, - Info, - Debug, - Trace, -} - -impl From for origin::Level { - fn from(value: Level) -> Self { - match value { - Level::Error => origin::Level::ERROR, - Level::Warn => origin::Level::WARN, - Level::Info => origin::Level::INFO, - Level::Debug => origin::Level::DEBUG, - Level::Trace => origin::Level::TRACE, - } - } -} - -#[pyclass] -pub struct Logger { - inner: origin::Logger, -} - -impl From for Logger { - fn from(inner: origin::Logger) -> Self { - Self { inner } - } -} - -#[pymethods] -impl Logger { - #[new] - pub fn build( - log_dir: Option, - max_level: Option, - uptime: Option, - ) -> anyhow::Result { - // concert our wrapper type - let max_level = max_level.map(origin::Level::from); - let mut builder = origin::Logger::builder(); - if log_dir.is_some() { - builder = builder.log_dir(log_dir.unwrap()); - } - if max_level.is_some() { - builder = builder.max_level(max_level.unwrap()); - } - if uptime.is_some() { - builder = builder.uptime(uptime.unwrap()); - } - Ok(builder.build()?.into()) - } - - /// ## logging at [`Level::ERROR`] - pub fn error(&self, printable: String) { - self.inner.error(printable) - } - /// ## logging at [`Level::WARN`] - pub fn warn(&self, printable: String) { - self.inner.warn(printable) - } - /// ## logging at [`Level::INFO`] - pub fn info(&self, printable: String) { - self.inner.info(printable) - } - /// ## logging at [`Level::DEBUG`] - pub fn debug(&self, printable: String) { - self.inner.debug(printable) - } - /// ## logging at [`Level::StringRACE`] - pub fn trace(&self, printable: String) { - self.inner.trace(printable) - } -} - -/// implement a python module in Rust -pub fn submodule(py: Python, parent: &PyModule) -> PyResult<()> { - let module = PyModule::new(py, "log")?; - module.add_class::()?; - parent.add_submodule(module)?; - Ok(()) -} diff --git a/members/libpt-py/src/net/mod.rs b/members/libpt-py/src/net/mod.rs deleted file mode 100644 index e69de29..0000000 -- 2.40.1 From b06a821f2931bf3cfd7f85bc0945541849a4af47 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 19:43:43 +0200 Subject: [PATCH 55/66] docs(cli): add crate level documentation but I'm lazy --- members/libpt-cli/src/lib.rs | 2 ++ 1 file changed, 2 insertions(+) diff --git a/members/libpt-cli/src/lib.rs b/members/libpt-cli/src/lib.rs index 7ffceab..965f94e 100644 --- a/members/libpt-cli/src/lib.rs +++ b/members/libpt-cli/src/lib.rs @@ -1,3 +1,5 @@ +//! This module bundles a lot of good CLI tools, and adds some of it's own, to make development of +//! CLI apps easier and more ergonomic. #![warn(clippy::pedantic, clippy::style, clippy::nursery)] pub mod args; pub mod printing; -- 2.40.1 From 914f17f3595fa656e6baca2a39cbabd053a972d4 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 19:45:21 +0200 Subject: [PATCH 56/66] chore: bump libpt-log --- members/libpt-log/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/members/libpt-log/Cargo.toml b/members/libpt-log/Cargo.toml index e1645ab..8b943dc 100644 --- a/members/libpt-log/Cargo.toml +++ b/members/libpt-log/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "libpt-log" publish.workspace = true -version = "0.4.3" +version = "0.5.0" edition.workspace = true authors.workspace = true license.workspace = true -- 2.40.1 From c9f879b97faf861d08937be44629400e14e5945f Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 19:46:27 +0200 Subject: [PATCH 57/66] chore: bump libpt-cli --- members/libpt-cli/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/members/libpt-cli/Cargo.toml b/members/libpt-cli/Cargo.toml index 12176b3..bb036a5 100644 --- a/members/libpt-cli/Cargo.toml +++ b/members/libpt-cli/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "libpt-cli" publish.workspace = true -version = "0.1.0" +version = "0.1.1" edition.workspace = true authors.workspace = true license.workspace = true -- 2.40.1 From b96b2ac4f62b9ca5d87626edd75286c277badab1 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 19:47:23 +0200 Subject: [PATCH 58/66] chore: bump libpt --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 1b9d809..39c216b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -5,7 +5,7 @@ default-members = [".", "members/libpt-core"] [workspace.package] publish = true -version = "0.6.0-alpha.0" +version = "0.6.0" edition = "2021" authors = ["Christoph J. Scherr "] license = "GPL-3.0-or-later" -- 2.40.1 From 7db1833cbbe3b489b53f258093e5f82489a84e36 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 19:50:12 +0200 Subject: [PATCH 59/66] feat(log): apperently, show time or not was also no option??? --- members/libpt-log/src/lib.rs | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/members/libpt-log/src/lib.rs b/members/libpt-log/src/lib.rs index e307ff7..6783177 100644 --- a/members/libpt-log/src/lib.rs +++ b/members/libpt-log/src/lib.rs @@ -267,6 +267,15 @@ impl LoggerBuilder { self } + /// when making a log, display the time of the message + /// + /// Default: true + #[must_use] + pub const fn display_time(mut self, show_time: bool) -> Self { + self.show_time = show_time; + self + } + /// when making a log, display the log level of the message /// /// Default: true -- 2.40.1 From d052fb2b34fc34af22fd7165f8627aab15b48cdf Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 19:51:22 +0200 Subject: [PATCH 60/66] docs(cli): example was broken --- members/libpt-cli/examples/repl.rs | 12 +----------- 1 file changed, 1 insertion(+), 11 deletions(-) diff --git a/members/libpt-cli/examples/repl.rs b/members/libpt-cli/examples/repl.rs index dc8c357..2d07794 100644 --- a/members/libpt-cli/examples/repl.rs +++ b/members/libpt-cli/examples/repl.rs @@ -31,17 +31,7 @@ enum ReplCommand { fn main() -> anyhow::Result<()> { // You would normally make a proper cli interface with clap before entering the repl. This is // omitted here for brevity - let _logger = { - let mut this = { - let mut this = Logger::builder(); - this.show_time = false; - this - }; - let max_level = Level::INFO; - this.max_level = max_level; - this - } - .build()?; + let _logger = Logger::builder().display_time(false).build(); // the compiler can infer that we want to use the ReplCommand enum. let mut repl = DefaultRepl::::default(); -- 2.40.1 From b1e6558a306298fe5b453722592f4c1916b7f5f8 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 19:52:09 +0200 Subject: [PATCH 61/66] chore: bumps for log and cli --- members/libpt-cli/Cargo.toml | 2 +- members/libpt-log/Cargo.toml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/members/libpt-cli/Cargo.toml b/members/libpt-cli/Cargo.toml index bb036a5..da25d65 100644 --- a/members/libpt-cli/Cargo.toml +++ b/members/libpt-cli/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "libpt-cli" publish.workspace = true -version = "0.1.1" +version = "0.1.2" edition.workspace = true authors.workspace = true license.workspace = true diff --git a/members/libpt-log/Cargo.toml b/members/libpt-log/Cargo.toml index 8b943dc..8a327a0 100644 --- a/members/libpt-log/Cargo.toml +++ b/members/libpt-log/Cargo.toml @@ -1,7 +1,7 @@ [package] name = "libpt-log" publish.workspace = true -version = "0.5.0" +version = "0.5.1" edition.workspace = true authors.workspace = true license.workspace = true -- 2.40.1 From 89c481fa2ea2e575424118739cb1dfce2e46e289 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 19:53:50 +0200 Subject: [PATCH 62/66] fix: make the libpt log version correct --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index 39c216b..a7083ea 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -21,7 +21,7 @@ anyhow = "1.0.79" thiserror = "1.0.56" libpt-core = { version = "0.4.0", path = "members/libpt-core" } libpt-bintols = { version = "0.5.1", path = "members/libpt-bintols" } -libpt-log = { version = "0.4.2", path = "members/libpt-log" } +libpt-log = { version = "0.5.1", path = "members/libpt-log" } libpt-cli = { version = "0.1.0", path = "members/libpt-cli" } [package] -- 2.40.1 From 69ce80483df1da7c16c619ad0c6b5aa6c5cb1114 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 19:54:11 +0200 Subject: [PATCH 63/66] chore: clippy findings --- members/libpt-log/src/lib.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/members/libpt-log/src/lib.rs b/members/libpt-log/src/lib.rs index 6783177..fa5c697 100644 --- a/members/libpt-log/src/lib.rs +++ b/members/libpt-log/src/lib.rs @@ -31,7 +31,7 @@ use error::Error; /// I'm just repackaging it a little to make it more ergonomic pub use tracing; pub use tracing::{debug, error, info, trace, warn, Level}; -use tracing_appender::{self, non_blocking::NonBlocking}; +use tracing_appender::{self}; use tracing_subscriber::fmt::{format::FmtSpan, time}; use anyhow::{bail, Result}; -- 2.40.1 From 21444f1ffe7eb2eae08e33892e555f5152bb43e7 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 19:56:03 +0200 Subject: [PATCH 64/66] chore: bump versions --- Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Cargo.toml b/Cargo.toml index a7083ea..a7b63d6 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -22,7 +22,7 @@ thiserror = "1.0.56" libpt-core = { version = "0.4.0", path = "members/libpt-core" } libpt-bintols = { version = "0.5.1", path = "members/libpt-bintols" } libpt-log = { version = "0.5.1", path = "members/libpt-log" } -libpt-cli = { version = "0.1.0", path = "members/libpt-cli" } +libpt-cli = { version = "0.1.2", path = "members/libpt-cli" } [package] name = "libpt" -- 2.40.1 From ca3cabf6caa53376b9c2ccda5bc5147d0ad7c574 Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 17:59:06 +0000 Subject: [PATCH 65/66] automatic cargo CI changes --- members/libpt-cli/examples/repl.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/members/libpt-cli/examples/repl.rs b/members/libpt-cli/examples/repl.rs index 2d07794..162cd09 100644 --- a/members/libpt-cli/examples/repl.rs +++ b/members/libpt-cli/examples/repl.rs @@ -1,7 +1,7 @@ use console::style; use libpt_cli::repl::{DefaultRepl, Repl}; use libpt_cli::{clap, printing, strum}; -use libpt_log::{debug, Level, Logger}; +use libpt_log::{debug, Logger}; use clap::Subcommand; use strum::EnumIter; -- 2.40.1 From 5c6e5b242e935a9a94add5c4a45b758cf8335eeb Mon Sep 17 00:00:00 2001 From: PlexSheep Date: Tue, 9 Jul 2024 20:16:04 +0200 Subject: [PATCH 66/66] docs(cli): help template was missing something --- members/libpt-cli/src/args.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/members/libpt-cli/src/args.rs b/members/libpt-cli/src/args.rs index 90edbf5..019179e 100644 --- a/members/libpt-cli/src/args.rs +++ b/members/libpt-cli/src/args.rs @@ -18,7 +18,7 @@ use log; /// # use libpt_cli::args::HELP_TEMPLATE; /// use clap::Parser; /// #[derive(Parser, Debug, Clone, PartialEq, Eq, Hash)] -/// #[command(help_template = HELP_TEMPLATE)] +/// #[command(help_template = HELP_TEMPLATE, author, version)] /// pub struct MyArgs { /// /// show more details /// #[arg(short, long)] -- 2.40.1