tabwriter-1.4.0/.cargo_vcs_info.json0000644000000001360000000000100130410ustar { "git": { "sha1": "ed1e8789569e06060c7afec9b79d719a30706a03" }, "path_in_vcs": "" }tabwriter-1.4.0/.github/workflows/ci.yml000064400000000000000000000034461046102023000163530ustar 00000000000000name: ci on: pull_request: push: branches: - master schedule: - cron: '00 01 * * *' jobs: test: name: test runs-on: ${{ matrix.os }} strategy: matrix: build: - pinned - stable - beta - nightly - macos - win-msvc - win-gnu include: - build: pinned os: ubuntu-latest rust: 1.67.0 - build: stable os: ubuntu-latest rust: stable - build: beta os: ubuntu-latest rust: beta - build: nightly os: ubuntu-latest rust: nightly - build: macos os: macos-latest rust: stable - build: win-msvc os: windows-latest rust: stable - build: win-gnu os: windows-latest rust: stable-x86_64-gnu steps: - name: Checkout repository uses: actions/checkout@v3 - name: Install Rust uses: dtolnay/rust-toolchain@master with: toolchain: ${{ matrix.rust }} - run: cargo build --verbose - run: cargo doc --verbose - run: cargo test --verbose - run: cargo test --verbose --features ansi_formatting - run: cargo build --verbose --manifest-path tabwriter-bin/Cargo.toml rustfmt: name: rustfmt runs-on: ubuntu-latest steps: - name: Checkout repository uses: actions/checkout@v3 - name: Install Rust uses: dtolnay/rust-toolchain@master with: toolchain: stable components: rustfmt - name: Install rustfmt run: rustup component add rustfmt - name: Check formatting run: | cargo fmt -- --check - name: Check formatting of tabwriter-bin run: | cargo fmt --manifest-path tabwriter-bin/Cargo.toml -- --check tabwriter-1.4.0/.gitignore000064400000000000000000000000511046102023000136150ustar 00000000000000.*.swp tags target ctags.rust Cargo.lock tabwriter-1.4.0/COPYING000064400000000000000000000001761046102023000126700ustar 00000000000000This project is dual-licensed under the Unlicense and MIT licenses. You may use this code under the terms of either license. tabwriter-1.4.0/Cargo.toml0000644000000017440000000000100110450ustar # THIS FILE IS AUTOMATICALLY GENERATED BY CARGO # # When uploading crates to the registry Cargo will automatically # "normalize" Cargo.toml files for maximal compatibility # with all versions of Cargo and also rewrite `path` dependencies # to registry (e.g., crates.io) dependencies. # # If you are reading this file be aware that the original Cargo.toml # will likely look very different (and much more reasonable). # See Cargo.toml.orig for the original contents. [package] edition = "2021" name = "tabwriter" version = "1.4.0" authors = ["Andrew Gallant "] description = "Elastic tabstops." homepage = "https://github.com/BurntSushi/tabwriter" documentation = "https://docs.rs/tabwriter" readme = "README.md" keywords = [ "tabs", "elastic", "aligned", "whitespace", "table", ] license = "Unlicense/MIT" repository = "https://github.com/BurntSushi/tabwriter" [dependencies.unicode-width] version = "0.1.10" [features] ansi_formatting = [] default = [] tabwriter-1.4.0/Cargo.toml.orig000064400000000000000000000007751046102023000145310ustar 00000000000000[package] name = "tabwriter" version = "1.4.0" #:version authors = ["Andrew Gallant "] description = "Elastic tabstops." documentation = "https://docs.rs/tabwriter" homepage = "https://github.com/BurntSushi/tabwriter" repository = "https://github.com/BurntSushi/tabwriter" readme = "README.md" keywords = ["tabs", "elastic", "aligned", "whitespace", "table"] license = "Unlicense/MIT" edition = "2021" [dependencies] unicode-width = "0.1.10" [features] default = [] ansi_formatting = [] tabwriter-1.4.0/LICENSE-MIT000064400000000000000000000020711046102023000132650ustar 00000000000000The MIT License (MIT) Copyright (c) 2015 Andrew Gallant Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions: The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. tabwriter-1.4.0/Makefile000064400000000000000000000003761046102023000132770ustar 00000000000000all: echo Nothing to do... ctags: ctags --recurse --options=ctags.rust --languages=Rust docs: cargo doc in-dir ./target/doc fix-perms rscp ./target/doc/* gopher:~/www/burntsushi.net/rustdoc/ push: git push origin master git push github master tabwriter-1.4.0/README.md000064400000000000000000000056471046102023000131240ustar 00000000000000tabwriter is a crate that implements [elastic tabstops](http://nickgravgaard.com/elastictabstops/index.html). It provides both a library for wrapping Rust `Writer`s and a small program that exposes the same functionality at the command line. [![Build status](https://github.com/BurntSushi/tabwriter/workflows/ci/badge.svg)](https://github.com/BurntSushi/tabwriter/actions) [![](http://meritbadge.herokuapp.com/tabwriter)](https://crates.io/crates/tabwriter) Dual-licensed under MIT or the [UNLICENSE](http://unlicense.org). ### Simple example of library ```rust use std::io::Write; use tabwriter::TabWriter; let mut tw = TabWriter::new(vec![]); tw.write_all(b" Bruce Springsteen\tBorn to Run Bob Seger\tNight Moves Metallica\tBlack The Boss\tDarkness on the Edge of Town ").unwrap(); tw.flush().unwrap(); let written = String::from_utf8(tw.into_inner().unwrap()).unwrap(); assert_eq!(&written, " Bruce Springsteen Born to Run Bob Seger Night Moves Metallica Black The Boss Darkness on the Edge of Town "); ``` You can see an example of *real* use in my [CSV toolkit](https://github.com/BurntSushi/xsv/blob/master/src/cmd/table.rs#L57-L60). ### Simple example of command line utility ```bash [andrew@Liger tabwriter] cat sample | sed 's/ /\\t/g' a\tb\tc abc\tmnopqrstuv\txyz abcmnoxyz\tmore text a\tb\tc [andrew@Liger tabwriter] ./target/tabwriter < sample a b c abc mnopqrstuv xyz abcmnoxyz more text a b c ``` Notice that once a column block is broken, alignment starts over again. ### Documentation The API is fully documented with some examples: [http://burntsushi.net/rustdoc/tabwriter/](http://burntsushi.net/rustdoc/tabwriter/). ### Installation This crate works with Cargo. Assuming you have Rust and [Cargo](http://crates.io/) installed, simply check out the source and run tests: ```bash git clone git://github.com/BurntSushi/tabwriter cd tabwriter cargo test ``` You can also add `tabwriter` as a dependency to your project's `Cargo.toml`: ```toml [dependencies] tabwriter = "1" ``` ### Dealing with ANSI escape codes If you want `tabwriter` to be aware of ANSI escape codes, then you should enable the `TabWriter::ansi` option. Previously this was done by enabling the crate feature `ansi_formatting`, but that feature is now deprecated. (If you use it, then `TabWriter::ansi` will be automatically enabled for you. Otherwise it is disabled by default.) ### Minimum Rust version policy This crate's minimum supported `rustc` version is `1.67.0`. The current policy is that the minimum Rust version required to use this crate can be increased in minor version updates. For example, if `crate 1.0` requires Rust 1.20.0, then `crate 1.0.z` for all values of `z` will also require Rust 1.20.0 or newer. However, `crate 1.y` for `y > 0` may require a newer minimum version of Rust. In general, this crate will be conservative with respect to the minimum supported version of Rust. tabwriter-1.4.0/UNLICENSE000064400000000000000000000022731046102023000131050ustar 00000000000000This is free and unencumbered software released into the public domain. Anyone is free to copy, modify, publish, use, compile, sell, or distribute this software, either in source code form or as a compiled binary, for any purpose, commercial or non-commercial, and by any means. In jurisdictions that recognize copyright laws, the author or authors of this software dedicate any and all copyright interest in the software to the public domain. We make this dedication for the benefit of the public at large and to the detriment of our heirs and successors. We intend this dedication to be an overt act of relinquishment in perpetuity of all present and future rights to this software under copyright law. THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. For more information, please refer to tabwriter-1.4.0/rustfmt.toml000064400000000000000000000000541046102023000142310ustar 00000000000000max_width = 79 use_small_heuristics = "max" tabwriter-1.4.0/session.vim000064400000000000000000000000701046102023000140260ustar 00000000000000au BufWritePost *.rs silent!make ctags > /dev/null 2>&1 tabwriter-1.4.0/src/lib.rs000064400000000000000000000357471046102023000135540ustar 00000000000000//! This crate provides an implementation of //! [elastic tabstops](http://nickgravgaard.com/elastictabstops/index.html). //! It is a minimal port of Go's //! [tabwriter](http://golang.org/pkg/text/tabwriter/) package. //! Namely, its main mode of operation is to wrap a `Writer` and implement //! elastic tabstops for the text written to the wrapped `Writer`. //! //! This package is also bundled with a program, `tabwriter`, //! that exposes this functionality at the command line. //! //! Here's an example that shows basic alignment: //! //! ```rust //! use std::io::Write; //! use tabwriter::TabWriter; //! //! let mut tw = TabWriter::new(vec![]); //! write!(&mut tw, " //! Bruce Springsteen\tBorn to Run //! Bob Seger\tNight Moves //! Metallica\tBlack //! The Boss\tDarkness on the Edge of Town //! ").unwrap(); //! tw.flush().unwrap(); //! //! let written = String::from_utf8(tw.into_inner().unwrap()).unwrap(); //! assert_eq!(&*written, " //! Bruce Springsteen Born to Run //! Bob Seger Night Moves //! Metallica Black //! The Boss Darkness on the Edge of Town //! "); //! ``` //! //! Note that `flush` **must** be called or else `TabWriter` may never write //! anything. This is because elastic tabstops requires knowing about future //! lines in order to align output. More precisely, all text considered in a //! single alignment must fit into memory. //! //! Here's another example that demonstrates how *only* contiguous columns //! are aligned: //! //! ```rust //! use std::io::Write; //! use tabwriter::TabWriter; //! //! let mut tw = TabWriter::new(vec![]).padding(1); //! write!(&mut tw, " //!fn foobar() {{ //! let mut x = 1+1;\t// addition //! x += 1;\t// increment in place //! let y = x * x * x * x;\t// multiply! //! //! y += 1;\t// this is another group //! y += 2 * 2;\t// that is separately aligned //!}} //!").unwrap(); //! tw.flush().unwrap(); //! //! let written = String::from_utf8(tw.into_inner().unwrap()).unwrap(); //! assert_eq!(&*written, " //!fn foobar() { //! let mut x = 1+1; // addition //! x += 1; // increment in place //! let y = x * x * x * x; // multiply! //! //! y += 1; // this is another group //! y += 2 * 2; // that is separately aligned //!} //!"); //! ``` #![deny(missing_docs)] use std::cmp; use std::error; use std::fmt; use std::io::{self, Write}; use std::iter; use std::mem; use std::str; #[cfg(test)] mod test; /// TabWriter wraps an arbitrary writer and aligns tabbed output. /// /// Elastic tabstops work by aligning *contiguous* tabbed delimited fields /// known as *column blocks*. When a line appears that breaks all contiguous /// blocks, all buffered output will be flushed to the underlying writer. /// Otherwise, output will stay buffered until `flush` is explicitly called. #[derive(Debug)] pub struct TabWriter { w: W, buf: io::Cursor>, lines: Vec>, curcell: Cell, minwidth: usize, padding: usize, alignment: Alignment, ansi: bool, tab_indent: bool, } /// `Alignment` represents how a `TabWriter` should align text within its cell. #[derive(Debug)] pub enum Alignment { /// Text should be aligned with the left edge of the cell Left, /// Text should be centered within the cell Center, /// Text should be aligned with the right edge of the cell Right, } #[derive(Debug)] struct Cell { start: usize, // offset into TabWriter.buf width: usize, // in characters size: usize, // in bytes } impl TabWriter { /// Create a new `TabWriter` from an existing `Writer`. /// /// All output written to `Writer` is passed through `TabWriter`. /// Contiguous column blocks indicated by tabs are aligned. /// /// Note that `flush` must be called to guarantee that `TabWriter` will /// write to the given writer. pub fn new(w: W) -> TabWriter { TabWriter { w, buf: io::Cursor::new(Vec::with_capacity(1024)), lines: vec![vec![]], curcell: Cell::new(0), minwidth: 2, padding: 2, alignment: Alignment::Left, ansi: cfg!(feature = "ansi_formatting"), tab_indent: false, } } /// Set the minimum width of each column. That is, all columns will have /// *at least* the size given here. If a column is smaller than `minwidth`, /// then it is padded with spaces. /// /// The default minimum width is `2`. pub fn minwidth(mut self, minwidth: usize) -> TabWriter { self.minwidth = minwidth; self } /// Set the padding between columns. All columns will be separated by /// *at least* the number of spaces indicated by `padding`. If `padding` /// is zero, then columns may run up against each other without any /// separation. /// /// The default padding is `2`. pub fn padding(mut self, padding: usize) -> TabWriter { self.padding = padding; self } /// Set the alignment of text within cells. This will effect future flushes. /// /// The default alignment is `Alignment::Left`. pub fn alignment(mut self, alignment: Alignment) -> TabWriter { self.alignment = alignment; self } /// Ignore ANSI escape codes when computing the number of display columns. /// /// This is disabled by default. (But is enabled by default when the /// deprecated `ansi_formatting` crate feature is enabled.) pub fn ansi(mut self, yes: bool) -> TabWriter { self.ansi = yes; self } /// Always use tabs for indentation columns (i.e., padding of /// leading empty cells on the left). /// /// This is disabled by default. pub fn tab_indent(mut self, yes: bool) -> TabWriter { self.tab_indent = yes; self } /// Unwraps this `TabWriter`, returning the underlying writer. /// /// This internal buffer is flushed before returning the writer. If the /// flush fails, then an error is returned. pub fn into_inner(mut self) -> Result>> { match self.flush() { Ok(()) => Ok(self.w), Err(err) => Err(IntoInnerError(self, err)), } } /// Resets the state of the aligner. Once the aligner is reset, all future /// writes will start producing a new alignment. fn reset(&mut self) { self.buf = io::Cursor::new(Vec::with_capacity(1024)); self.lines = vec![vec![]]; self.curcell = Cell::new(0); } /// Adds the bytes received into the buffer and updates the size of /// the current cell. fn add_bytes(&mut self, bytes: &[u8]) { self.curcell.size += bytes.len(); let _ = self.buf.write_all(bytes); // cannot fail } /// Ends the current cell, updates the UTF8 width of the cell and starts /// a fresh cell. fn term_curcell(&mut self) { let mut curcell = Cell::new(self.buf.position() as usize); mem::swap(&mut self.curcell, &mut curcell); if self.ansi { curcell.update_width(&self.buf.get_ref(), count_columns_ansi); } else { curcell.update_width(&self.buf.get_ref(), count_columns_noansi); } self.curline_mut().push(curcell); } /// Return a view of the current line of cells. fn curline(&mut self) -> &[Cell] { let i = self.lines.len() - 1; &*self.lines[i] } /// Return a mutable view of the current line of cells. fn curline_mut(&mut self) -> &mut Vec { let i = self.lines.len() - 1; &mut self.lines[i] } } impl Cell { fn new(start: usize) -> Cell { Cell { start, width: 0, size: 0 } } fn update_width( &mut self, buf: &[u8], count_columns: impl Fn(&[u8]) -> usize, ) { let end = self.start + self.size; self.width = count_columns(&buf[self.start..end]); } } impl io::Write for TabWriter { fn write(&mut self, buf: &[u8]) -> io::Result { let mut lastterm = 0usize; for (i, &c) in buf.iter().enumerate() { match c { b'\t' | b'\n' => { self.add_bytes(&buf[lastterm..i]); self.term_curcell(); lastterm = i + 1; if c == b'\n' { let ncells = self.curline().len(); self.lines.push(vec![]); // Having a single cell means that *all* previous // columns have been broken, so we should just flush. if ncells == 1 { self.flush()?; } } } _ => {} } } self.add_bytes(&buf[lastterm..]); Ok(buf.len()) } fn flush(&mut self) -> io::Result<()> { if self.curcell.size > 0 { self.term_curcell(); } let widths = cell_widths(&self.lines, self.minwidth); // This is a trick to avoid allocating padding for every cell. // Just allocate the most we'll ever need and borrow from it. let biggest_width = widths .iter() .map(|ws| ws.iter().map(|&w| w).max().unwrap_or(0)) .max() .unwrap_or(0); let padding: String = iter::repeat(' ').take(biggest_width + self.padding).collect(); let mut first = true; for (line, widths) in self.lines.iter().zip(widths.iter()) { if !first { self.w.write_all(b"\n")?; } else { first = false } let mut use_tabs = self.tab_indent; for (i, cell) in line.iter().enumerate() { let bytes = &self.buf.get_ref()[cell.start..cell.start + cell.size]; if i >= widths.len() { // There is no width for the last column assert_eq!(i, line.len() - 1); self.w.write_all(bytes)?; } else { if use_tabs && cell.size == 0 { write!(&mut self.w, "\t")?; continue; } use_tabs = false; assert!(widths[i] >= cell.width); let extra_space = widths[i] - cell.width; let (left_spaces, mut right_spaces) = match self.alignment { Alignment::Left => (0, extra_space), Alignment::Right => (extra_space, 0), Alignment::Center => { (extra_space / 2, extra_space - extra_space / 2) } }; right_spaces += self.padding; write!(&mut self.w, "{}", &padding[0..left_spaces])?; self.w.write_all(bytes)?; write!(&mut self.w, "{}", &padding[0..right_spaces])?; } } } self.reset(); Ok(()) } } /// An error returned by `into_inner`. /// /// This combines the error that happened while flushing the buffer with the /// `TabWriter` itself. pub struct IntoInnerError(W, io::Error); impl IntoInnerError { /// Returns the error which caused the `into_error()` call to fail. pub fn error(&self) -> &io::Error { &self.1 } /// Returns the `TabWriter` instance which generated the error. pub fn into_inner(self) -> W { self.0 } } impl fmt::Debug for IntoInnerError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.error().fmt(f) } } impl fmt::Display for IntoInnerError { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { self.error().fmt(f) } } impl error::Error for IntoInnerError { #[allow(deprecated)] fn description(&self) -> &str { self.error().description() } fn cause(&self) -> Option<&dyn error::Error> { Some(self.error()) } } fn cell_widths(lines: &Vec>, minwidth: usize) -> Vec> { // Naively, this algorithm looks like it could be O(n^2m) where `n` is // the number of lines and `m` is the number of contiguous columns. // // However, I claim that it is actually O(nm). That is, the width for // every contiguous column is computed exactly once. let mut ws: Vec<_> = (0..lines.len()).map(|_| vec![]).collect(); for (i, iline) in lines.iter().enumerate() { if iline.is_empty() { continue; } for col in ws[i].len()..(iline.len() - 1) { let mut width = minwidth; let mut contig_count = 0; for line in lines[i..].iter() { if col + 1 >= line.len() { // ignores last column break; } contig_count += 1; width = cmp::max(width, line[col].width); } assert!(contig_count >= 1); for j in i..(i + contig_count) { ws[j].push(width); } } } ws } fn count_columns_noansi(bytes: &[u8]) -> usize { use unicode_width::UnicodeWidthChar; // If we have a Unicode string, then attempt to guess the number of // *display* columns used. match str::from_utf8(bytes) { Err(_) => bytes.len(), Ok(s) => s .chars() .map(|c| UnicodeWidthChar::width(c).unwrap_or(0)) .fold(0, |sum, width| sum + width), } } fn count_columns_ansi(bytes: &[u8]) -> usize { use unicode_width::UnicodeWidthChar; // If we have a Unicode string, then attempt to guess the number of // *display* columns used. match str::from_utf8(bytes) { Err(_) => bytes.len(), Ok(s) => strip_formatting(s) .chars() .map(|c| UnicodeWidthChar::width(c).unwrap_or(0)) .fold(0, |sum, width| sum + width), } } fn strip_formatting<'t>(input: &'t str) -> std::borrow::Cow<'t, str> { let mut escapes = find_ansi_escapes(input).peekable(); if escapes.peek().is_none() { return std::borrow::Cow::Borrowed(input); } let mut without_escapes = String::with_capacity(input.len()); let mut last_end = 0; for mat in escapes { without_escapes.push_str(&input[last_end..mat.start]); last_end = mat.end; } without_escapes.push_str(&input[last_end..]); std::borrow::Cow::Owned(without_escapes) } fn find_ansi_escapes<'t>( input: &'t str, ) -> impl Iterator> + 't { const ESCAPE_PREFIX: &str = "\x1B["; let mut last_end = 0; std::iter::from_fn(move || { let start = last_end + input[last_end..].match_indices(ESCAPE_PREFIX).next()?.0; let after_prefix = start + ESCAPE_PREFIX.len(); let end = after_prefix + input[after_prefix..].match_indices('m').next()?.0 + 1; last_end = end; Some(start..end) }) } tabwriter-1.4.0/src/test.rs000064400000000000000000000140611046102023000137470ustar 00000000000000use crate::Alignment; use crate::TabWriter; use std::io::Write; fn ordie(r: Result) -> T { match r { Ok(r) => r, Err(e) => panic!("{}", e.to_string()), } } fn readable_str(s: &str) -> String { s.replace(" ", "·").replace("\t", "") } fn tabw() -> TabWriter> { TabWriter::new(Vec::new()) } fn tabify(mut tw: TabWriter>, s: &str) -> String { ordie(write!(&mut tw, "{}", s)); ordie(tw.flush()); ordie(String::from_utf8(tw.into_inner().unwrap())) } fn iseq(tw: TabWriter>, s: &str, expected: &str) { let written = tabify(tw, s); if expected != written { panic!( "\n\nexpected:\n-----\n{}\n-----\ngot:\n-----\n{}\n-----\n\n", readable_str(expected), readable_str(&written) ); } } #[test] fn test_no_cells() { iseq(tabw(), "foo\nbar\nfubar", "foo\nbar\nfubar"); } #[test] fn test_no_cells_trailing() { iseq(tabw(), "foo\nbar\nfubar\n", "foo\nbar\nfubar\n"); } #[test] fn test_no_cells_prior() { iseq(tabw(), "\nfoo\nbar\nfubar", "\nfoo\nbar\nfubar"); } #[test] fn test_empty() { iseq(tabw(), "", ""); } #[test] fn test_empty_lines() { iseq(tabw(), "\n\n\n\n", "\n\n\n\n"); } #[test] fn test_empty_cell() { iseq(tabw().padding(0).minwidth(2), "\t\n", " \n"); } #[test] fn test_empty_cell_no_min() { iseq(tabw().padding(0).minwidth(0), "\t\n", "\n"); } #[test] fn test_empty_cells() { iseq(tabw().padding(0).minwidth(2), "\t\t\n", " \n"); } #[test] fn test_empty_cells_no_min() { iseq(tabw().padding(0).minwidth(0), "\t\t\n", "\n"); } #[test] fn test_empty_cells_ignore_trailing() { iseq(tabw().padding(0).minwidth(2), "\t\t\t", " "); } #[test] fn test_one_cell() { iseq(tabw().padding(2).minwidth(2), "a\tb\nxx\tyy", "a b\nxx yy"); } #[test] fn test_one_cell_right() { iseq( tabw().padding(2).minwidth(2).alignment(Alignment::Right), "a\tb\nxx\tyy", " a b\nxx yy", ); } #[test] fn test_one_cell_center() { iseq( tabw().padding(2).minwidth(2).alignment(Alignment::Center), "a\tb\nxx\tyy", "a b\nxx yy", ); } #[test] fn test_no_padding() { iseq(tabw().padding(0).minwidth(2), "a\tb\nxx\tyy", "a b\nxxyy"); } // See: https://github.com/BurntSushi/tabwriter/issues/26 #[test] fn test_no_padding_one_row() { iseq(tabw().padding(0).minwidth(2), "a\tb\n", "a b\n"); iseq(tabw().padding(0).minwidth(1), "a\tb\n", "ab\n"); iseq(tabw().padding(0).minwidth(0), "a\tb\n", "ab\n"); } #[test] fn test_minwidth() { iseq(tabw().minwidth(5).padding(0), "a\tb\nxx\tyy", "a b\nxx yy"); } #[test] fn test_contiguous_columns() { iseq( tabw().padding(1).minwidth(0), "x\tfoo\tx\nx\tfoofoo\tx\n\nx\tfoofoofoo\tx", "x foo x\nx foofoo x\n\nx foofoofoo x", ); } #[test] fn test_table_right() { iseq( tabw().padding(1).minwidth(0).alignment(Alignment::Right), "x\tfoo\txx\t\nxx\tfoofoo\tx\t\n", " x foo xx \nxx foofoo x \n", ); } #[test] fn test_table_center() { iseq( tabw().padding(1).minwidth(0).alignment(Alignment::Center), "x\tfoo\txx\t\nxx\tfoofoo\tx\t\n", "x foo xx \nxx foofoo x \n", ); } #[test] fn test_contiguous_columns_right() { iseq( tabw().padding(1).minwidth(0).alignment(Alignment::Right), "x\tfoo\tx\nx\tfoofoo\tx\n\nx\tfoofoofoo\tx", "x foo x\nx foofoo x\n\nx foofoofoo x", ); } #[test] fn test_contiguous_columns_center() { iseq( tabw().padding(1).minwidth(0).alignment(Alignment::Center), "x\tfoo\tx\nx\tfoofoo\tx\n\nx\tfoofoofoo\tx", "x foo x\nx foofoo x\n\nx foofoofoo x", ); } #[test] fn test_unicode() { iseq( tabw().padding(2).minwidth(2), "a\tÞykkvibær\tz\naaaa\tïn Bou Chella\tzzzz\na\tBâb el Ahmar\tz", "a Þykkvibær z\n\ aaaa ïn Bou Chella zzzz\n\ a Bâb el Ahmar z", ) } #[test] fn test_contiguous_columns_complex() { iseq( tabw().padding(1).minwidth(3), " fn foobar() { let mut x = 1+1; // addition x += 1; // increment in place let y = x * x * x * x; // multiply! y += 1; // this is another group y += 2 * 2; // that is separately aligned } ", " fn foobar() { let mut x = 1+1; // addition x += 1; // increment in place let y = x * x * x * x; // multiply! y += 1; // this is another group y += 2 * 2; // that is separately aligned } ", ); } // This tests that ANSI formatting is handled automatically when the ansi_formatting // feature is enabled without any other configuration. #[test] #[cfg(feature = "ansi_formatting")] fn ansi_formatting_by_feature() { let output = "foo\tbar\tfoobar\n\ \x1b[31mföÅ\x1b[0m\t\x1b[32mbär\x1b[0m\t\x1b[36mfoobar\x1b[0m\n\ \x1b[34mfoo\tbar\tfoobar\n\x1b[0m"; iseq( tabw(), &output[..], "foo bar foobar\n\ \x1b[31mföÅ\x1b[0m \x1b[32mbär\x1b[0m \x1b[36mfoobar\x1b[0m\n\ \x1b[34mfoo bar foobar\n\x1b[0m", ) } // This tests that ANSI formatting is handled when explicitly opted into, // regardless of whether the ansi_formatting feature is enabled. #[test] fn ansi_formatting_by_config() { let output = "foo\tbar\tfoobar\n\ \x1b[31mföÅ\x1b[0m\t\x1b[32mbär\x1b[0m\t\x1b[36mfoobar\x1b[0m\n\ \x1b[34mfoo\tbar\tfoobar\n\x1b[0m"; iseq( tabw().ansi(true), &output[..], "foo bar foobar\n\ \x1b[31mföÅ\x1b[0m \x1b[32mbär\x1b[0m \x1b[36mfoobar\x1b[0m\n\ \x1b[34mfoo bar foobar\n\x1b[0m", ) } #[test] fn tab_indent() { iseq( tabw().tab_indent(true).padding(1), " type cell struct { \tsize\tint\t// cell size in bytes \twidth\tint\t// cell width in runes \thtab\tbool\t// true if the cell is terminated by an htab ('\\t') } ", " type cell struct { \tsize int // cell size in bytes \twidth int // cell width in runes \thtab bool // true if the cell is terminated by an htab ('\\t') } ", ) }