gitql_cli/printer/
table_printer.rs

1use comfy_table::Color;
2use comfy_table::ContentArrangement;
3use gitql_core::object::GitQLObject;
4use gitql_core::object::Row;
5
6use super::BaseOutputPrinter;
7
8enum PaginationInput {
9    NextPage,
10    PreviousPage,
11    Quit,
12}
13
14pub struct TablePrinter {
15    pub pagination: bool,
16    pub page_size: usize,
17    pub theme_config: TableThemeConfig,
18}
19
20pub struct TableThemeConfig {
21    pub header_forground_color: Option<Color>,
22    pub header_background_color: Option<Color>,
23    pub arrangement: Option<ContentArrangement>,
24}
25
26impl Default for TableThemeConfig {
27    fn default() -> TableThemeConfig {
28        TableThemeConfig {
29            header_forground_color: Some(Color::Green),
30            header_background_color: None,
31            arrangement: Some(comfy_table::ContentArrangement::Dynamic),
32        }
33    }
34}
35
36impl TablePrinter {
37    pub fn new(pagination: bool, page_size: usize) -> Self {
38        TablePrinter {
39            pagination,
40            page_size,
41            theme_config: TableThemeConfig::default(),
42        }
43    }
44
45    pub fn set_theme(&mut self, theme: TableThemeConfig) {
46        self.theme_config = theme;
47    }
48}
49
50impl BaseOutputPrinter for TablePrinter {
51    fn print(&self, object: &mut GitQLObject) {
52        if object.is_empty() || object.groups[0].is_empty() {
53            return;
54        }
55
56        let titles = &object.titles;
57        let group = object.groups.first().unwrap();
58        let group_len = group.len();
59
60        // Setup table headers
61        let mut table_headers = vec![];
62        for key in titles {
63            let mut table_cell = comfy_table::Cell::new(key);
64            if let Some(forground_color) = self.theme_config.header_forground_color {
65                table_cell = table_cell.fg(forground_color);
66            }
67
68            if let Some(background_color) = self.theme_config.header_background_color {
69                table_cell = table_cell.bg(background_color);
70            }
71
72            table_headers.push(table_cell);
73        }
74
75        // Print all data without pagination
76        if !self.pagination || self.page_size >= group_len {
77            self.print_group_as_table(titles, table_headers, &group.rows);
78            return;
79        }
80
81        // Setup the pagination mode
82        let number_of_pages = (group_len as f64 / self.page_size as f64).ceil() as usize;
83        let mut current_page = 1;
84
85        loop {
86            let start_index = (current_page - 1) * self.page_size;
87            let end_index = (start_index + self.page_size).min(group_len);
88
89            let current_page_groups = &group.rows[start_index..end_index];
90            println!("Page {current_page}/{number_of_pages}");
91            self.print_group_as_table(titles, table_headers.clone(), current_page_groups);
92
93            let pagination_input = self.handle_pagination_input(current_page, number_of_pages);
94            match pagination_input {
95                PaginationInput::NextPage => current_page += 1,
96                PaginationInput::PreviousPage => current_page -= 1,
97                PaginationInput::Quit => break,
98            }
99        }
100    }
101}
102
103impl TablePrinter {
104    fn print_group_as_table(
105        &self,
106        titles: &[String],
107        table_headers: Vec<comfy_table::Cell>,
108        rows: &[Row],
109    ) {
110        let mut table = comfy_table::Table::new();
111
112        // Setup table style
113        table.load_preset(comfy_table::presets::UTF8_FULL);
114        table.apply_modifier(comfy_table::modifiers::UTF8_ROUND_CORNERS);
115
116        if let Some(arrangement) = &self.theme_config.arrangement {
117            table.set_content_arrangement(arrangement.clone());
118        }
119
120        table.set_header(table_headers);
121
122        let titles_len = titles.len();
123
124        // Add rows to the table
125        for row in rows {
126            let mut table_row: Vec<comfy_table::Cell> = vec![];
127            for index in 0..titles_len {
128                if let Some(value) = row.values.get(index) {
129                    table_row.push(comfy_table::Cell::new(value.literal()));
130                }
131            }
132            table.add_row(table_row);
133        }
134
135        // Print table
136        println!("{table}");
137    }
138
139    fn handle_pagination_input(
140        &self,
141        current_page: usize,
142        number_of_pages: usize,
143    ) -> PaginationInput {
144        loop {
145            if current_page < 2 {
146                println!("Enter 'n' for next page, or 'q' to quit:");
147            } else if current_page == number_of_pages {
148                println!("'p' for previous page, or 'q' to quit:");
149            } else {
150                println!("Enter 'n' for next page, 'p' for previous page, or 'q' to quit:");
151            }
152
153            std::io::Write::flush(&mut std::io::stdout()).expect("flush failed!");
154
155            let mut line = String::new();
156            std::io::stdin()
157                .read_line(&mut line)
158                .expect("Failed to read input");
159
160            let input = line.trim();
161            if input == "q" || input == "n" || input == "p" {
162                match input {
163                    "n" => {
164                        if current_page < number_of_pages {
165                            return PaginationInput::NextPage;
166                        } else {
167                            println!("Already on the last page");
168                            continue;
169                        }
170                    }
171                    "p" => {
172                        if current_page > 1 {
173                            return PaginationInput::PreviousPage;
174                        } else {
175                            println!("Already on the first page");
176                            continue;
177                        }
178                    }
179                    "q" => return PaginationInput::Quit,
180                    _ => unreachable!(),
181                }
182            }
183
184            println!("Invalid input");
185        }
186    }
187}