cli_tables/lib.rs
1use std::{
2 fmt,
3 error,
4 cmp::{
5 max,
6 min
7 }
8};
9use terminal_size::{
10 Width,
11 terminal_size
12};
13
14#[derive(Debug, PartialEq)]
15pub struct TableError {
16 pub message: String,
17}
18
19impl fmt::Display for TableError {
20 fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result {
21 write!(f, "{}", self.message)
22 }
23}
24
25impl error::Error for TableError {}
26
27#[derive(Debug)]
28pub struct Table {
29 table_vec: Vec<Vec<String>>,
30 num_records: usize,
31 num_fields: usize
32}
33
34impl fmt::Display for Table {
35 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
36 write!(f, "({:?}, {}, {})", self.table_vec, self.num_records, self.num_fields)
37 }
38}
39
40impl Table {
41 pub fn new() -> Self {
42 Table {
43 table_vec: Vec::new(),
44 num_records: 0,
45 num_fields: 0
46 }
47 }
48
49 pub fn num_records(&self) -> usize {
50 return self.num_records;
51 }
52
53 pub fn num_fields(&self) -> usize {
54 return self.num_fields;
55 }
56
57 /// ### Description
58 /// Pushes a new record to the end of the `Table` object.
59 ///
60 /// ### Arguments
61 /// * `new_record: &Vec<&str>` - An immutable reference
62 /// to string slices that represent the new record.
63 ///
64 /// ### Errors
65 /// Returns an error if the number of fields is invalid.
66 ///
67 /// ### Example
68 /// ```
69 /// use cli_tables::Table;
70 ///
71 /// let mut table = Table::new();
72 ///
73 /// let header = vec!["Id", "Title", "Series", "Author"];
74 /// table.push_row(&header).unwrap();
75 ///
76 /// let book = vec!["0", "Sword of Destiny", "The Witcher Series", "Andrzej Sapkowski"];
77 /// table.push_row(&book).unwrap();
78 /// ```
79 pub fn push_row(&mut self, new_record: &Vec<&str>) -> Result<(), TableError> {
80 let new_record: Vec<String> = new_record.iter().map(|&field| field.to_string()).collect();
81 let num_fields = new_record.len();
82
83 if num_fields == self.num_fields && self.num_records != 0 {
84 self.table_vec.push(new_record.to_vec());
85 self.num_records += 1;
86 Ok(())
87 }
88 else if self.num_records == 0 {
89 self.table_vec.push(new_record.to_vec());
90 self.num_records = 1;
91 self.num_fields = num_fields;
92 Ok(())
93 }
94 else {
95 let msg = format!("Invalid number of fields in record. Found {}, but expected {}.", num_fields, self.num_fields);
96 Err(TableError {
97 message: msg,
98 })
99 }
100 }
101
102 /// ### Description
103 /// Pushes a new record to the end of the `Table` object.
104 ///
105 /// ### Arguments
106 /// * `new_record: &Vec<String>` - An immutable reference
107 /// to Strings that represent the new record.
108 ///
109 /// ### Errors
110 /// Returns an error if the number of fields is invalid.
111 ///
112 /// ### Example
113 /// ```
114 /// use cli_tables::Table;
115 ///
116 /// let mut table = Table::new();
117 ///
118 /// let header = vec![
119 /// "Id".to_string(),
120 /// "Title".to_string(),
121 /// "Series".to_string(),
122 /// "Author".to_string()
123 /// ];
124 /// table.push_row_string(&header).unwrap();
125 ///
126 /// let book = vec![
127 /// "0".to_string(),
128 /// "Sword of Destiny".to_string(),
129 /// "The Witcher Series".to_string(),
130 /// "Andrzej Sapkowski".to_string()
131 /// ];
132 /// table.push_row_string(&book).unwrap();
133 /// ```
134 pub fn push_row_string(&mut self, new_record: &Vec<String>) -> Result<(), TableError> {
135 let num_fields = new_record.len();
136
137 if num_fields == self.num_fields && self.num_records != 0 {
138 self.table_vec.push(new_record.to_vec());
139 self.num_records += 1;
140 Ok(())
141 }
142 else if self.num_records == 0 {
143 self.table_vec.push(new_record.to_vec());
144 self.num_records = 1;
145 self.num_fields = num_fields;
146 Ok(())
147 }
148 else {
149 let msg = format!("Invalid number of fields in record. Found {}, but expected {}.", num_fields, self.num_fields);
150 Err(TableError {
151 message: msg,
152 })
153 }
154 }
155
156 /// ### Description
157 /// The `push_rows` function takes multiple records, and adds each one
158 /// in order to the `Table` object.
159 ///
160 /// ### Arguments
161 /// * `new_records` - reference to a `Vec<Vec<&str>>` object that
162 /// represents multiple records.
163 ///
164 /// ### Errors
165 /// Returns a `TableError` if the number of fields is not equal
166 /// for each record or if the number of fields is not equal to
167 /// the existing number of fields in the `Table` object.
168 ///
169 /// ### Example
170 /// ```
171 /// use cli_tables::Table;
172 ///
173 /// let mut table = Table::new();
174 ///
175 /// let table_vec = vec![
176 /// vec!["Id", "Title", "Series", "Author"],
177 /// vec!["0", "Sword of Destiny", "The Witcher Series", "Andrzej Sapkowski"],
178 /// vec!["1", "The Last Wish", "The Witcher Series", "Andrzej Sapkowski"]
179 /// ];
180 /// table.push_rows(&table_vec).unwrap();
181 /// ```
182 pub fn push_rows(&mut self, new_records: &Vec<Vec<&str>>) -> Result<(), TableError> {
183 let num_records = new_records.len();
184 let num_fields = new_records[0].len();
185 for record in 1..num_records {
186 if new_records[record].len() != num_fields {
187 return Err(TableError {
188 message: "Records have an unequal number of fields".to_string()
189 })
190 }
191 }
192 if !self.table_vec.is_empty() {
193 if num_fields != self.num_fields {
194 return Err(TableError {
195 message: format!(
196 "Invalid number of fields in records. Found {}, but expected {}.",
197 num_fields,
198 self.num_fields
199 )
200 })
201 }
202 } else {
203 self.num_records = num_records;
204 self.num_fields = num_fields;
205 }
206 for record in 0..self.num_records {
207 self.table_vec.push(Vec::new());
208 for field in 0..self.num_fields {
209 self.table_vec[record].push(new_records[record][field].to_string());
210 }
211 }
212 Ok(())
213 }
214
215 /// ### Description
216 /// Retrieves the desired record of the `Table` object and returns it.
217 ///
218 /// ### Arguments
219 /// * `id: usize` - represets the desired record id to be retrieved.
220 ///
221 /// ### Errors
222 /// Returns a `TableError` if no record with the given id is found.
223 ///
224 /// ### Example
225 /// ```
226 /// use cli_tables::Table;
227 ///
228 /// let mut table = Table::new();
229 ///
230 /// let header = vec![
231 /// "Id",
232 /// "Title",
233 /// "Series",
234 /// "Author"
235 /// ];
236 /// table.push_row(&header).unwrap();
237 ///
238 /// table.get_row(0).unwrap();
239 /// ```
240 pub fn get_row(&self, id: usize) -> Result<Vec<String>, TableError> {
241 for record in 0..self.num_records {
242 if record == id {
243 return Ok(self.table_vec[record].clone())
244 }
245 }
246 Err(TableError {
247 message: "No record with matching id".to_string()
248 })
249 }
250
251 /// ### Description
252 /// Deletes the desired record from the `Table` object.
253 ///
254 /// ### Arguments
255 /// * `id` - A `usize` value that represets the desired
256 /// record id to be deleted.
257 ///
258 /// ### Errors
259 /// Returns a `TableError` if no record with the given id is found.
260 ///
261 /// ### Example
262 /// ```
263 /// use cli_tables::Table;
264 ///
265 /// let mut table = Table::new();
266 ///
267 /// let header = vec![
268 /// "Id",
269 /// "Title",
270 /// "Series",
271 /// "Author"
272 /// ];
273 /// table.push_row(&header).unwrap();
274 ///
275 /// table.delete_record(0).unwrap();
276 /// ```
277 pub fn delete_record(&mut self, id: usize) -> Result<(), TableError> {
278 for record in 0..self.num_records {
279 if record == id {
280 self.table_vec.remove(record);
281 self.num_records -= 1;
282 return Ok(())
283 }
284 }
285 Err(TableError {
286 message: "No record with matching id".to_string()
287 })
288 }
289
290 /// ### Description
291 /// The `set` function takes multiple records, and adds each one
292 /// in order to the `Table` object. If the `Table` object is not
293 /// empty, it is reset with the given values.
294 ///
295 /// ### Arguments
296 /// * `new_table` - reference to a `Vec<Vec<&str>>` object that
297 /// represents multiple records.
298 ///
299 /// ### Errors
300 /// Returns a `TableError` if the number of fields is not equal
301 /// for each record.
302 ///
303 /// ### Example
304 /// ```
305 /// use cli_tables::Table;
306 ///
307 /// let mut table = Table::new();
308 ///
309 /// let table_vec = vec![
310 /// vec!["Id", "Title", "Series", "Author"],
311 /// vec!["0", "Sword of Destiny", "The Witcher Series", "Andrzej Sapkowski"],
312 /// vec!["1", "The Last Wish", "The Witcher Series", "Andrzej Sapkowski"]
313 /// ];
314 /// table.set(&table_vec).unwrap();
315 /// ```
316 pub fn set(&mut self, new_table: &Vec<Vec<&str>>) -> Result<(), TableError> {
317 if !self.table_vec.is_empty() {
318 self.table_vec = Vec::new();
319 }
320 self.num_records = new_table.len();
321 self.num_fields = new_table[0].len();
322 for record in 0..new_table.len() {
323 if self.num_fields == new_table[record].len() {
324 self.table_vec.push(Vec::new());
325 for field in 0..new_table[record].len() {
326 self.table_vec[record].push(new_table[record][field].to_string());
327 }
328 } else {
329 return Err(TableError {
330 message: "Records have an unequal number of fields".to_string()
331 })
332 }
333 }
334 Ok(())
335 }
336
337 pub fn to_string(&self) -> String {
338 if self.table_vec.is_empty() {
339 return "+----------------+\n| Table is empty |\n+----------------+".to_string();
340 }
341
342 let mut table_str = String::new();
343 let mut field_width = vec![0; self.num_fields];
344 let mut field_length = vec![
345 vec![0; self.num_fields];
346 self.num_records
347 ];
348 let mut terminal_width = 0;
349 if let Some((Width(width), _)) = terminal_size() {
350 terminal_width = width.into();
351 }
352 let mut table_width = 0;
353 let padding_length: usize = 2;
354
355 // table characters
356 let newline = '\n';
357 let border = '|';
358 let padding = ' ';
359 let edge = '+';
360 let line = '-';
361
362 // determine field width for each field
363 for record in 0..self.num_records {
364 // for first record, set width
365 if record == 0 {
366 for field in 0..self.num_fields {
367 field_length[record][field] = self.table_vec[record][field].len();
368 field_width[field] = field_length[record][field];
369 }
370 } else {
371 for field in 0..self.num_fields {
372 field_length[record][field] = self.table_vec[record][field].len();
373 // otherwise, compare widths to find max
374 field_width[field] = max(
375 field_width[field],
376 field_length[record][field]
377 )
378 }
379 }
380 }
381 table_width += field_width.iter().sum::<usize>();
382
383 // account for formatting for the table width
384 let num_borders = self.num_fields + 1;
385 let num_spaces = self.num_fields * 2;
386 table_width += num_borders + num_spaces;
387
388 // determine field widths
389 while table_width > terminal_width {
390 let mut max_field_width = 0;
391 let mut widest_field = 0;
392 for field in 0..self.num_fields {
393 if field_width[field] > max_field_width {
394 widest_field = field;
395 max_field_width = field_width[field];
396 }
397 }
398 field_width[widest_field] -= 1;
399 table_width -= 1;
400 }
401
402 // determine record heights
403 let mut record_height = Vec::new();
404 let mut is_wrapped = vec![
405 vec![false; self.num_fields];
406 self.num_records
407 ];
408 for record in 0..self.num_records {
409 let mut max_record_height = 0;
410 for field in 0..self.num_fields {
411 if field_length[record][field] % field_width[field] != 0 {
412 max_record_height = max(
413 max_record_height,
414 (field_length[record][field] / field_width[field]) + 1
415 );
416 }
417 else {
418 max_record_height = max(
419 max_record_height,
420 field_length[record][field] / field_width[field]
421 );
422 }
423 is_wrapped[record][field] = field_length[record][field] > field_width[field];
424 }
425 record_height.push(max_record_height);
426 }
427
428 // add top border
429 table_str.push(edge);
430 for field in 0..self.num_fields {
431 for _ in 0..field_width[field] + padding_length {
432 table_str.push(line);
433 }
434 table_str.push(edge);
435 }
436 table_str.push(newline);
437
438 // add column headers
439 if record_height[0] > 1 {
440 let mut remaining = "";
441 for line in 0..record_height[0] {
442 table_str.push(border);
443 table_str.push(padding);
444 for field in 0..self.num_fields {
445 if is_wrapped[0][field] {
446 let slice; // check other comment
447 if line == 0 {
448 slice = &self.table_vec[0][field][..field_width[field]];
449 remaining = &self.table_vec[0][field][field_width[field]..];
450 } else {
451 slice = &remaining[..field_width[field]];
452 remaining = &remaining[field_width[field]..];
453 }
454 table_str.push_str(slice);
455 }
456 }
457 table_str.push(padding);
458 table_str.push(border);
459 table_str.push(newline);
460 }
461 } else {
462 table_str.push(border);
463 table_str.push(padding);
464 for field in 0..self.num_fields {
465 table_str.push_str(&self.table_vec[0][field]);
466 for _ in field_length[0][field]..field_width[field] {
467 table_str.push(padding);
468 }
469 // add separators
470 if field != self.num_fields - 1 {
471 table_str.push(padding);
472 table_str.push(border);
473 table_str.push(padding);
474 }
475 }
476 table_str.push(padding);
477 table_str.push(border);
478 table_str.push(newline);
479 }
480
481 // add middle line
482 table_str.push(edge);
483 for field in 0..self.num_fields {
484 for _ in 0..field_width[field] + padding_length {
485 table_str.push(line);
486 }
487 table_str.push(edge);
488 }
489 table_str.push(newline);
490
491 // add values
492 for record in 1..self.num_records {
493 let mut remaining = "";
494 if record_height[record] > 1 {
495 for line in 0..record_height[record] {
496 table_str.push(border);
497 table_str.push(padding);
498 for field in 0..self.num_fields {
499 // add truncated value and store remaining
500 if is_wrapped[record][field] {
501 let slice; // removed "" assignment and make not mutable
502 let mut slice_length = field_width[field];
503 if line == 0 {
504 slice = &self.table_vec[record][field][..field_width[field]];
505 remaining = &self.table_vec[record][field][field_width[field]..];
506 } else {
507 slice_length = min(field_width[field], remaining.len());
508 slice = &remaining[..slice_length];
509 remaining = &remaining[slice_length..];
510 }
511 table_str.push_str(slice);
512 if slice_length < field_width[field] {
513 for _ in slice_length..field_width[field] {
514 table_str.push(padding);
515 }
516 }
517 } else {
518 if line == 0 {
519 table_str.push_str(&self.table_vec[record][field]);
520 // add padding
521 for _ in field_length[record][field]..field_width[field] {
522 table_str.push(padding);
523 }
524 } else {
525 // add padding
526 for _ in 0..field_width[field] {
527 table_str.push(padding);
528 }
529 }
530 }
531 // add separators
532 if field != self.num_fields - 1 {
533 table_str.push(padding);
534 table_str.push(border);
535 table_str.push(padding);
536 }
537 }
538 table_str.push(padding);
539 table_str.push(border);
540 table_str.push(newline);
541 }
542 } else {
543 table_str.push(border);
544 table_str.push(padding);
545 for field in 0..self.num_fields {
546 // add value
547 table_str.push_str(&self.table_vec[record][field]);
548 // add padding
549 for _ in field_length[record][field]..field_width[field] {
550 table_str.push(padding);
551 }
552 // add separators
553 if field != self.num_fields - 1 {
554 table_str.push(padding);
555 table_str.push(border);
556 table_str.push(padding);
557 }
558 }
559 table_str.push(padding);
560 table_str.push(border);
561 table_str.push(newline);
562 }
563 }
564
565 // add bottom border
566 table_str.push(edge);
567 for field in 0..self.num_fields {
568 for _ in 0..field_width[field] + padding_length {
569 table_str.push(line);
570 }
571 table_str.push(edge);
572 }
573
574 table_str
575 }
576}