Repository: zhiburt/tabled
Pull request #116 (2022-04-27 16:06:16)

Add Index support


Patch:
diff --git a/examples/builder.rs b/examples/builder.rs
index 169e6c2..3b0ec78 100644
--- a/examples/builder.rs
+++ b/examples/builder.rs
@@ -9,9 +9,9 @@ fn main() {
 
     let oceans = ["Atlantic", "Pacific", "Indian", "Southern", "Arctic"];
 
-    let mut builder = tabled::builder::Builder::default().set_header(["#", "Ocean"]);
+    let mut builder = tabled::builder::Builder::default().set_columns(["#", "Ocean"]);
     for (i, ocean) in oceans.iter().enumerate() {
-        builder = builder.add_row([i.to_string(), ocean.to_string()]);
+        builder = builder.add_record([i.to_string(), ocean.to_string()]);
     }
 
     let table = builder
diff --git a/examples/index.rs b/examples/index.rs
new file mode 100644
index 0000000..7ce75b0
--- /dev/null
+++ b/examples/index.rs
@@ -0,0 +1,53 @@
+//! The example can be run by this command
+//! `cargo run --example index`
+
+use tabled::{Style, Table, Tabled};
+
+#[derive(Tabled)]
+struct Distribution {
+    name: &'static str,
+    #[tabled(display_with = "display_based_on")]
+    based_on: Option<&'static str>,
+    is_active: bool,
+    is_cool: bool,
+}
+
+fn display_based_on(o: &Option<&'static str>) -> String {
+    match o {
+        &Some(s) => s.into(),
+        None => "Unknown".into(),
+    }
+}
+
+fn main() {
+    let data = [
+        Distribution {
+            name: "Manjaro",
+            based_on: Some("Arch"),
+            is_cool: true,
+            is_active: true,
+        },
+        Distribution {
+            name: "Debian",
+            based_on: None,
+            is_cool: true,
+            is_active: true,
+        },
+        Distribution {
+            name: "Debian",
+            based_on: None,
+            is_cool: true,
+            is_active: true,
+        },
+    ];
+
+    let table = Table::builder(&data)
+        .index()
+        .set_index(0)
+        .set_name(None)
+        .transpose()
+        .build()
+        .with(Style::modern());
+
+    println!("{}", table);
+}
diff --git a/src/builder.rs b/src/builder.rs
index 5ef68f7..5c63f42 100644
--- a/src/builder.rs
+++ b/src/builder.rs
@@ -1,5 +1,62 @@
 //! Builder module provides a [Builder] type which helps building
 //! a [Table] dynamically.
+//!
+//! It also contains [IndexBuilder] which can help to build a table with index.
+//!
+//! # Example
+//!
+//! Here's an example of [IndexBuilder] usage
+//!
+//! ```
+//! use tabled::{Table, Tabled, Style};
+//!
+//! #[derive(Tabled)]
+//! struct Mission {
+//!     name: &'static str,
+//!     #[tabled(inline)]
+//!     status: Status,
+//! }
+//!
+//! #[derive(Tabled)]
+//! enum Status {
+//!     Complete,
+//!     Started,
+//!     Ready,
+//!     Unknown,
+//! }
+//!
+//! let data = [
+//!     Mission { name: "Algebra", status: Status::Unknown },
+//!     Mission { name: "Apolo", status: Status::Complete },
+//! ];
+//!
+//! let table = Table::builder(&data)
+//!     .index()
+//!     .set_index(0)
+//!     .set_name(None)
+//!     .transpose()
+//!     .build()
+//!     .with(Style::modern());
+//!
+//! println!("{}", table);
+//!
+//! assert_eq!(
+//!     table.to_string(),
+//!     concat!(
+//!         "┌──────────┬─────────┬───────┐\n",
+//!         "│          │ Algebra │ Apolo │\n",
+//!         "├──────────┼─────────┼───────┤\n",
+//!         "│ Complete │         │   +   │\n",
+//!         "├──────────┼─────────┼───────┤\n",
+//!         "│ Started  │         │       │\n",
+//!         "├──────────┼─────────┼───────┤\n",
+//!         "│  Ready   │         │       │\n",
+//!         "├──────────┼─────────┼───────┤\n",
+//!         "│ Unknown  │    +    │       │\n",
+//!         "└──────────┴─────────┴───────┘\n",
+//!    ),
+//! )
+//! ```
 
 use std::{fmt::Display, iter::FromIterator};
 
@@ -14,8 +71,8 @@ use crate::{Style, Table};
 /// ```rust
 /// use tabled::builder::Builder;
 /// let table = Builder::default()
-///     .set_header(["index", "measure", "value"])
-///     .add_row(["0", "weight", "0.443"])
+///     .set_columns(["index", "measure", "value"])
+///     .add_record(["0", "weight", "0.443"])
 ///     .build();
 ///
 /// println!("{}", table);
@@ -38,10 +95,10 @@ use crate::{Style, Table};
 /// ```
 #[derive(Debug, Default, Clone)]
 pub struct Builder {
-    /// A header row.
-    headers: Option<Vec<String>>,
     /// A list of rows.
-    rows: Vec<Vec<String>>,
+    records: Vec<Vec<String>>,
+    /// A columns row.
+    columns: Option<Vec<String>>,
     /// A number of columns.
     size: usize,
     /// A content of cells which are created in case rows has different length.
@@ -61,39 +118,39 @@ impl Builder {
     /// ```rust
     /// use tabled::builder::Builder;
     /// let builder = Builder::default()
-    ///     .set_header(0..3)
-    ///     .add_row(["i", "surname", "lastname"]);
+    ///     .set_columns(0..3)
+    ///     .add_record(["i", "surname", "lastname"]);
     /// ```
-    pub fn set_header<H, T>(mut self, header: H) -> Self
+    pub fn set_columns<H, T>(mut self, columns: H) -> Self
     where
         H: IntoIterator<Item = T>,
         T: Display,
     {
-        let header: Vec<String> = header.into_iter().map(|t| t.to_string()).collect();
-        self.update_size(header.len());
-        self.headers = Some(header);
+        let columns: Vec<String> = columns.into_iter().map(|t| t.to_string()).collect();
+        self.update_size(columns.len());
+        self.columns = Some(columns);
 
         self
     }
 
     /// Adds a row to a [Table].
     ///
-    /// If [Self::set_header] is not set the first row will be considered a header.
+    /// If [Self::set_columns] is not set the first row will be considered a header.
     ///
     /// ```rust
     /// use tabled::builder::Builder;
     /// let builder = Builder::default()
-    ///     .add_row(0..3)
-    ///     .add_row(["i", "surname", "lastname"]);
+    ///     .add_record(0..3)
+    ///     .add_record(["i", "surname", "lastname"]);
     /// ```
-    pub fn add_row<R, T>(mut self, row: R) -> Self
+    pub fn add_record<R, T>(mut self, record: R) -> Self
     where
         R: IntoIterator<Item = T>,
         T: Display,
     {
-        let row: Vec<String> = row.into_iter().map(|t| t.to_string()).collect();
+        let row: Vec<String> = record.into_iter().map(|t| t.to_string()).collect();
         self.update_size(row.len());
-        self.rows.push(row);
+        self.records.push(row);
 
         self
     }
@@ -105,8 +162,8 @@ impl Builder {
     /// use tabled::builder::Builder;
     /// let table = Builder::default()
     ///     .set_default_text("undefined")
-    ///     .set_header(0..3)
-    ///     .add_row(["i"])
+    ///     .set_columns(0..3)
+    ///     .add_record(["i"])
     ///     .build();
     /// ```
     pub fn set_default_text<T: Into<String>>(mut self, text: T) -> Self {
@@ -119,13 +176,42 @@ impl Builder {
     /// ```rust
     /// use tabled::builder::Builder;
     /// let table = Builder::default()
-    ///     .set_header(["i", "column1", "column2"])
-    ///     .add_row(["0", "value1", "value2"])
+    ///     .set_columns(["i", "column1", "column2"])
+    ///     .add_record(["0", "value1", "value2"])
     ///     .build();
     /// ```
     pub fn build(mut self) -> Table {
         self.fix_rows();
-        build_table(self.headers, self.rows, self.size)
+        build_table(self.columns, self.records, self.size)
+    }
+
+    /// Add an index to the [Table].
+    ///
+    /// Default index is a range 0-N where N is amount of records.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// use tabled::Table;
+    /// let table = Table::builder(&["Hello", "World", "!"])
+    ///     .index()
+    ///     .build();
+    ///
+    /// assert_eq!(
+    ///     table.to_string(),
+    ///     "+---+-------+\n\
+    ///      |   | &str  |\n\
+    ///      +---+-------+\n\
+    ///      | 0 | Hello |\n\
+    ///      +---+-------+\n\
+    ///      | 1 | World |\n\
+    ///      +---+-------+\n\
+    ///      | 2 |   !   |\n\
+    ///      +---+-------+\n"
+    /// )
+    /// ```
+    pub fn index(self) -> IndexBuilder {
+        IndexBuilder::new(self)
     }
 
     fn update_size(&mut self, size: usize) {
@@ -137,13 +223,13 @@ impl Builder {
     fn fix_rows(&mut self) {
         let empty_cell_text = self.empty_cell_text.clone().unwrap_or_default();
 
-        if let Some(header) = self.headers.as_mut() {
+        if let Some(header) = self.columns.as_mut() {
             if self.size > header.len() {
                 append_vec(header, self.size - header.len(), empty_cell_text.clone());
             }
         }
 
-        for row in self.rows.iter_mut() {
+        for row in self.records.iter_mut() {
             if self.size > row.len() {
                 append_vec(row, self.size - row.len(), empty_cell_text.clone());
             }
@@ -159,7 +245,7 @@ where
     fn from_iter<T: IntoIterator<Item = R>>(iter: T) -> Self {
         let mut builder = Self::default();
         for row in iter {
-            builder = builder.add_row(row);
+            builder = builder.add_record(row);
         }
 
         builder
@@ -173,15 +259,15 @@ where
     fn extend<T: IntoIterator<Item = D>>(&mut self, iter: T) {
         let row: Vec<String> = iter.into_iter().map(|t| t.to_string()).collect();
         self.update_size(row.len());
-        self.rows.push(row);
+        self.records.push(row);
     }
 }
 
 impl From<Vec<Vec<String>>> for Builder {
-    fn from(rows: Vec<Vec<String>>) -> Self {
-        let max_row_length = rows.iter().map(|row| row.len()).max().unwrap_or(0);
+    fn from(records: Vec<Vec<String>>) -> Self {
+        let max_row_length = records.iter().map(|row| row.len()).max().unwrap_or(0);
         Self {
-            rows,
+            records,
             size: max_row_length,
             ..Default::default()
         }
@@ -189,23 +275,31 @@ impl From<Vec<Vec<String>>> for Builder {
 }
 
 /// Building [Table] from ordinary data.
-fn build_table(header: Option<Vec<String>>, rows: Vec<Vec<String>>, count_columns: usize) -> Table {
-    let grid = build_grid(header, rows, count_columns);
+fn build_table(
+    columns: Option<Vec<String>>,
+    records: Vec<Vec<String>>,
+    count_columns: usize,
+) -> Table {
+    let grid = build_grid(records, columns, count_columns);
     create_table_from_grid(grid)
 }
 
 /// Building [Grid] from ordinary data.
-fn build_grid(header: Option<Vec<String>>, rows: Vec<Vec<String>>, count_columns: usize) -> Grid {
-    let mut count_rows = rows.len();
-
-    if header.is_some() {
+fn build_grid(
+    records: Vec<Vec<String>>,
+    columns: Option<Vec<String>>,
+    count_columns: usize,
+) -> Grid {
+    let mut count_rows = records.len();
+
+    if columns.is_some() {
         count_rows += 1;
     }
 
     let mut grid = Grid::new(count_rows, count_columns);
 
     let mut row = 0;
-    if let Some(headers) = header {
+    if let Some(headers) = columns {
         for (i, text) in headers.into_iter().enumerate() {
             grid.set(Entity::Cell(0, i), Settings::new().text(text));
         }
@@ -213,7 +307,7 @@ fn build_grid(header: Option<Vec<String>>, rows: Vec<Vec<String>>, count_columns
         row = 1;
     }
 
-    for fields in rows.into_iter() {
+    for fields in records.into_iter() {
         // don't show off a empty data array
         if fields.is_empty() {
             continue;
@@ -229,31 +323,294 @@ fn build_grid(header: Option<Vec<String>>, rows: Vec<Vec<String>>, count_columns
     grid
 }
 
-fn create_table_from_grid(mut grid: Grid) -> Table {
+fn create_table_from_grid(grid: Grid) -> Table {
+    let mut table = Table { grid };
+
     // it's crusial to set a global setting rather than a setting for an each cell
     // as it will be hard to override that since how Grid::style method works
-    grid.set(
-        Entity::Global,
-        Settings::new()
-            .padding(
-                Indent::spaced(1),
-                Indent::spaced(1),
-                Indent::spaced(0),
-                Indent::spaced(0),
-            )
-            .alignment(AlignmentHorizontal::Center)
-            .formatting(Formatting {
-                tab_width: 4,
-                horizontal_trim: true,
-                allow_lines_alignement: false,
-                vertical_trim: false,
-            }),
-    );
-
-    let table = Table { grid };
+    table.grid.set(Entity::Global, default_cell_style());
+
     table.with(Style::ascii())
 }
 
+fn default_cell_style() -> Settings {
+    Settings::new()
+        .padding(
+            Indent::spaced(1),
+            Indent::spaced(1),
+            Indent::spaced(0),
+            Indent::spaced(0),
+        )
+        .alignment(AlignmentHorizontal::Center)
+        .formatting(Formatting {
+            tab_width: 4,
+            horizontal_trim: true,
+            allow_lines_alignement: false,
+            vertical_trim: false,
+        })
+}
+
 fn append_vec(v: &mut Vec<String>, n: usize, value: String) {
     v.extend((0..n).map(|_| value.clone()));
 }
+
+/// [IndexBuilder] helps to add an index to the table.
+///
+/// Index is a column on the left of the table.
+///
+/// It also can be used to transpose the table.
+///
+/// # Example
+///
+/// ```
+/// use tabled::builder::Builder;
+/// let table = Builder::default()
+///     .index()
+///     .build();
+/// ```
+#[derive(Debug, Clone)]
+pub struct IndexBuilder {
+    /// Index is an index data.
+    /// It's always set.
+    index: Vec<String>,
+    /// Name of an index
+    name: Option<String>,
+    /// A flag which checks if we need to actualy use index.
+    ///
+    /// It might happen when it's only nessary to [Self::transpose] table.
+    print_index: bool,
+    /// A flag which checks if table was transposed.
+    transposed: bool,
+    /// Original builder instance.
+    b: Builder,
+}
+
+impl IndexBuilder {
+    /// Creates a new [Self] instance.
+    ///
+    /// It creates a default index a range from 0 to N. (N - count rows)
+    /// It also sets a default columns to the range 0 .. N (N - count columns).
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// use tabled::builder::Builder;
+    /// let table = Builder::default()
+    ///     .set_columns(["i", "col-1", "col-2"])
+    ///     .add_record(["0", "value-1", "value-2"])
+    ///     .index()
+    ///     .build();
+    ///
+    /// assert_eq!(
+    ///     table.to_string(),
+    ///     "+---+---+---------+---------+\n\
+    ///      |   | i |  col-1  |  col-2  |\n\
+    ///      +---+---+---------+---------+\n\
+    ///      | 0 | 0 | value-1 | value-2 |\n\
+    ///      +---+---+---------+---------+\n"
+    /// )
+    /// ```
+    fn new(mut b: Builder) -> Self {
+        let index = build_range_index(b.records.len());
+
+        if b.columns.is_none() {
+            b.columns = Some(build_range_index(b.size))
+        }
+
+        Self {
+            index,
+            name: None,
+            print_index: true,
+            transposed: false,
+            b,
+        }
+    }
+
+    /// No flag makes builder to not use an index.
+    ///
+    /// It may be usefull when only [Self::transpose] need to be used.
+    pub fn no_index(mut self) -> Self {
+        self.print_index = false;
+        self
+    }
+
+    /// Set an index name.
+    ///
+    /// When [None] the name won't be used.
+    pub fn set_name(mut self, name: Option<String>) -> Self {
+        self.name = name;
+        self
+    }
+
+    /// Sets a index to the chosen column.
+    ///
+    /// Also sets a name of the index to the column name.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// use tabled::builder::Builder;
+    /// let table = Builder::default()
+    ///     .set_columns(["i", "column1", "column2"])
+    ///     .add_record(["0", "value1", "value2"])
+    ///     .index()
+    ///     .set_index(1)
+    ///     .build();
+    ///
+    /// assert_eq!(
+    ///     table.to_string(),
+    ///     "+---------+---+---------+\n\
+    ///      |         | i | column2 |\n\
+    ///      +---------+---+---------+\n\
+    ///      | column1 |   |         |\n\
+    ///      +---------+---+---------+\n\
+    ///      | value1  | 0 | value2  |\n\
+    ///      +---------+---+---------+\n"
+    /// )
+    /// ```
+    pub fn set_index(mut self, column: usize) -> Self {
+        if self.b.columns.is_none() {
+            return self;
+        }
+
+        if column >= self.b.size {
+            return self;
+        }
+
+        let name = self
+            .b
+            .columns
+            .as_mut()
+            .map(|v| remove_or_default(v, column))
+            .unwrap_or_default();
+
+        self.name = Some(name);
+
+        self.index = get_column(&mut self.b.records, column);
+
+        self.b.size -= 1;
+
+        self
+    }
+
+    /// Transpose index and columns.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// use tabled::builder::Builder;
+    /// let table = Builder::default()
+    ///     .set_columns(["i", "column-1", "column-2", "column-3"])
+    ///     .add_record(["0", "value-1", "value-2", "value-3"])
+    ///     .add_record(["1", "value-4", "value-5", "value-6"])
+    ///     .add_record(["2", "value-7", "value-8", "value-9"])
+    ///     .index()
+    ///     .set_index(1)
+    ///     .transpose()
+    ///     .build();
+    ///
+    /// assert_eq!(
+    ///     table.to_string(),
+    ///     "+----------+---------+---------+---------+\n\
+    ///      | column-1 | value-1 | value-4 | value-7 |\n\
+    ///      +----------+---------+---------+---------+\n\
+    ///      |    i     |    0    |    1    |    2    |\n\
+    ///      +----------+---------+---------+---------+\n\
+    ///      | column-2 | value-2 | value-5 | value-8 |\n\
+    ///      +----------+---------+---------+---------+\n\
+    ///      | column-3 | value-3 | value-6 | value-9 |\n\
+    ///      +----------+---------+---------+---------+\n"
+    /// )
+    /// ```
+    pub fn transpose(mut self) -> Self {
+        let columns = self.b.columns.take().unwrap();
+
+        self.b.columns = Some(self.index);
+        self.index = columns;
+
+        let new_count_columns = self.b.records.len();
+        make_rows_columns(&mut self.b.records, self.b.size);
+        self.b.size = new_count_columns;
+
+        self.transposed = !self.transposed;
+
+        self
+    }
+
+    /// Builds a table.
+    pub fn build(self) -> Table {
+        build_index(self).build()
+    }
+}
+
+fn make_rows_columns(v: &mut Vec<Vec<String>>, count_columns: usize) {
+    let mut columns = Vec::with_capacity(count_columns);
+    for _ in 0..count_columns {
+        let column = get_column(v, 0);
+        columns.push(column);
+    }
+
+    v.clear();
+
+    for column in columns {
+        v.push(column);
+    }
+}
+
+fn build_index(mut b: IndexBuilder) -> Builder {
+    if b.index.is_empty() {
+        return b.b;
+    }
+
+    b.b.size += 1;
+
+    let records = &mut b.b.records;
+
+    let columns = b.b.columns.take().unwrap();
+    records.insert(0, columns);
+
+    // add index column
+    if b.print_index {
+        b.index.insert(0, String::new());
+        insert_column(records, b.index, 0);
+    }
+
+    if let Some(name) = b.name {
+        if b.transposed && b.print_index {
+            records[0][0] = name;
+        } else {
+            records.insert(1, vec![name]);
+        }
+    }
+
+    b.b
+}
+
+fn insert_column<T: Default>(v: &mut [Vec<T>], mut column: Vec<T>, col: usize) {
+    for row in v.iter_mut() {
+        let value = remove_or_default(&mut column, col);
+        row.insert(col, value);
+    }
+}
+
+fn get_column(v: &mut [Vec<String>], col: usize) -> Vec<String> {
+    let mut column = Vec::with_capacity(v.len());
+    for row in v.iter_mut() {
+        let value = remove_or_default(row, col);
+        column.push(value);
+    }
+
+    column
+}
+
+fn remove_or_default<T: Default>(v: &mut Vec<T>, i: usize) -> T {
+    if v.len() > i {
+        v.remove(i)
+    } else {
+        T::default()
+    }
+}
+
+fn build_range_index(n: usize) -> Vec<String> {
+    (0..n).map(|i| i.to_string()).collect()
+}
diff --git a/src/table.rs b/src/table.rs
index 05f652c..4a4c756 100644
--- a/src/table.rs
+++ b/src/table.rs
@@ -65,6 +65,59 @@ impl Table {
         Self::from_iter(iter)
     }
 
+    /// Creates a builder from a data set given.
+    ///
+    /// # Example
+    ///
+    /// ```
+    /// use tabled::{Table, Tabled};
+    ///
+    /// #[derive(Tabled)]
+    /// struct User {
+    ///     name: &'static str,
+    ///     #[tabled(inline("device::"))]
+    ///     device: Device,
+    /// }
+    ///
+    /// #[derive(Tabled)]
+    /// enum Device {
+    ///     PC,
+    ///     Mobile
+    /// }
+    ///
+    /// let data = vec![
+    ///     User { name: "Vlad", device: Device::Mobile },
+    ///     User { name: "Dimitry", device: Device::PC },
+    ///     User { name: "John", device: Device::PC },
+    /// ];
+    ///
+    /// let table = Table::builder(data)
+    ///     .index()
+    ///     .set_index(0)
+    ///     .transpose()
+    ///     .build()
+    ///     .to_string();
+    ///
+    /// assert_eq!(
+    ///     table,
+    ///     "+----------------+------+---------+------+\n\
+    ///      |      name      | Vlad | Dimitry | John |\n\
+    ///      +----------------+------+---------+------+\n\
+    ///      |   device::PC   |      |    +    |  +   |\n\
+    ///      +----------------+------+---------+------+\n\
+    ///      | device::Mobile |  +   |         |      |\n\
+    ///      +----------------+------+---------+------+\n"
+    /// )
+    /// ```
+    pub fn builder<I, T>(iter: I) -> Builder
+    where
+        T: Tabled,
+        I: IntoIterator<Item = T>,
+    {
+        let rows = iter.into_iter().map(|t| t.fields());
+        Builder::from_iter(rows).set_columns(T::headers())
+    }
+
     /// Returns a table shape (count rows, count columns).
     pub fn shape(&self) -> (usize, usize) {
         (self.grid.count_rows(), self.grid.count_columns())
@@ -96,8 +149,7 @@ where
     where
         T: IntoIterator<Item = D>,
     {
-        let rows = iter.into_iter().map(|t| t.fields());
-        Builder::from_iter(rows).set_header(D::headers()).build()
+        Self::builder(iter).build()
     }
 }
 


Test patch:
diff --git a/tests/builder_test.rs b/tests/builder_test.rs
index c867152..2eabad8 100644
--- a/tests/builder_test.rs
+++ b/tests/builder_test.rs
@@ -3,11 +3,11 @@ use std::iter::FromIterator;
 use tabled::builder::Builder;
 
 #[test]
-fn builder_add_row() {
+fn builder_add_record() {
     let builder = Builder::default()
-        .add_row(["1", "2", "3"])
-        .add_row(["a", "b", "c"])
-        .add_row(["d", "e", "f"]);
+        .add_record(["1", "2", "3"])
+        .add_record(["a", "b", "c"])
+        .add_record(["d", "e", "f"]);
     let table = builder.build().to_string();
     let expected = "+---+---+---+\n\
                          | 1 | 2 | 3 |\n\
@@ -21,11 +21,11 @@ fn builder_add_row() {
 }
 
 #[test]
-fn builder_add_row_can_has_different_types() {
+fn builder_add_record_can_has_different_types() {
     let builder = Builder::default()
-        .add_row([1, 2, 3])
-        .add_row(["a", "b", "c"])
-        .add_row(['d', 'e', 'f']);
+        .add_record([1, 2, 3])
+        .add_record(["a", "b", "c"])
+        .add_record(['d', 'e', 'f']);
     let table = builder.build().to_string();
     let expected = "+---+---+---+\n\
                          | 1 | 2 | 3 |\n\
@@ -41,9 +41,9 @@ fn builder_add_row_can_has_different_types() {
 #[test]
 fn builder_header() {
     let builder = Builder::default()
-        .add_row(["a", "b", "c"])
-        .add_row(["d", "e", "f"])
-        .set_header(["1", "2", "3"]);
+        .add_record(["a", "b", "c"])
+        .add_record(["d", "e", "f"])
+        .set_columns(["1", "2", "3"]);
     let table = builder.build().to_string();
     let expected = "+---+---+---+\n\
                          | 1 | 2 | 3 |\n\
@@ -74,9 +74,9 @@ fn builder_from_iter() {
 #[test]
 fn builder_used_with_different_number_of_columns() {
     let builder = Builder::default()
-        .set_header(["1", "2"])
-        .add_row(["a", "b", "c"])
-        .add_row(["d"]);
+        .set_columns(["1", "2"])
+        .add_record(["a", "b", "c"])
+        .add_record(["d"]);
     let table = builder.build().to_string();
     let expected = "+---+---+---+\n\
                          | 1 | 2 |   |\n\
@@ -88,9 +88,9 @@ fn builder_used_with_different_number_of_columns() {
     assert_eq!(table, expected);
 
     let builder = Builder::default()
-        .set_header(["1", "2", "3"])
-        .add_row(["a", "b"])
-        .add_row(["d"]);
+        .set_columns(["1", "2", "3"])
+        .add_record(["a", "b"])
+        .add_record(["d"]);
     let table = builder.build().to_string();
     let expected = "+---+---+---+\n\
                          | 1 | 2 | 3 |\n\
@@ -102,9 +102,9 @@ fn builder_used_with_different_number_of_columns() {
     assert_eq!(table, expected);
 
     let builder = Builder::default()
-        .set_header(["1"])
-        .add_row(["a", "b"])
-        .add_row(["d", "e", "f"]);
+        .set_columns(["1"])
+        .add_record(["a", "b"])
+        .add_record(["d", "e", "f"]);
     let table = builder.build().to_string();
     let expected = "+---+---+---+\n\
                          | 1 |   |   |\n\
@@ -120,9 +120,9 @@ fn builder_used_with_different_number_of_columns() {
 fn builder_with_default_cell() {
     let builder = Builder::default()
         .set_default_text("NaN")
-        .set_header(["1", "2"])
-        .add_row(["a", "b", "c"])
-        .add_row(["d"]);
+        .set_columns(["1", "2"])
+        .add_record(["a", "b", "c"])
+        .add_record(["d"]);
     let table = builder.build().to_string();
     let expected = "+---+-----+-----+\n\
                          | 1 |  2  | NaN |\n\
@@ -135,9 +135,9 @@ fn builder_with_default_cell() {
 
     let builder = Builder::default()
         .set_default_text("NaN")
-        .set_header(["1", "2", "3"])
-        .add_row(["a", "b"])
-        .add_row(["d"]);
+        .set_columns(["1", "2", "3"])
+        .add_record(["a", "b"])
+        .add_record(["d"]);
     let table = builder.build().to_string();
     let expected = "+---+-----+-----+\n\
                          | 1 |  2  |  3  |\n\
@@ -150,9 +150,9 @@ fn builder_with_default_cell() {
 
     let builder = Builder::default()
         .set_default_text("NaN")
-        .set_header(["1"])
-        .add_row(["a", "b"])
-        .add_row(["d", "e", "f"]);
+        .set_columns(["1"])
+        .add_record(["a", "b"])
+        .add_record(["d", "e", "f"]);
     let table = builder.build().to_string();
     let expected = "+---+-----+-----+\n\
                          | 1 | NaN | NaN |\n\
@@ -297,7 +297,7 @@ fn builder_from_with_empty_lines() {
 fn qc_table_is_consistent(data: Vec<Vec<isize>>) -> bool {
     let mut builder = Builder::default();
     for row in data {
-        builder = builder.add_row(row);
+        builder = builder.add_record(row);
     }
 
     let table = builder.build().to_string();
diff --git a/tests/index_test.rs b/tests/index_test.rs
new file mode 100644
index 0000000..1bf04ec
--- /dev/null
+++ b/tests/index_test.rs
@@ -0,0 +1,307 @@
+use tabled::Table;
+use util::create_vector;
+
+mod util;
+
+#[test]
+fn builder_index() {
+    let table = Table::builder(create_vector::<3, 2>()).index().build();
+
+    println!("{}", table);
+
+    assert_eq!(
+        table.to_string(),
+        concat!(
+            "+---+---+----------+----------+\n",
+            "|   | N | column 0 | column 1 |\n",
+            "+---+---+----------+----------+\n",
+            "| 0 | 0 |   0-0    |   0-1    |\n",
+            "+---+---+----------+----------+\n",
+            "| 1 | 1 |   1-0    |   1-1    |\n",
+            "+---+---+----------+----------+\n",
+            "| 2 | 2 |   2-0    |   2-1    |\n",
+            "+---+---+----------+----------+\n",
+        )
+    );
+}
+
+#[test]
+fn builder_index_transpose() {
+    let table = Table::builder(create_vector::<4, 2>())
+        .index()
+        .transpose()
+        .build();
+
+    println!("{}", table);
+
+    assert_eq!(
+        table.to_string(),
+        concat!(
+            "+----------+-----+-----+-----+-----+\n",
+            "|          |  0  |  1  |  2  |  3  |\n",
+            "+----------+-----+-----+-----+-----+\n",
+            "|    N     |  0  |  1  |  2  |  3  |\n",
+            "+----------+-----+-----+-----+-----+\n",
+            "| column 0 | 0-0 | 1-0 | 2-0 | 3-0 |\n",
+            "+----------+-----+-----+-----+-----+\n",
+            "| column 1 | 0-1 | 1-1 | 2-1 | 3-1 |\n",
+            "+----------+-----+-----+-----+-----+\n",
+        )
+    );
+}
+
+#[test]
+fn builder_index_transpose_transpose() {
+    let data = create_vector::<4, 2>();
+    let builder = Table::builder(&data).index();
+
+    let orig_table = builder.clone().build().to_string();
+    let two_times_transposed_table = builder.transpose().transpose().build().to_string();
+
+    assert_eq!(orig_table, two_times_transposed_table,);
+}
+
+#[test]
+fn builder_index_no_name_transpose_transpose() {
+    let data = create_vector::<4, 2>();
+    let builder = Table::builder(&data).index().set_name(None);
+
+    let orig_table = builder.clone().build().to_string();
+    let two_times_transposed_table = builder.transpose().transpose().build().to_string();
+
+    assert_eq!(orig_table, two_times_transposed_table,);
+}
+
+#[test]
+fn builder_index_0() {
+    let table = Table::builder(create_vector::<4, 2>())
+        .index()
+        .set_index(0)
+        .build();
+
+    println!("{}", table);
+
+    assert_eq!(
+        table.to_string(),
+        concat!(
+            "+---+----------+----------+\n",
+            "|   | column 0 | column 1 |\n",
+            "+---+----------+----------+\n",
+            "| N |          |          |\n",
+            "+---+----------+----------+\n",
+            "| 0 |   0-0    |   0-1    |\n",
+            "+---+----------+----------+\n",
+            "| 1 |   1-0    |   1-1    |\n",
+            "+---+----------+----------+\n",
+            "| 2 |   2-0    |   2-1    |\n",
+            "+---+----------+----------+\n",
+            "| 3 |   3-0    |   3-1    |\n",
+            "+---+----------+----------+\n",
+        )
+    );
+}
+
+#[test]
+fn builder_index_0_no_name() {
+    let table = Table::builder(create_vector::<4, 2>())
+        .index()
+        .set_index(0)
+        .set_name(None)
+        .build();
+
+    println!("{}", table);
+
+    assert_eq!(
+        table.to_string(),
+        concat!(
+            "+---+----------+----------+\n",
+            "|   | column 0 | column 1 |\n",
+            "+---+----------+----------+\n",
+            "| 0 |   0-0    |   0-1    |\n",
+            "+---+----------+----------+\n",
+            "| 1 |   1-0    |   1-1    |\n",
+            "+---+----------+----------+\n",
+            "| 2 |   2-0    |   2-1    |\n",
+            "+---+----------+----------+\n",
+            "| 3 |   3-0    |   3-1    |\n",
+            "+---+----------+----------+\n",
+        )
+    );
+}
+
+#[test]
+fn builder_index_0_name() {
+    let table = Table::builder(create_vector::<4, 2>())
+        .index()
+        .set_index(0)
+        .set_name(Some("Hello World".to_owned()))
+        .build();
+
+    println!("{}", table);
+
+    assert_eq!(
+        table.to_string(),
+        concat!(
+            "+-------------+----------+----------+\n",
+            "|             | column 0 | column 1 |\n",
+            "+-------------+----------+----------+\n",
+            "| Hello World |          |          |\n",
+            "+-------------+----------+----------+\n",
+            "|      0      |   0-0    |   0-1    |\n",
+            "+-------------+----------+----------+\n",
+            "|      1      |   1-0    |   1-1    |\n",
+            "+-------------+----------+----------+\n",
+            "|      2      |   2-0    |   2-1    |\n",
+            "+-------------+----------+----------+\n",
+            "|      3      |   3-0    |   3-1    |\n",
+            "+-------------+----------+----------+\n",
+        )
+    );
+}
+
+#[test]
+fn builder_index_0_name_transpose() {
+    let table = Table::builder(create_vector::<4, 2>())
+        .index()
+        .set_index(0)
+        .set_name(Some("Hello World".to_owned()))
+        .transpose()
+        .build();
+
+    println!("{}", table);
+
+    assert_eq!(
+        table.to_string(),
+        concat!(
+            "+-------------+-----+-----+-----+-----+\n",
+            "| Hello World |  0  |  1  |  2  |  3  |\n",
+            "+-------------+-----+-----+-----+-----+\n",
+            "|  column 0   | 0-0 | 1-0 | 2-0 | 3-0 |\n",
+            "+-------------+-----+-----+-----+-----+\n",
+            "|  column 1   | 0-1 | 1-1 | 2-1 | 3-1 |\n",
+            "+-------------+-----+-----+-----+-----+\n",
+        )
+    );
+}
+
+#[test]
+fn builder_index_with_no_columns() {
+    let table = tabled::builder::Builder::default()
+        .add_record(["1", "2", "3"])
+        .add_record(["a", "b", "c"])
+        .add_record(["d", "e", "f"])
+        .index()
+        .build();
+
+    println!("{}", table);
+
+    assert_eq!(
+        table.to_string(),
+        concat!(
+            "+---+---+---+---+\n",
+            "|   | 0 | 1 | 2 |\n",
+            "+---+---+---+---+\n",
+            "| 0 | 1 | 2 | 3 |\n",
+            "+---+---+---+---+\n",
+            "| 1 | a | b | c |\n",
+            "+---+---+---+---+\n",
+            "| 2 | d | e | f |\n",
+            "+---+---+---+---+\n",
+        )
+    );
+}
+
+#[test]
+fn builder_index_with_no_columns_and_name() {
+    let table = tabled::builder::Builder::default()
+        .add_record(["1", "2", "3"])
+        .add_record(["a", "b", "c"])
+        .add_record(["d", "e", "f"])
+        .index()
+        .set_name(Some("Hello World".to_owned()))
+        .build();
+
+    println!("{}", table);
+
+    assert_eq!(
+        table.to_string(),
+        concat!(
+            "+-------------+---+---+---+\n",
+            "|             | 0 | 1 | 2 |\n",
+            "+-------------+---+---+---+\n",
+            "| Hello World |   |   |   |\n",
+            "+-------------+---+---+---+\n",
+            "|      0      | 1 | 2 | 3 |\n",
+            "+-------------+---+---+---+\n",
+            "|      1      | a | b | c |\n",
+            "+-------------+---+---+---+\n",
+            "|      2      | d | e | f |\n",
+            "+-------------+---+---+---+\n",
+        )
+    );
+}
+
+#[test]
+fn builder_index_with_no_columns_transpose() {
+    let table = tabled::builder::Builder::default()
+        .add_record(["1", "2", "3"])
+        .add_record(["a", "b", "c"])
+        .add_record(["d", "e", "f"])
+        .index()
+        .transpose()
+        .build();
+
+    println!("{}", table);
+
+    assert_eq!(
+        table.to_string(),
+        concat!(
+            "+---+---+---+---+\n",
+            "|   | 0 | 1 | 2 |\n",
+            "+---+---+---+---+\n",
+            "| 0 | 1 | a | d |\n",
+            "+---+---+---+---+\n",
+            "| 1 | 2 | b | e |\n",
+            "+---+---+---+---+\n",
+            "| 2 | 3 | c | f |\n",
+            "+---+---+---+---+\n",
+        )
+    );
+}
+
+#[test]
+fn builder_index_empty() {
+    let table = tabled::builder::Builder::default().index().build();
+
+    assert_eq!(table.to_string(), "");
+}
+
+#[test]
+fn builder_index_transpose_empty() {
+    let table = tabled::builder::Builder::default()
+        .index()
+        .transpose()
+        .build();
+
+    assert_eq!(table.to_string(), "");
+}
+
+#[test]
+fn builder_index_invalid_dosnt_panic() {
+    let table = tabled::builder::Builder::default()
+        .index()
+        .set_index(100)
+        .build();
+
+    assert_eq!(table.to_string(), "");
+}
+
+#[test]
+fn builder_index_name_doesnt_shown_when_empty() {
+    let table = tabled::builder::Builder::default()
+        .index()
+        .set_name(Some("Hello World".to_owned()))
+        .build();
+
+    assert_eq!(table.to_string(), "");
+}