gitql_cli/printer/
table_printer.rs1use 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 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 if !self.pagination || self.page_size >= group_len {
77 self.print_group_as_table(titles, table_headers, &group.rows);
78 return;
79 }
80
81 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 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 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 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}