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
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 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
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
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
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
//! # TablePrint
//!
//! TablePrint is a library for tables, including features such as:
//!  * Printing to the terminal.
//!  * Export to HTML
//!  * Export to CSV
//!  * Wrap

pub mod error;
pub use error::*;

#[derive(Debug, Clone, Hash, Eq, PartialEq, Ord, PartialOrd, Default)]
pub struct Table {
    /// These are the table headings.
    /// For example, if counting days of the week, this may be
    /// `["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
	pub headers: Vec<String>,
	/// This is the main content of the table.
	/// This goes below the heading.
	///
	/// The unnested vec is the X,
	/// the nested is the Y.
	pub content: Vec<Vec<String>>,
}

impl Table {
	/// Creates a new TablePrint.
	pub fn new(
    	headers: Vec<String>
	) -> Self {
		Self {
    		content: Vec::with_capacity(headers.len()),
    		headers,
		}
	}

	/// Creates a TablePrint with content
	pub fn with_content (
		headers: Vec<String>,
		content: Vec<Vec<String>>
	) -> Self {
		Self {
			headers,
			content
		}
	}

	/// Inserts a row into the table.
	pub fn insert_row(
		&mut self,
		row: Vec<String>
	) {
    	// Checks if an allocation is needed
		while self.content.len() < row.len() {
			self.content.push(Vec::new());
		}

		// Now we can just pushn it to where it needs to go.
		for (i, element) in row.into_iter().enumerate() {
			self.content[i].push(element);
		}
	}

	/// Retrieves a row from the table, this is given as a reference.
	///
	/// If you need mutability have a look at [`Self::get_row_mut`]
	pub fn get_row(&self, row: usize) -> Option<Vec<&str>> {
		if row > self.get_row_count() {
			return None
		}
    	
    	// Creates a vec which we will store out row in.
		let mut vec = Vec::with_capacity(self.headers.len());

		// Iterates over the column
		for column in &self.content {
    		// Attempts to get the row
			if let Some(item) = column.get(row) {
    			vec.push(item.as_str());
			}
		}

		Some(vec)
	}

	/// Retrieves a mutable row from the table, this is given as a reference.
	pub fn get_row_mut(&mut self, row: usize) -> Option<Vec<&mut str>> {
    	// Creates a vec which we will store out row in.
		let mut vec = Vec::with_capacity(self.headers.len());

		// Iterates over the column
		for column in &mut self.content {
    		// Attempts to get the row
			match column.get_mut(row) {
    			Some(item) => vec.push(item.as_mut_str()),
				None => return None,
			}
		}

		Some(vec)
	}

	/// Retrieves the column count, not including the header.
	pub fn get_column_count(&self) -> usize {
		self.content.len()
	}
	
	/// Retrieves the count of rows, not including the header.
	pub fn get_row_count(&self) -> usize {
    	// The count
		let mut count = 0;

		// Loop through the content
		for column in &self.content {
			count = usize::max(count, column.len());
		}

		count
	}

	/// Turns this into a CSV string.
	pub fn get_csv(&self) -> String {
    	// Create a string to store the value in
		let mut string = String::new();

		// First lets do the heading
		for (i, header) in self.headers.iter().enumerate() {
			string.push_str(
    			&format!(
        			"\"{}\"",
        			header.replace("\"", "\"\"")
    			)
			);
			if i != self.headers.len() - 1 {
				string.push(',');
			}
		}

		// Add a newline
		string.push('\n');

		// And now the rows.
		for i in 0..self.get_row_count() {
    		if let Some(row) = self.get_row(i) {
        		for (i, element) in row.iter().enumerate() {
        			string.push_str(
            			&format!(
                			"\"{}\"",
                			element.replace("\"", "\"\"")
            			)
        			);
        			if i != row.len() - 1 {
    					string.push(',');
        			}
        		}
        		string.push('\n');
    		}
		}

		string
	}

	/// Turns this into a HTML table. This function generates a complete
	/// HTML document. If only a singal element is required than have a
	/// look at [`Self::get_html_element`]
	pub fn get_html(&self, embeded_css: Option<&str>) -> String {
    	format!(
        	include_str!("../data/template.html"),
        	// Check if we have any CSS
        	style_info = &match embeded_css {
				None => String::new(),
				Some(css) => format!("<style\n>{}\n</style>\n", css),
        	},
        	// Insert the table
        	table = self.get_html_element()
    	)
	}

	/// Turns this into a single HTML element.
	pub fn get_html_element(&self) -> String {
    	// To store the string
		let mut string = String::new();

		// Do the headers
		string.push_str("<tr>\n");
		for header in &self.headers {
    		string.push_str("<th>");
    		// Push and Escape
			string.push_str(
    			&header.replace("&", "&amp")
    			.replace("<", "&lt")
    			.replace(">", "&gt")
    			.replace("\n", "<br>")
			);
			string.push_str("</th>\n");
		}
		string.push_str("</tr>\n");

		// Rows
		for i in 0..self.get_row_count() {
    		if let Some(row) = self.get_row(i) {
        		string.push_str("<tr>");
        		for element in row {
            		string.push_str("<td>");
        			string.push_str(
            			&element.replace("&", "&amp")
            			.replace("<", "&lt")
            			.replace(">", "&gt")
            			.replace("\n", "<br>")
        			);
        			string.push_str("</td>\n");
        		}
        		string.push_str("</tr>\n");
    		}
		}
		
		// Add the final tags
		format!("<table>{}</table>", string)
	}
	
	/// Gets a pretty version of this. Width is the max width this can take up
	/// and should probably just be the terminal width.
	pub fn get_pretty(&self, width: usize) -> Result<String, PrettyError>  {
    	// To prevent a divide by zero error
		if width == 0 {
			return Err(PrettyError::ZeroedWidth);
		}
    	
		// Calcualtes how much space we have for each column.
    	let width = width / usize::max(self.content.len(), self.headers.len());

    	// Incase there is no room.
    	//
    	// We use 4 because we need borders around the
    	// elements so it looks *pretty*.
		if width <= 4 {
			return Err(PrettyError::NoSpaceWidth);
		}
    	
    	// Create a string which we are going to print to.
		let mut string = String::new();

		// Headers

		let mut prev_row_len = self.headers.len();
		
    	// Print the line
		string.push_str(
			&get_pretty_line(
				prev_row_len,
				width,
				"="
			)
		);
		string.push_str(
    		&get_pretty_row(
        		self
        			.headers.
        			iter().
        			map(String::as_str).
        			collect::<Vec<&str>>().
        			as_slice(),
        		width
    		)
		);

		// Rows
		for i in 0..self.get_row_count() {
    		if let Some(row) = self.get_row(i) {
            	// Print the line
				string.push_str(
    				&get_pretty_line(
        				usize::max(row.len(), prev_row_len),
        				width,
        				match i {
            				0 => "=",
            				_ => "-"
            			},
    				)
				);

				// Print the content
    			string.push_str(&get_pretty_row(row.as_slice(), width));

    			// For the next row.
    			prev_row_len = row.len();
    		}
		}

		// Print the final line
		string.push_str(&get_pretty_line(
			prev_row_len,
			width,
			"-",
		));
		
		Ok(string)
	}
}


fn get_pretty_line(cells: usize, cell_width: usize, chr: &str) -> String {
	// Creates the string
	//
	// We use cell_width + 3 because of the join and the padding
	let mut string = String::with_capacity(cells * (cell_width+3) + 1);

	// Bulk of it
	let line = chr.repeat(cell_width-1);
	let section = format!("+{}", line).repeat(cells);
	string.push_str(&section);
	
	// Final separator and newline
	string.push_str("+\n");

	string
}

fn get_pretty_row(row: &[&str], cell_width: usize) -> String {
	if row.is_empty() {
		return String::new();
	}
    
    // String to store the row
	let mut string = String::new();

	// Vector to store the wrapped rows in.
	let mut wrapped_rows: Vec<Vec<String>> =
		Vec::with_capacity(row.len());

	// Loop through the rows
	for i in row {
		let wrapped = textwrap::fill(i, cell_width-4);
		// Push the rows to the row.
		wrapped_rows.push(
			wrapped.split('\n').map(String::from).collect()
		);
	}

	let mut height = 0_usize;
	
	// Now we find out the height of the rows.
	for i in &wrapped_rows {
		// Gets the biggest number
		height = usize::max(height, i.len());
	}

	// Now we can get to adding them to actually printing them.
	for j in 0..height {
		// Loops through everything in the wrapped rows.
		// We add the i-th line of each to the string.
    	for i in &wrapped_rows {
			match i.get(j) {
    			Some (line) => string.push_str(
					&format!(
						"| {}{}",
						line,
						" ".repeat((cell_width-line.len())-2)
					)
    			),
    			None => {
        			string.push_str("| ");
        			string.push_str(&" ".repeat(cell_width-2));
    			}
			}
		}
    	// Print the end of the table
    	string.push_str("|\n");
	}

	string
}