zhiburt / tabled

An easy to use library for pretty print tables of Rust structs and enums.
MIT License
2.01k stars 84 forks source link

Impossible to call `Style::empty().remove_left()` #370

Closed fritzrehde closed 11 months ago

fritzrehde commented 11 months ago

I want to create a table without a left border, so there should be no character at all left of the first column. However, calling Style::empty().remove_left() is simply not possible, I am assuming because of the generics implementation. How do I completely remove a left border?

zhiburt commented 11 months ago

Hi @fritzrehde

I am assuming because of the generics implementation

Right

You can't do it for Style::empty() (cause there's no left border, means there's nothing to set off), but you can do it for other styles.

I want to create a table without a left border, so there should be no character at all left of the first column

Just a note: It's actually could be done not only by Style but as well by Border, RawStyle and likely by more means.


Something like this.

    let mut table = Table::from_iter(['a'..='z', 'b'..='z', 'c'..='z']);
    table.with(Style::sharp().remove_left().remove_right());
    println!("{table}");
───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───┬───
 a │ b │ c │ d │ e │ f │ g │ h │ i │ j │ k │ l │ m │ n │ o │ p │ q │ r │ s │ t │ u │ v │ w │ x │ y │ z 
───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───┼───
 b │ c │ d │ e │ f │ g │ h │ i │ j │ k │ l │ m │ n │ o │ p │ q │ r │ s │ t │ u │ v │ w │ x │ y │ z │   
 c │ d │ e │ f │ g │ h │ i │ j │ k │ l │ m │ n │ o │ p │ q │ r │ s │ t │ u │ v │ w │ x │ y │ z │   │   
───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───┴───

Let me know if it helps, or describe what you're trying to achieve further.

Take care

fritzrehde commented 11 months ago

I would like sth like:

a  b  c  d  e  ...  z
b  c  d  e  ...  z
c  d  e  ...  z

I'm mostly using tabled to ensure that a multi-line column will stay inside it's column instead of overlapping. Here's my code:

        let color = get_possible_values::<Color>();
        let boldness = get_possible_values::<Boldness>();
        let key_code = get_possible_values::<KeyCode>();
        let key_modifier = get_possible_values::<KeyModifier>();
        let operation = get_possible_values::<OperationParsed>();

        // TODO: remove extra space before left column once tabled supports removing left border
        let table_data = [
            [" COLOR", &format!("[{color}]")],
            [" BOLDNESS", &format!("[{boldness}]")],
            [" KEY", "[<KEY-MODIFIER>+<KEY-CODE>, <KEY-CODE>]"],
            [" KEY-MODIFIER", &format!("[{key_modifier}]")],
            [" KEY-CODE", &format!("[{key_code}]")],
            [" OP", &format!("[{operation}]")],
        ];
        let mut table = Builder::from_iter(table_data).build();
        table.with(TableStyle::empty());

        // Set table width to terminal width.
        if let Some((TerminalWidth(width), _)) = terminal_size() {
            let width: usize = width.into();
            table
                .with(Width::wrap(width).priority::<PriorityMax>().keep_words())
                .with(Width::increase(width));
        }

        format!("Possible values:\n{table}")

and the output i want:

Possible values:
  COLOR         [white, black, red, green, yellow, blue, magenta, cyan, gray, dark-gray,
                light-red, light-green, light-yellow, light-blue, light-magenta, light-cyan,
                reset, unspecified]
  BOLDNESS      [bold, non-bold, unspecified]
  KEY           [<KEY-MODIFIER>+<KEY-CODE>, <KEY-CODE>]
  KEY-MODIFIER  [alt, ctrl]
  KEY-CODE      [esc, enter, left, right, up, down, home, end, pageup, pagedown, backtab,
                backspace, delete, insert, tab, space, <lowercase char>, <uppercase char>,
                f<1-12>]
  OP            [exit, reload, cursor up <N>, cursor down <N>, cursor first, cursor last, select,
                unselect, toggle-selection, select-all, unselect-all, exec -- <CMD>, exec & --
                <CMD>, exec tui & -- <TUI-CMD>, set-env <ENV> -- <CMD>, unset-env <ENV>,
                read-into-env <ENV>, help-show, help-hide, help-toggle]

Ideally, I want the first column to start all the way on the left without a border, so it's inline with the "Possible values:". However, for now I have settled for the hacky approach of moving it to the right by two spaces.

zhiburt commented 11 months ago

Something like this?


use std::iter::FromIterator;

use tabled::{
    settings::{peaker::PriorityMax, Padding, Settings, Style, Width},
    Table,
};

fn main() {
    let color = "white, black, red, green, yellow, blue, magenta, cyan, gray, dark-gray, light-red, light-green, light-yellow, light-blue, light-magenta, light-cyan, reset, unspecified";
    let boldness = "bold, non-bold, unspecified";
    let key = "<KEY-MODIFIER>+<KEY-CODE>, <KEY-CODE>";
    let key_code = "esc, enter, left, right, up, down, home, end, pageup, pagedown, backtab, backspace, delete, insert, tab, space, <lowercase char>, <uppercase char>, f<1-12>";
    let key_modifier = "alt, ctrl";
    let operation = "exit, reload, cursor up <N>, cursor down <N>, cursor first, cursor last, select, unselect, toggle-selection, select-all, unselect-all, exec -- <CMD>, exec & --<CMD>, exec tui & -- <TUI-CMD>, set-env <ENV> -- <CMD>, unset-env <ENV>, read-into-env <ENV>, help-show, help-hide, help-toggle";

    let data = [
        [String::from("COLOR"), format!("[{color}]")],
        [String::from("BOLDNESS"), format!("[{boldness}]")],
        [String::from("KEY"), format!("[{key}]")],
        [String::from("KEY-MODIFIER"), format!("[{key_modifier}]")],
        [String::from("KEY-CODE"), format!("[{key_code}]")],
        [String::from("OP"), format!("[{operation}]")],
    ];

    let mut table = Table::from_iter(data);
    table.with(Style::blank()).with(Padding::zero());

    let terminal_width = 100;
    table.with(Settings::new(
        Width::wrap(terminal_width)
            .priority::<PriorityMax>()
            .keep_words(),
        Width::increase(terminal_width),
    ));

    println!("Possible values:\n{table}");
}
Possible values:
COLOR        [white, black, red, green, yellow, blue, magenta, cyan, gray, dark-gray, light-red,    
             light-green, light-yellow, light-blue, light-magenta, light-cyan, reset, unspecified]  
BOLDNESS     [bold, non-bold, unspecified]                                                          
KEY          [<KEY-MODIFIER>+<KEY-CODE>, <KEY-CODE>]                                                
KEY-MODIFIER [alt, ctrl]                                                                            
KEY-CODE     [esc, enter, left, right, up, down, home, end, pageup, pagedown, backtab, backspace,   
             delete, insert, tab, space, <lowercase char>, <uppercase char>, f<1-12>]               
OP           [exit, reload, cursor up <N>, cursor down <N>, cursor first, cursor last, select,      
             unselect, toggle-selection, select-all, unselect-all, exec -- <CMD>, exec & --<CMD>,   
             exec tui & -- <TUI-CMD>, set-env <ENV> -- <CMD>, unset-env <ENV>, read-into-env <ENV>, 
             help-show, help-hide, help-toggle] 
zhiburt commented 11 months ago

You could also do it in a more structured way.

use tabled::{
    settings::{Rotate, peaker::PriorityMax, Padding, Settings, Style, Width},
    Table, Tabled
};

#[derive(Tabled)]
struct CmdArgs {
    #[tabled(display_with = "format_list")]
    color: Vec<String>,
    #[tabled(display_with = "format_list")]
    boldness: Vec<String>,
    #[tabled(display_with = "format_list")]
    key: Vec<String>,
    #[tabled(display_with = "format_list")]
    key_code: Vec<String>,
    #[tabled(display_with = "format_list")]
    key_modifier: Vec<String>,
    #[tabled(rename = "op", display_with = "format_list")]
    operation: Vec<String>,
}

fn format_list(list: &[String]) -> String {
    list.to_vec().join(", ")
}

fn parse_list(text: &str) -> Vec<String> {
    text.split(", ").map(String::from).collect()
}

fn main() {
    let cmd_args = CmdArgs {
        color: parse_list("white, black, red, green, yellow, blue, magenta, cyan, gray, dark-gray, light-red, light-green, light-yellow, light-blue, light-magenta, light-cyan, reset, unspecified"),
        boldness: parse_list("bold, non-bold, unspecified"),
        key: parse_list("<KEY-MODIFIER>+<KEY-CODE>, <KEY-CODE>"),
        key_code: parse_list("esc, enter, left, right, up, down, home, end, pageup, pagedown, backtab, backspace, delete, insert, tab, space, <lowercase char>, <uppercase char>, f<1-12>"),
        key_modifier: parse_list("alt, ctrl"),
        operation: parse_list("exit, reload, cursor up <N>, cursor down <N>, cursor first, cursor last, select, unselect, toggle-selection, select-all, unselect-all, exec -- <CMD>, exec & --<CMD>, exec tui & -- <TUI-CMD>, set-env <ENV> -- <CMD>, unset-env <ENV>, read-into-env <ENV>, help-show, help-hide, help-toggle"),
    };

    let mut table = Table::new([cmd_args]);
    table.with(Rotate::Left);
    table.with(Style::blank()).with(Padding::zero());

    let terminal_width = 100;
    table.with(Settings::new(
        Width::wrap(terminal_width)
            .priority::<PriorityMax>()
            .keep_words(),
        Width::increase(terminal_width),
    ));

    println!("Possible values:\n{table}");
}
Possible values:
op           exit, reload, cursor up <N>, cursor down <N>, cursor first, cursor last, select,       
             unselect, toggle-selection, select-all, unselect-all, exec -- <CMD>, exec & --<CMD>,   
             exec tui & -- <TUI-CMD>, set-env <ENV> -- <CMD>, unset-env <ENV>, read-into-env <ENV>, 
             help-show, help-hide, help-toggle                                                      
key_modifier alt, ctrl                                                                              
key_code     esc, enter, left, right, up, down, home, end, pageup, pagedown, backtab, backspace,    
             delete, insert, tab, space, <lowercase char>, <uppercase char>, f<1-12>                
key          <KEY-MODIFIER>+<KEY-CODE>, <KEY-CODE>                                                  
boldness     bold, non-bold, unspecified                                                            
color        white, black, red, green, yellow, blue, magenta, cyan, gray, dark-gray, light-red,     
             light-green, light-yellow, light-blue, light-magenta, light-cyan, reset, unspecified   
fritzrehde commented 11 months ago

This works for now:

        let color = get_possible_values::<PrettyColor>();
        let boldness = get_possible_values_with_alternatives_and_filtering::<Boldness>();
        let key_code = get_possible_values_with_alternatives_and_filtering::<KeyCode>();
        let key_modifier = get_possible_values_with_alternatives_and_filtering::<KeyModifier>();
        let operation = get_possible_values_with_alternatives_and_filtering::<OperationParsed>();

        let table_data = [
            ["COLOR", &format!("[{color}]")],
            ["BOLDNESS", &format!("[{boldness}]")],
            ["KEY", "[<KEY-MODIFIER>+<KEY-CODE>, <KEY-CODE>]"],
            ["KEY-MODIFIER", &format!("[{key_modifier}]")],
            ["KEY-CODE", &format!("[{key_code}]")],
            ["OP", &format!("[{operation}]")],
        ];
        let mut table = Builder::from_iter(table_data).build();
        table
            .with(TableStyle::blank())
            // Remove all margins.
            .with(Margin::new(0, 0, 0, 0))
            // Remove left padding.
            .with(Padding::new(0, 1, 0, 0));

        // Set table width to terminal width.
        if let Some((TerminalWidth(width), _)) = terminal_size() {
            let width: usize = width.into();
            table
                .with(Width::wrap(width).priority::<PriorityMax>().keep_words())
                .with(Width::increase(width));
        }

        format!("Possible values:\n{table}")

which produces:

Possible values:
COLOR         [white, black, red, green, yellow, blue, magenta, cyan, gray, dark-gray, light-red,
              light-green, light-yellow, light-blue, light-magenta, light-cyan, reset,
              unspecified]
BOLDNESS      [bold, non-bold, unspecified]
KEY           [<KEY-MODIFIER>+<KEY-CODE>, <KEY-CODE>]
KEY-MODIFIER  [alt, ctrl]
KEY-CODE      [esc, enter, left, right, up, down, home, end, pageup, pagedown, backtab, backspace,
              delete, insert, tab, space, <lowercase char>, <uppercase char>, f<1-12>]
OP            [exit, reload, cursor up <N>, cursor down <N>, cursor first, cursor last, select,
              unselect, toggle-selection, select-all, unselect-all, exec -- <CMD>, exec & --
              <CMD>, exec tui & -- <TUI-CMD>, set-env <ENV> -- <CMD>, unset-env <ENV>,
              read-into-env <ENV>, help-show, help-hide, help-toggle]

The only problem is that I now also want to display each of the COLOR values in its actual color, and tabled doesn't seem to play well with ANSI characters. When each color is colored with ANSI, the output is:

Possible values:
COLOR         [white, black, red, green, yellow,
              blue, magenta, cyan, gray, dark-gray,
              light-red, light-green, light-yellow, light-blue,
              light-magenta, light-cyan, reset, unspecified]
BOLDNESS      [bold, non-bold, unspecified]
KEY           [<KEY-MODIFIER>+<KEY-CODE>, <KEY-CODE>]
KEY-MODIFIER  [alt, ctrl]
KEY-CODE      [esc, enter, left, right, up, down, home, end, pageup, pagedown, backtab, backspace,
              delete, insert, tab, space, <lowercase char>, <uppercase char>, f<1-12>]
OP            [exit, reload, cursor up <N>, cursor down <N>, cursor first, cursor last, select,
              unselect, toggle-selection, select-all, unselect-all, exec -- <CMD>, exec & --
              <CMD>, exec tui & -- <TUI-CMD>, set-env <ENV> -- <CMD>, unset-env <ENV>,
              read-into-env <ENV>, help-show, help-hide, help-toggle]

As you can see, the linebreak is color string is not wrapped correctly, it's wrapped early. Is this a bug?

fritzrehde commented 11 months ago

Note that even without .keep_words() (which I originally thought might be the culprit) the wrapping is not correct for ANSI strings.

zhiburt commented 11 months ago

Note that even without .keep_words() (which I originally thought might be the culprit) the wrapping is not correct for ANSI strings.

Are you set color feature on?

tabled = { version = "0.14", features = ["color"] }

PS: Yesss I'd renamed the flag to ANSI...

fritzrehde commented 11 months ago

Yes, you're right, I didn't have that enabled, and by enabling it, it works. It's also clearly stated in the documentation readme, so it's on me for not setting it. Thanks!