tabwriter-1.1.0/.gitignore010064400017500000144000000000511274016737400137300ustar0000000000000000.*.swp tags target ctags.rust Cargo.lock tabwriter-1.1.0/.travis.yml010064400017500000144000000006021331472400100140330ustar0000000000000000language: rust rust: - 1.20.0 - stable - beta - nightly script: - cargo build --verbose - cargo build --verbose --features ansi_formatting - cargo doc - cargo test --verbose - cargo test --verbose --features ansi_formatting - if [ "$TRAVIS_RUST_VERSION" = "nightly" ]; then cargo bench --verbose; cargo bench --verbose --features ansi_formatting; fi tabwriter-1.1.0/COPYING010064400017500000144000000001761274016737400130030ustar0000000000000000This project is dual-licensed under the Unlicense and MIT licenses. You may use this code under the terms of either license. tabwriter-1.1.0/Cargo.toml.orig010066400017500000144000000011331331472406500146250ustar0000000000000000[package] name = "tabwriter" version = "1.1.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" [dependencies] unicode-width = "0.1" regex = { version = "1", optional = true } lazy_static = { version = "1", optional = true} [features] default = [] ansi_formatting = ["regex", "lazy_static"] tabwriter-1.1.0/Cargo.toml0000644000000021650000000000000110770ustar00# 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 believe there's an error in this file please file an # issue against the rust-lang/cargo repository. If you're # editing this file be aware that the upstream Cargo.toml # will likely look very different (and much more reasonable) [package] name = "tabwriter" version = "1.1.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.lazy_static] version = "1" optional = true [dependencies.regex] version = "1" optional = true [dependencies.unicode-width] version = "0.1" [features] ansi_formatting = ["regex", "lazy_static"] default = [] tabwriter-1.1.0/LICENSE-MIT010064400017500000144000000020711274016737400134000ustar0000000000000000The 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.1.0/Makefile010064400017500000144000000003761274016737400134120ustar0000000000000000all: 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.1.0/README.md010066400017500000144000000042431324776623300132320ustar0000000000000000tabwriter 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://api.travis-ci.org/BurntSushi/tabwriter.png)](https://travis-ci.org/BurntSushi/tabwriter) [![](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 tabwriter::TabWriter; let mut tw = TabWriter::new(vec![]); tw.write_str(" 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.as_slice(), " 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 checkout 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 compile it with the `ansi_formatting` feature enabled. tabwriter-1.1.0/UNLICENSE010064400017500000144000000022731274016737400132200ustar0000000000000000This 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.1.0/session.vim010064400017500000144000000000701274016737400141410ustar0000000000000000au BufWritePost *.rs silent!make ctags > /dev/null 2>&1 tabwriter-1.1.0/src/lib.rs010064400017500000144000000302101303302465400136300ustar0000000000000000//! 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)] #[cfg(feature = "ansi_formatting")] #[macro_use] extern crate lazy_static; #[cfg(feature = "ansi_formatting")] extern crate regex; extern crate unicode_width; use std::cmp; use std::error; use std::fmt; use std::io::{self, Write}; use std::iter; use std::mem; use std::str; #[cfg(feature = "ansi_formatting")] use regex::Regex; #[cfg(feature = "ansi_formatting")] use std::borrow::Cow; #[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, } #[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: w, buf: io::Cursor::new(Vec::with_capacity(1024)), lines: vec!(vec!()), curcell: Cell::new(0), minwidth: 2, padding: 2, } } /// 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 passed 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 } /// 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); curcell.update_width(&self.buf.get_ref()); 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: start, width: 0, size: 0 } } fn update_width(&mut self, buf: &[u8]) { let end = self.start + self.size; self.width = display_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 { try!(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 { try!(self.w.write_all(b"\n")); } else { first = false } for (i, cell) in line.iter().enumerate() { let bytes = &self.buf.get_ref()[cell.start..cell.start + cell.size]; try!(self.w.write_all(bytes)); if i >= widths.len() { assert_eq!(i, line.len()-1); } else { assert!(widths[i] >= cell.width); let padsize = self.padding + widths[i] - cell.width; try!(write!(&mut self.w, "{}", &padding[0..padsize])); } } } 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 { fn description(&self) -> &str { self.error().description() } fn cause(&self) -> Option<&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 } #[cfg(not(feature = "ansi_formatting"))] fn display_columns(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), } } #[cfg(feature = "ansi_formatting")] fn display_columns(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), } } #[cfg(feature = "ansi_formatting")] fn strip_formatting<'t>(input: &'t str) -> Cow<'t, str> { // use lazy_static to avoid compiling the regex every time // this function is called lazy_static! { static ref RE: regex::Regex = Regex::new(r#"\x1B\[.+?m"#).unwrap(); } RE.replace_all(input, "") } tabwriter-1.1.0/src/test.rs010066400017500000144000000065171324776617100140760ustar0000000000000000use std::io::Write; use TabWriter; fn ordie(r: Result) -> T { match r { Ok(r) => r, Err(e) => panic!("{}", e.to_string()), } } fn readable_str(s: &str) -> String { s.replace(" ", "·") } 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_no_padding() { iseq(tabw().padding(0).minwidth(2), "a\tb\nxx\tyy", "a b\nxxyy"); } #[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_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 } "); } #[test] #[cfg(feature = "ansi_formatting")] fn test_ansi_formatting() { 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") }