Many hyperlinks are disabled.
Use anonymous login
to enable hyperlinks.
Difference From qpprint-0.3.0 To qpprint-0.3.1
2025-05-30
| ||
12:54 | Merge meta. check-in: 7e55bb9041 user: jan tags: trunk | |
12:09 | Merge from trunk to get latest unicode width updates. check-in: 8551ed53a3 user: jan tags: meta | |
11:13 | Release maintenance. check-in: c838aa0bea user: jan tags: qpprint-0.3.1, trunk | |
11:07 | Merge. check-in: be1d708848 user: jan tags: trunk | |
2025-05-16
| ||
22:15 | Start work on fixing cell width when using multibyte utf-8 characters. check-in: 764f26c1b7 user: jan tags: unicode | |
21:08 | Release maintenance. check-in: 735e2540e6 user: jan tags: qpprint-0.3.0, trunk | |
21:05 | Update edition & msrv. Happy clippy. check-in: bd0251b533 user: jan tags: trunk | |
Changes to Cargo.toml.
1 2 | [package] name = "qpprint" | | | 1 2 3 4 5 6 7 8 9 10 | [package] name = "qpprint" version = "0.3.1" edition = "2024" license = "0BSD" # https://crates.io/category_slugs categories = [ "text-processing" ] keywords = [ "cli", "console", "terminal", "format", "print" ] repository = "https://repos.qrnch.tech/pub/qpprint" description = "Simple console printing/formatting." |
︙ | ︙ | |||
21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 | # https://doc.rust-lang.org/cargo/reference/manifest.html#the-badges-section [badges] maintenance = { status = "experimental" } [dependencies] terminal_size = { version = "0.4.2" } yansi = { version = "1.0.1" } [lints.clippy] all = { level = "warn", priority = -1 } pedantic = { level = "warn", priority = -1 } nursery = { level = "warn", priority = -1 } cargo = { level = "warn", priority = -1 } multiple_crate_versions = "allow" | > | 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | # https://doc.rust-lang.org/cargo/reference/manifest.html#the-badges-section [badges] maintenance = { status = "experimental" } [dependencies] terminal_size = { version = "0.4.2" } unicode-width = { version = "0.2.0" } yansi = { version = "1.0.1" } [lints.clippy] all = { level = "warn", priority = -1 } pedantic = { level = "warn", priority = -1 } nursery = { level = "warn", priority = -1 } cargo = { level = "warn", priority = -1 } multiple_crate_versions = "allow" |
Changes to examples/newtbl.rs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | use qpprint::tbl::{Align, CellValue, Column, Data, Renderer}; use yansi::{Color, Painted}; fn main() { table1(); println!(); table2(); println!(); table3(); } fn table1() { // // Define table columns // | > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 | use qpprint::tbl::{Align, CellValue, Column, Data, Renderer}; use yansi::{Color, Painted}; fn main() { table1(); println!(); table2(); println!(); table3(); println!(); table4(); } fn table1() { // // Define table columns // |
︙ | ︙ | |||
92 93 94 95 96 97 98 99 100 101 102 103 104 105 | let columns = [idcol, namecol, namecol2]; let mut data = Data::new(columns.len()); data.add_row(vec![4.into(), "hell".into(), "hell".into()]); data.add_row(vec![42.into(), "hello".into(), "hello".into()]); data.add_row(vec![11.into(), "world".into(), "world".into()]); let renderer = Renderer::new(&columns, &data).header(Some('=')); renderer.print(); } // vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 : | > > > > > > > > > > > > > > > > > | 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 | let columns = [idcol, namecol, namecol2]; let mut data = Data::new(columns.len()); data.add_row(vec![4.into(), "hell".into(), "hell".into()]); data.add_row(vec![42.into(), "hello".into(), "hello".into()]); data.add_row(vec![11.into(), "world".into(), "world".into()]); let renderer = Renderer::new(&columns, &data).header(Some('=')); renderer.print(); } fn table4() { let keycol = Column::new("Key", ToString::to_string); let valcol = Column::new("Value", ToString::to_string); let columns = [keycol, valcol]; let mut data = Data::new(columns.len()); data.add_row(vec!["hell".into(), "hell".into()]); data.add_row(vec!["hello🔒".into(), "hello".into()]); data.add_row(vec!["world".into(), "world".into()]); let renderer = Renderer::new(&columns, &data).header(Some('=')); renderer.print(); } // vim: set ft=rust et sw=2 ts=2 sts=2 cinoptions=2 tw=79 : |
Changes to src/lib.rs.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 | pub mod tbl; use terminal_size::{terminal_size, Height, Width}; pub use yansi::{Color, Painted}; pub enum Types { Paragraph(String) } pub enum Align { Left, Center, Right } pub struct Column { | > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 | pub mod tbl; use terminal_size::{terminal_size, Height, Width}; pub use yansi::{Color, Painted}; pub enum Types { Paragraph(String) } #[derive(Copy, Clone, Debug)] pub enum Align { Left, Center, Right } pub struct Column { |
︙ | ︙ |
Changes to src/tbl.rs.
1 2 3 4 5 6 7 8 9 10 11 12 13 | use std::{ borrow::Cow, fmt, iter::{self, zip} }; use yansi::Painted; pub use super::Align; #[allow(clippy::type_complexity)] pub struct Column { | > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | use std::{ borrow::Cow, fmt, iter::{self, zip} }; use unicode_width::UnicodeWidthStr; use yansi::Painted; pub use super::Align; #[allow(clippy::type_complexity)] pub struct Column { |
︙ | ︙ | |||
227 228 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 | } pub fn print(&self) { println!("{self}"); } } impl fmt::Display for Renderer<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // // Iterate over cells and generate a new rendered table. // Every cell here is a String // let mut rendered: Vec<Vec<String>> = Vec::with_capacity(self.data.len()); // // Used to keep track of auto-detected column widths. // // If the renderer is configured to show a header, then initialize to the // column titles. Otherwise initialize to 0. // // If a column min and/or max widths have been configured, the column // widths will be clamped later. // let mut col_widths: Vec<usize> = if self.show_header { | > > > > > > > > > > | | 229 230 231 232 233 234 235 236 237 238 239 240 241 242 243 244 245 246 247 248 249 250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270 271 272 | } pub fn print(&self) { println!("{self}"); } } /// Calculate the width (in units of number of terminal character cells) of a /// unicode string. /// /// Apparently this is not an exact science. It should work well enough as /// long as no one tries to be too creative. #[inline] fn strlen(s: &str) -> usize { //c.title.chars().count() s.width() } impl fmt::Display for Renderer<'_> { fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { // // Iterate over cells and generate a new rendered table. // Every cell here is a String // let mut rendered: Vec<Vec<String>> = Vec::with_capacity(self.data.len()); // // Used to keep track of auto-detected column widths. // // If the renderer is configured to show a header, then initialize to the // column titles. Otherwise initialize to 0. // // If a column min and/or max widths have been configured, the column // widths will be clamped later. // let mut col_widths: Vec<usize> = if self.show_header { self.cols.iter().map(|c| strlen(&c.title)).collect() } else { vec![0; self.cols.len()] }; // // Convert table of CellValues into a table of Strings. // |
︙ | ︙ | |||
280 281 282 283 284 285 286 | { let cell_str = (col.renderer)(cell); // ToDo: Cut down to size if cell_str.len() exceeds column's max_width //if cell_str.len() > col_width {} | | | 292 293 294 295 296 297 298 299 300 301 302 303 304 305 306 | { let cell_str = (col.renderer)(cell); // ToDo: Cut down to size if cell_str.len() exceeds column's max_width //if cell_str.len() > col_width {} *cw = std::cmp::max(*cw, strlen(&cell_str)); rrow.push(cell_str); } rendered.push(rrow); } // |
︙ | ︙ | |||
306 307 308 309 310 311 312 | if self.show_header { // // Print column headers // for (col, cw) in std::iter::zip(self.cols, &col_widths) { let title = trunc_str(&col.title, *cw, col.trunc_len, col.trunc_ch); | | < | < < < < < < < < | 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 | if self.show_header { // // Print column headers // for (col, cw) in std::iter::zip(self.cols, &col_widths) { let title = trunc_str(&col.title, *cw, col.trunc_len, col.trunc_ch); let cc = format_cell(&title, *cw, col.title_align); fields.push(cc); } writeln!(f, "{}", fields.join(&colspace))?; // // Print heading underline // |
︙ | ︙ | |||
349 350 351 352 353 354 355 | for ((col, cw), (cv, cell)) in iter::zip(iter::zip(self.cols, &col_widths), iter::zip(cvs, row)) { let cell = trunc_str(&cell, *cw, col.trunc_len, col.trunc_ch); if let Some(ref stylize) = col.stylize { let cell = stylize(cv, &cell); | | < | < < < < < < < < | < | < < < < < < < < > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > > | | < | 352 353 354 355 356 357 358 359 360 361 362 363 364 365 366 367 368 369 370 371 372 373 374 375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405 406 407 408 409 410 411 412 413 414 415 416 417 418 419 420 421 422 423 424 425 426 427 428 429 430 431 432 433 434 435 436 437 438 439 440 441 442 443 | for ((col, cw), (cv, cell)) in iter::zip(iter::zip(self.cols, &col_widths), iter::zip(cvs, row)) { let cell = trunc_str(&cell, *cw, col.trunc_len, col.trunc_ch); if let Some(ref stylize) = col.stylize { let cell = stylize(cv, &cell); let cc = format_painted_cell(&cell, *cw, col.cell_align); fields.push(cc); } else { let cc = format_cell(&cell, *cw, col.cell_align); fields.push(cc); } } writeln!(f, "{}", fields.join(&colspace))?; } Ok(()) } } fn format_cell(s: &str, cell_width: usize, align: Align) -> String { match align { Align::Left => { //println!("cell_width={cell_width}, strlen(s)={}", strlen(s)); let pad = cell_width - strlen(s); format!("{s}{:pad$}", "") } Align::Center => { let pad = cell_width - strlen(s); let (lpad, rpad) = split_len(pad); format!("{:lpad$}{s}{:rpad$}", "", "") } Align::Right => { let pad = cell_width - strlen(s); format!("{:pad$}{s}", "") } } } fn format_painted_cell( s: &Painted<String>, cell_width: usize, align: Align ) -> String { match align { Align::Left => { let pad = cell_width - strlen(&s.value); format!("{s}{:pad$}", "") } Align::Center => { let pad = cell_width - strlen(&s.value); let (lpad, rpad) = split_len(pad); format!("{:lpad$}{s}{:rpad$}", "", "") } Align::Right => { let pad = cell_width - strlen(&s.value); format!("{:pad$}{s}", "") } } } #[inline] const fn split_len(len: usize) -> (usize, usize) { let left = len / 2; let right = len - left; (left, right) } fn clamp_width(w: usize, min: Option<usize>, max: Option<usize>) -> usize { let w = min.map_or(w, |min| if w < min { min } else { w }); max.map_or(w, |max| if w > max { max } else { w }) } fn trunc_str( s: &str, width: usize, trunc_len: usize, trunc_ch: char ) -> Cow<str> { let slen = strlen(s); if slen > width { let trunc = s[..slen - trunc_len - 1].to_string(); let cont = std::iter::repeat_n(trunc_ch, trunc_len).collect::<String>(); let s = format!("{trunc}{cont}"); Cow::from(s) } else { Cow::from(s) } } |
︙ | ︙ |
Changes to www/changelog.md.
1 2 3 4 5 6 | # Change Log ⚠️ indicates a breaking change. ## [Unreleased] | | > > > > > > > > > > > > > > > > > | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 | # Change Log ⚠️ indicates a breaking change. ## [Unreleased] [Details](/vdiff?from=qpprint-0.3.1&to=trunk) ### Added ### Changed ### Removed --- ## [0.3.1] - 2025-05-30 [Details](/vdiff?from=qpprint-0.3.0&to=qpprint-0.3.1) ### Added - Derive `Copy`, `Clone` and `Debug` for `Align`. ### Changed - Fix cell width calculation when it contains unicode characters that are wider than a single character cell when drawn to the terminal. Apparently this isn't an exact science, but should work as long as one doesn't try to be too creative. --- ## [0.3.0] - 2025-05-16 [Details](/vdiff?from=qpprint-0.2.1&to=qpprint-0.3.0) ### Changed |
︙ | ︙ |