├── .babelrc ├── .coveralls.yml ├── .editorconfig ├── .eslintrc.js ├── .github └── ISSUE_TEMPLATE.md ├── .gitignore ├── .huskyrc.js ├── .travis.yml ├── Gruntfile.js ├── LICENSE.txt ├── README.md ├── adapters ├── automattic-cli-table.js ├── default-adapter.js └── terminal-adapter.js ├── conf.json ├── dist ├── tty-table.cjs.js ├── tty-table.esm.js └── tty-table.umd.js ├── docker ├── Dockerfile.centos-7 ├── Dockerfile.ubuntu-12.04 ├── Dockerfile.ubuntu-14.04 └── Dockerfile.ubuntu-18.04 ├── docs ├── automattic-cli-table.md ├── pull_request_template.md └── terminal.md ├── examples ├── auto-resize-percentage-widths.js ├── auto-resize-single-column-widths.js ├── auto-resize-undeclared-widths.js ├── auto-with-percentage.js ├── auttomatic-cli-table-tests.js ├── browser-example.html ├── cli-test.js ├── config │ ├── header.js │ └── header.json ├── constructor-types.js ├── data │ ├── data-wide.csv │ ├── data.csv │ ├── data.json │ ├── delimiter-inside-quoted-fields.csv │ ├── fake-stream.js │ └── streamer.js ├── empty-rows.js ├── hide-header.js ├── multiple-tables.js ├── no-border-no-header.js ├── null-undefined-values.js ├── placeholder-for-wide-chars.js ├── styles-and-formatting.js ├── template-literal-cell-value.js ├── text-wrapping.js ├── truncated-lines.js └── wide-characters.js ├── npm_scripts └── run-examples.js ├── package-lock.json ├── package.json ├── packaging.md ├── rollup.config.js ├── src ├── defaults.js ├── factory.d.ts ├── factory.js ├── format.js ├── main.js ├── render.js └── style.js └── test ├── example-utils.js ├── mocha.conf.js └── saved_test_outputs ├── auto-resize-percentage-widths-output.txt ├── auto-resize-single-column-widths-output.txt ├── auto-resize-undeclared-widths-output.txt ├── auto-with-percentage-output.txt ├── auttomatic-cli-table-tests-output.txt ├── cli-test-output.txt ├── constructor-types-output.txt ├── empty-rows-output.txt ├── hide-header-output.txt ├── multiple-tables-output.txt ├── no-border-no-header-output.txt ├── null-undefined-values-output.txt ├── placeholder-for-wide-chars-output.txt ├── styles-and-formatting-output.txt ├── template-literal-cell-value-output.txt ├── text-wrapping-output.txt ├── truncated-lines-output.txt └── wide-characters-output.txt /.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": ["es2015"], 3 | "env": { 4 | "production": { 5 | "presets": ["minify"] 6 | } 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /.coveralls.yml: -------------------------------------------------------------------------------- 1 | repo_token: 99qqlvlswLnV9GSZzmvyIR3aHKzFpKrXu 2 | -------------------------------------------------------------------------------- /.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | # Unix-style newlines with a newline ending every file 4 | [*] 5 | end_of_line = lf 6 | insert_final_newline = true 7 | 8 | 9 | # Matches multiple files with brace expansion notation 10 | # Set default charset 11 | [*.{js,jsx,html,sass}] 12 | charset = utf-8 13 | indent_style = tab 14 | indent_size = 2 15 | trim_trailing_whitespace = true 16 | -------------------------------------------------------------------------------- /.eslintrc.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | "env": { 3 | "commonjs": true, 4 | "es6": true, 5 | "node": true 6 | }, 7 | "extends": "standard", 8 | "globals": { 9 | "Atomics": "readonly", 10 | "SharedArrayBuffer": "readonly" 11 | }, 12 | "parserOptions": { 13 | "ecmaVersion": 2018 14 | }, 15 | "rules": { 16 | "brace-style": "error", 17 | 18 | "comma-spacing": ["error", {"before": false, "after": true}], 19 | 20 | "indent": [ 21 | "error", 22 | 2, 23 | { 24 | "SwitchCase": 1 25 | } 26 | ], 27 | 28 | "linebreak-style": [ 29 | "error", 30 | "unix" 31 | ], 32 | 33 | "key-spacing": ["error", { 34 | "beforeColon": false, 35 | "afterColon": true 36 | }], 37 | 38 | "no-case-declarations": "off", 39 | 40 | "no-trailing-spaces": "error", 41 | 42 | "operator-linebreak": ["error", "before"], 43 | 44 | "prefer-template": "error", 45 | 46 | "semi": [ 47 | "error", 48 | "never" 49 | ], 50 | 51 | "semi-spacing": ["error", {"before": false, "after": true}], 52 | 53 | "space-before-blocks": "error", 54 | 55 | "spaced-comment": ["error", "always", { "exceptions": ["-", "+"] }], 56 | 57 | "quotes": [ 58 | "error", 59 | "double" 60 | ] 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Problem Summary 2 | 3 | ## Expected Result (screen shot if possible) 4 | 5 | 6 | ## Actual Result (screen shot if possible) 7 | 8 | 9 | ## Environment Information 10 | 11 | * OS: 12 | 13 | * Terminal Emulator: 14 | 15 | * Node Version: 16 | 17 | 18 | ## Steps to Reproduce 19 | 20 | 1. foo 21 | 2. bar 22 | 3. baz 23 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .vim 3 | npm-debug.log 4 | Session.vim 5 | tags 6 | .nyc_output 7 | temp 8 | .idea 9 | scratch-files 10 | -------------------------------------------------------------------------------- /.huskyrc.js: -------------------------------------------------------------------------------- 1 | const tasks = arr => arr.join(' && ') 2 | 3 | module.exports = { 4 | 'hooks': { 5 | 'pre-commit': tasks([ 6 | "npm run lint", 7 | "npm run lint-examples", 8 | "npm run test" 9 | ]) 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | --- 2 | sudo: required 3 | 4 | #language: node_js 5 | 6 | #node_js: 7 | # - "node" 8 | 9 | env: 10 | # Test package on all supported OSes. 11 | - distribution: centos 12 | version: 7 13 | - distribution: ubuntu 14 | version: 14.04 15 | - distribution: ubuntu 16 | version: 18.04 17 | 18 | services: 19 | - docker 20 | - xvfb 21 | 22 | before_install: 23 | # Pull base image 24 | - 'sudo docker pull ${distribution}:${version}' 25 | # Create custom image named: ${distribution}-${version}:tty-table 26 | - 'sudo docker build --no-cache=true --file=docker/Dockerfile.${distribution}-${version} --tag=${distribution}-${version}:tty-table .' 27 | 28 | before_script: 29 | # Create virtual framebuffer so display output can be read with 30 | # correct dimensions by test suite 31 | - export DISPLAY=:99.0 32 | # - sh -e /etc/init.d/xvfb start 33 | 34 | script: 35 | # Create a container from the custom image and run tests on it 36 | - 'sudo docker run --name="${distribution}-${version}" ${distribution}-${version}:tty-table' 37 | -------------------------------------------------------------------------------- /Gruntfile.js: -------------------------------------------------------------------------------- 1 | /** 2 | * to debug gruntfile: 3 | * node-debug $(which grunt) task 4 | */ 5 | module.exports = function(grunt) { 6 | 7 | // modules for browserify to ignore 8 | const _ignore = `--ignore=path --ignore=request --ignore=http --ignore=fs --ignore=vm --ignore=lodash --ignore=yargs` 9 | 10 | // project configuration. 11 | grunt.initConfig({ 12 | 13 | 14 | pkg: grunt.file.readJSON("package.json"), 15 | 16 | 17 | shell: { 18 | 19 | browserify_prod_umd: { 20 | command: () => `npx browserify --standalone=TTY_Table ${_ignore} -r ./adapters/default-adapter.js:<%= pkg.name %> > ./dist/<%= pkg.name %>.umd.js -t [ babelify --presets [ es2015 babili] ]` 21 | }, 22 | 23 | browserify_devel_umd: { 24 | command: () => `npx browserify --debug --standalone=TTY_Table ${_ignore} -r ./adapters/default-adapter.js > ./dist/<%= pkg.name %>.umd.js -t [ babelify --presets [ es2015 babili] ]` 25 | }, 26 | 27 | browserify_prod_cjs: { 28 | command: () => `npx browserify ${_ignore} -r ./adapters/default-adapter.js:<%= pkg.name %> > ./dist/<%= pkg.name %>.cjs.js -t [ babelify --presets [ es2015 babili] ]` 29 | }, 30 | 31 | browserify_devel_cjs: { 32 | command: () => `npx browserify --debug ${_ignore} -r ./adapters/default-adapter.js:<%= pkg.name %> > ./dist/<%= pkg.name %>.cjs.devel.js -t [ babelify --presets [ es2015 babili] ]` 33 | }, 34 | 35 | generate_vim_tags_file: { 36 | command: () => `find . -name \"*.js\" -path \"./src/*\" | xargs jsctags {} -f | sed \"/^$/d\" | sort > tags` 37 | } 38 | }, 39 | 40 | // regenerate tags file on file save 41 | watch: { 42 | scripts: { 43 | files: ["**/*.js"], 44 | tasks: [ 45 | "shell:generate_vim_tags_file", 46 | "mochaTest:test" 47 | ], 48 | options: { 49 | spawn: true, 50 | livereload: true // defaults to port 35729 51 | } 52 | } 53 | } 54 | }) 55 | 56 | grunt.loadNpmTasks("grunt-contrib-watch") 57 | grunt.loadNpmTasks("grunt-shell") 58 | 59 | 60 | grunt.registerTask("tags", [ 61 | "shell:generate_vim_tags_file", 62 | ]) 63 | 64 | grunt.registerTask("browserify", [ 65 | "shell:browserify_prod_umd", 66 | //"shell:browserify_devel_umd", 67 | "shell:browserify_prod_cjs", 68 | //"shell:browserify_devel_cjs", 69 | ]) 70 | } 71 | -------------------------------------------------------------------------------- /LICENSE.txt: -------------------------------------------------------------------------------- 1 | Copyright 2018 $2y$10$pCG0Qzwi0AhuaYCFpydbS.c3PHUJGu3AJreDudGce.Zd/UV.HQyLe 2 | 3 | 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: 4 | 5 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 6 | 7 | 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. 8 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # tty-table 端子台 2 | 3 | [![NPM version](https://badge.fury.io/js/tty-table.svg)](http://badge.fury.io/js/tty-table) [![Coverage Status](https://coveralls.io/repos/github/tecfu/tty-table/badge.svg?branch=master)](https://coveralls.io/github/tecfu/tty-table?branch=master) 4 | --- 5 | 6 | Display your data in a table using a terminal, browser, or browser console. 7 | 8 | --- 9 | 10 | ## [Examples](examples/) 11 | 12 | [See here for complete example list](examples/) 13 | 14 | 15 | To view all example output: 16 | 17 | ```sh 18 | $ git clone https://github.com/tecfu/tty-table && cd tty-table && npm i 19 | $ npm run view-examples 20 | ``` 21 | 22 | ### Terminal (Static) 23 | 24 | [examples/styles-and-formatting.js](examples/styles-and-formatting.js) 25 | 26 | ![Static](https://cloud.githubusercontent.com/assets/7478359/15691679/07142030-273f-11e6-8f1e-25728d558a2d.png "Static Example") 27 | 28 | ### Terminal (Streaming) 29 | 30 | ``` 31 | $ node examples/data/fake-stream.js | tty-table --format json --header examples/config/header.js 32 | ``` 33 | 34 | ![Streaming](https://user-images.githubusercontent.com/7478359/51738817-47c25700-204d-11e9-9df1-04e478331658.gif "tty-table streaming example") 35 | 36 | - See the built-in help for the terminal version of tty-table with: 37 | ``` 38 | $ tty-table -h 39 | ``` 40 | 41 | ### Browser & Browser Console 42 | 43 | - View in Chrome or Chromium at [http://localhost:8070/examples/browser-example.html](http://localhost:8070/examples/browser-example.html) using a dockerized apache instance: 44 | 45 | ```sh 46 | git clone https://github.com/tecfu/tty-table 47 | cd tty-table 48 | docker run -dit --name tty-table-in-browser -p 8070:80 -v "$PWD":/usr/local/apache2/htdocs/ httpd:2.4 49 | ``` 50 | 51 | - [live demo (chrome only): jsfiddle](https://jsfiddle.net/nb14eyav/) 52 | - [live demo (chrome only): plnkr](https://plnkr.co/edit/iQn9xn5yCY4NUkXRF87o?p=preview) 53 | - [source: examples/browser-example.html](examples/browser-example.html) 54 | 55 | ![Browser Console Example](https://user-images.githubusercontent.com/7478359/74614563-cbcaff00-50e6-11ea-9101-5457497696b8.jpg "tty-table in the browser console") 56 | 57 |
58 |
59 | 60 | ## API Reference 61 | 62 | 63 | 64 | ### Table(header ```array```, rows ```array```, options ```object```) 65 | 66 | | Param | Type | Description | 67 | | --- | --- | --- | 68 | | [header](#header_options) | array | Per-column configuration. An array of objects, one object for each column. Each object contains properties you can use to configure that particular column. [See available properties](#header_options) | 69 | | [rows](#rows_examples) | array | Your data. An array of arrays or objects. [See examples](#rows_examples) | 70 | | [options](#options_properties) | object | Global table configuration. [See available properties](#options_properties) | 71 | 72 | 73 |
74 | 75 | 76 | #### header ```array of objects``` 77 | 78 | | Param | Type | Description | 79 | | --- | --- | --- | 80 | | alias | string | Text to display in column header cell | 81 | | align | string | default: "center" | 82 | | color | string | default: terminal default color | 83 | | footerAlign | string | default: "center" | 84 | | footerColor | string | default: terminal default color | 85 | | formatter | function(cellValue, columnIndex, rowIndex, rowData, inputData | Runs a callback on each cell value in the parent column.
Please note that fat arrow functions `() => {}` don't support scope overrides, and this feature won't work correctly within them. | 86 | | @formatter configure | function(object) | Configure cell properties. For example:
`this.configure({ truncate: false, align: "left" })` [More here](https://github.com/tecfu/tty-table/blob/master/examples/truncated-lines.js#L100-L110). | 87 | | @formatter resetStyle | function(cellValue) | Removes ANSI escape sequences. For example:
`this.resetStyle(" myText") // "myText"`
| 88 | | @formatter style | function(cellValue, effect) | Style cell value. For example:
`this.style("mytext", "bold", "green", "underline")`
For a full list of options in the terminal: [chalk](https://github.com/chalk/chalk). For a full list of options in the browser: [kleur](https://github.com/lukeed/kleur)| 89 | | headerAlign | string | default: "center" | 90 | | headerColor | string | default: terminal's default color | 91 | | marginLeft | integer | default: 0 | 92 | | marginTop | integer | default: 0 | 93 | | paddingBottom | integer | default: 0 | 94 | | paddingLeft | integer | default: 1 | 95 | | paddingRight | integer | default: 1 | 96 | | paddingTop | integer | default: 0 | 97 | | value | string | Name of the property to display in each cell when data passed as an array of objects | 98 | | width | string \|\| integer | default: "auto"
Can be a percentage of table width i.e. "20%" or a fixed number of columns i.e. "20".
When set to the default ("auto"), the column widths are made proportionate by the longest value in each column.
Note: Percentage columns and fixed value colums not intended to be mixed in the same table.| 99 | 100 | **Example** 101 | 102 | ```js 103 | let header = [{ 104 | value: "item", 105 | headerColor: "cyan", 106 | color: "white", 107 | align: "left", 108 | width: 20 109 | }, 110 | { 111 | value: "price", 112 | color: "red", 113 | width: 10, 114 | formatter: function (value) { 115 | let str = `$${value.toFixed(2)}` 116 | return (value > 5) ? this.style(str, "green", "bold") : 117 | this.style(str, "red", "underline") 118 | } 119 | }] 120 | ``` 121 | 122 |
123 |
124 | 125 | 126 | #### rows ```array``` 127 | 128 | **Example** 129 | - each row an array 130 | ```js 131 | const rows = [ 132 | ["hamburger",2.50], 133 | ] 134 | ``` 135 | - each row an object 136 | ```js 137 | const rows = [ 138 | { 139 | item: "hamburger", 140 | price: 2.50 141 | } 142 | ] 143 | ``` 144 | 145 | 146 |
147 |
148 | 149 | 150 | #### footer ```array``` 151 | - Footer is optional 152 | 153 | **Example** 154 | ```js 155 | const footer = [ 156 | "TOTAL", 157 | function (cellValue, columnIndex, rowIndex, rowData) { 158 | let total = rowData.reduce((prev, curr) => { 159 | return prev + curr[1] 160 | }, 0) 161 | .toFixed(2) 162 | 163 | return this.style(`$${total}`, "italic") 164 | } 165 | ] 166 | ``` 167 | 168 |
169 |
170 | 171 | 172 | #### options ```object``` 173 | 174 | | Param | Type | Description | 175 | | --- | --- | --- | 176 | | borderStyle | string | default: "solid".
options: "solid", "dashed", "none" | 177 | | borderColor | string | default: terminal default color | 178 | | color | string | default: terminal default color | 179 | | compact | boolean | default: false
Removes horizontal borders when true. | 180 | | defaultErrorValue | mixed | default: '�' | 181 | | defaultValue | mixed | default: '?' | 182 | | errorOnNull | boolean | default: false | 183 | | truncate | mixed | default: false
When this property is set to a string, cell contents will be truncated by that string instead of wrapped when they extend beyond of the width of the cell.
For example if:
"truncate":"..."
the cell will be truncated with "..."
Note: tty-table wraps overflowing cell text into multiple lines by default, so you would likely only utilize `truncate` for extremely long values. | 184 | | width | string | default: "100%"
Width of the table. Can be a percentage of i.e. "50%" or a fixed number of columns in the terminal viewport i.e. "100".
Note: When you use a percentage, your table will be "responsive".| 185 | 186 | 187 | **Example** 188 | ```js 189 | const options = { 190 | borderStyle: "solid", 191 | borderColor: "blue", 192 | headerAlign: "center", 193 | align: "left", 194 | color: "white", 195 | truncate: "...", 196 | width: "90%" 197 | } 198 | ``` 199 | 200 |
201 | 202 | ### Table.render() ⇒ String 203 | 204 | 205 | Add method to render table to a string 206 | 207 | **Example** 208 | ```js 209 | const out = Table(header,rows,options).render() 210 | console.log(out); //prints output 211 | ``` 212 | 213 | 214 | 215 |
216 |
217 | 218 | ## Installation 219 | 220 | - [Terminal](docs/terminal.md): 221 | 222 | ```sh 223 | $ npm install tty-table -g 224 | ``` 225 | 226 | - Node Module 227 | 228 | ```sh 229 | $ npm install tty-table 230 | ``` 231 | 232 | - Browser 233 | 234 | ```js 235 | import Table from 'https://cdn.jsdelivr.net/gh/tecfu/tty-table/dist/tty-table.esm.js' 236 | let Table = require('tty-table') // https://cdn.jsdelivr.net/gh/tecfu/tty-table/dist/tty-table.cjs.js 237 | let Table = TTY_Table; // https://cdn.jsdelivr.net/gh/tecfu/tty-table/dist/tty-table.umd.js 238 | ``` 239 | 240 | ## Version Compatibility 241 | 242 | | Node Version | tty-table Version | 243 | | -------------- | ------------------| 244 | | 8 | >= 2.0 | 245 | | 0.11 | >= 0.0 | 246 | 247 | ## Running tests 248 | 249 | ```sh 250 | $ npm test 251 | ``` 252 | 253 | ```sh 254 | $ npm run coverage 255 | ``` 256 | 257 | ## Saving the output of new unit tests 258 | 259 | ```sh 260 | $ npm run save-tests 261 | ``` 262 | 263 | ## Dev Tips 264 | 265 | - To generate vim tags (make sure [jsctags](https://github.com/ramitos/jsctags) is installed globally) 266 | 267 | ```sh 268 | $ npm run tags 269 | ``` 270 | 271 | - To generate vim tags on file save 272 | 273 | ```sh 274 | $ npm run watch-tags 275 | ``` 276 | 277 | ## Pull Requests 278 | 279 | Pull requests are encouraged! 280 | 281 | - Please remember to add a unit test when necessary 282 | - Please format your commit messages according to the ["Conventional Commits"](https://www.conventionalcommits.org/en/v1.0.0/) specification 283 | 284 | If you aren't familiar with Conventional Commits, here's a good [article on the topic](https://dev.to/maniflames/how-conventional-commits-improved-my-git-skills-1jfk) 285 | 286 | TL/DR: 287 | 288 | - feat: a feature that is visible for end users. 289 | - fix: a bugfix that is visible for end users. 290 | - chore: a change that doesn't impact end users (e.g. chances to CI pipeline) 291 | - docs: a change in the README or documentation 292 | - refactor: a change in production code focused on readability, style and/or performance. 293 | 294 | 295 | ## [Packaging as a distributable](packaging.md) 296 | 297 | 298 | ## License 299 | 300 | [MIT License](https://opensource.org/licenses/MIT) 301 | 302 | Copyright 2015-2020, Tecfu. 303 | -------------------------------------------------------------------------------- /adapters/automattic-cli-table.js: -------------------------------------------------------------------------------- 1 | var Factory = require("../src/factory.js") 2 | 3 | var Table = function (options) { 4 | options = options || {} 5 | options.adapter = "automattic" 6 | 7 | // translations 8 | 9 | // header 10 | var header = [] 11 | if (options.head && options.head instanceof Array) { 12 | options.head.forEach(function (val) { 13 | header.push({ 14 | value: val 15 | }) 16 | }) 17 | } 18 | 19 | // colWidths 20 | if (options.colWidths) { 21 | options.colWidths.forEach(function (val, i) { 22 | header[i].width = val 23 | }) 24 | } 25 | 26 | // colAligns 27 | if (options.colAligns) { 28 | options.colAligns.forEach(function (val, i) { 29 | header[i].align = val 30 | header[i].headerAlign = val 31 | }) 32 | } 33 | 34 | // style 35 | options.style = options.style || {} 36 | 37 | // style - padding 38 | if (options.style["padding-left"]) { 39 | options.paddingLeft = options.style["padding-left"] 40 | } 41 | 42 | if (options.style["padding-right"]) { 43 | options.paddingRight = options.style["padding-right"] 44 | } 45 | 46 | // style - colors 47 | if (options.style.head && options.style.head instanceof Array) { 48 | options.headerColor = options.style.head[0] 49 | } 50 | 51 | if (options.style.body && options.style.body instanceof Array) { 52 | options.color = options.style.body[0] 53 | } 54 | 55 | // style - compact 56 | if (options.style.compact) { 57 | options.compact = true 58 | } 59 | 60 | // @todo style - border color 61 | 62 | // inherited from prototype 63 | const t = Factory(header, [], [], options) 64 | t.toString = t.render 65 | return t 66 | } 67 | 68 | module.exports = Table 69 | -------------------------------------------------------------------------------- /adapters/default-adapter.js: -------------------------------------------------------------------------------- 1 | const Factory = require("./../src/factory.js") 2 | module.exports = Factory 3 | -------------------------------------------------------------------------------- /adapters/terminal-adapter.js: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | const path = require("path") 3 | const fs = require("fs") 4 | const csv = require("csv") 5 | const style = require("../src/style").style 6 | let yargs = require("yargs") 7 | 8 | yargs.epilog("Copyright github.com/tecfu 2018") 9 | 10 | yargs.option("config", { 11 | describe: "Specify the configuration for your table." 12 | }) 13 | 14 | yargs.option("csv-delimiter", { 15 | describe: "Set the field delimiter. One character only.", 16 | default: "," 17 | }) 18 | 19 | yargs.option("csv-escape", { 20 | describe: "Set the escape character. One character only." 21 | }) 22 | 23 | yargs.option("csv-rowDelimiter", { 24 | describe: "String used to delimit record rows. You can also use a special constant: \"auto\",\"unix\",\"max\",\"windows\",\"unicode\".", 25 | default: "\n" 26 | }) 27 | 28 | yargs.option("format", { 29 | describe: "Set input data format", 30 | choices: ["json", "csv"], 31 | default: "csv" 32 | }) 33 | 34 | yargs.option("options\u2010\u002A", { 35 | describe: "Specify an optional setting where * is the setting name. See README.md for a complete list." 36 | }) 37 | 38 | // run help only at the end 39 | yargs = yargs.help("h").argv 40 | 41 | const emitError = function (type, detail) { 42 | console.log(` 43 | ${style(type, "white", "bgRed")} 44 | 45 | ${detail}`) 46 | process.exit(1) 47 | } 48 | 49 | // note that this is the first run 50 | let alreadyRendered = false 51 | let previousHeight = 0 52 | 53 | let dataFormat = "csv" 54 | switch (true) { 55 | case (typeof yargs.format === "undefined"): 56 | break 57 | case (yargs.format.toString().match(/json/i) !== null): 58 | dataFormat = "json" 59 | break 60 | default: 61 | } 62 | 63 | // look for individually flagged options-* 64 | const options = {} 65 | Object.keys(yargs).forEach(function (key) { 66 | const keyParts = key.split("-") 67 | if (keyParts[0] === "options") { 68 | options[keyParts[1]] = yargs[key] 69 | } 70 | }) 71 | 72 | // look for options passed via config file 73 | let header = [] 74 | if (yargs.header) { 75 | if (!fs.existsSync(path.resolve(yargs.header))) { 76 | emitError( 77 | "Invalid file path", 78 | `Cannot find config file at: ${yargs.header}.` 79 | ) 80 | } 81 | // merge with any individually flagged options 82 | header = require(path.resolve(yargs.header)) 83 | } 84 | 85 | // because different dataFormats 86 | const runTable = function (header, body) { 87 | // footer = [], 88 | const Table = require("../src/factory.js") 89 | options.terminalAdapter = true 90 | const t1 = Table(header, body, options) 91 | 92 | // hide cursor 93 | console.log("\u001b[?25l") 94 | 95 | // wipe existing if already rendered 96 | if (alreadyRendered) { 97 | // move cursor up number to the top of the previous print 98 | // before deleting 99 | console.log(`\u001b[${previousHeight + 3}A`) 100 | 101 | // delete to end of terminal 102 | console.log("\u001b[0J") 103 | } else { 104 | alreadyRendered = true 105 | } 106 | 107 | console.log(t1.render()) 108 | 109 | // reset the previous height to the height of this output 110 | // for when we next clear the print 111 | previousHeight = t1.height 112 | } 113 | 114 | const chunks = [] 115 | process.stdin.resume() 116 | process.stdin.setEncoding("utf8") 117 | process.stdin.on("data", function (chunk) { 118 | chunks.push(chunk) 119 | }) 120 | process.stdin.on("end", function () { 121 | const stdin = chunks.join("") 122 | 123 | // handle dataFormats 124 | switch (true) { 125 | case (dataFormat === "json"): 126 | let data 127 | try { 128 | data = JSON.parse(stdin) 129 | } catch (e) { 130 | emitError( 131 | "JSON parse error", 132 | "Please check to make sure that your input data consists of JSON or specify a different format with the --format flag." 133 | ) 134 | } 135 | runTable(header, data) 136 | break 137 | default: 138 | const formatterOptions = {} 139 | Object.keys(yargs).forEach(function (key) { 140 | if (key.slice(0, 4) === "csv-" && typeof (yargs[key]) !== "undefined") { 141 | formatterOptions[key.slice(4)] = yargs[key] 142 | } 143 | }) 144 | 145 | csv.parse(stdin, formatterOptions, function (err, data) { 146 | // validate csv 147 | if (err || typeof data === "undefined") { 148 | emitError( 149 | "CSV parse error", 150 | "Please check to make sure that your input data consists of valid comma separated values or specify a different format with the --format flag." 151 | ) 152 | } 153 | runTable(header, data) 154 | }) 155 | } 156 | }) 157 | 158 | /* istanbul ignore next */ 159 | if (process.platform === "win32") { 160 | const rl = require("readline").createInterface({ 161 | input: process.stdin, 162 | output: process.stdout 163 | }) 164 | 165 | rl.on("SIGINT", function () { 166 | process.emit("SIGINT") 167 | }) 168 | } 169 | 170 | /* istanbul ignore next */ 171 | process.on("SIGINT", function () { 172 | // graceful shutdown 173 | process.exit() 174 | }) 175 | 176 | process.on("exit", function () { 177 | // show cursor 178 | console.log("\u001b[?25h") 179 | }) 180 | -------------------------------------------------------------------------------- /conf.json: -------------------------------------------------------------------------------- 1 | { 2 | "opts": { 3 | "no-usage-stats": true 4 | } 5 | } 6 | -------------------------------------------------------------------------------- /docker/Dockerfile.centos-7: -------------------------------------------------------------------------------- 1 | FROM centos:7 2 | 3 | RUN yum install -y epel-release 4 | 5 | # Install bzip2 6 | RUN yum install -y bzip2 7 | 8 | RUN yum install -y git 9 | 10 | # Install nodejs 8 11 | RUN curl -sL https://rpm.nodesource.com/setup_12.x | bash - 12 | RUN yum install -y nodejs 13 | 14 | # RUN ln -s /usr/bin/nodejs /usr/bin/node 15 | 16 | # Install tty-table 17 | RUN git clone https://www.github.com/tecfu/tty-table 18 | 19 | # Install dev dependencies 20 | WORKDIR /tty-table 21 | RUN npm install 22 | 23 | # Run unit tests 24 | RUN npx mocha 25 | -------------------------------------------------------------------------------- /docker/Dockerfile.ubuntu-12.04: -------------------------------------------------------------------------------- 1 | FROM ubuntu:12.04.5 2 | 3 | # Because there is no package cache in the image, you need to run: 4 | RUN apt-get update 5 | 6 | # Install nodejs 7 | RUN apt-get install curl -y 8 | RUN apt-get install python-software-properties -y 9 | RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - 10 | RUN apt-get update 11 | RUN apt-get install -y nodejs 12 | 13 | # Install tty-table 14 | RUN apt-get install git -y 15 | RUN git clone https://www.github.com/tecfu/tty-table 16 | 17 | WORKDIR /tty-table 18 | 19 | # Manual Phantomjs install 20 | RUN apt-get install wget -y 21 | RUN apt-get install bzip2 -y 22 | RUN apt-get install libfontconfig1 -y 23 | RUN cd /usr/local/share/ 24 | RUN wget https://bitbucket.org/ariya/phantomjs/downloads/phantomjs-1.9.6-linux-x86_64.tar.bz2 25 | RUN ls 26 | RUN tar xjf phantomjs-1.9.6-linux-x86_64.tar.bz2 27 | RUN rm -f phantomjs-1.9.6-linux-x86_64.tar.bz2 28 | RUN ln -s phantomjs-1.9.6-linux-x86_64 phantomjs 29 | RUN ln -s /usr/local/share/phantomjs/bin/phantomjs /usr/bin/phantomjs 30 | 31 | #RUN npm uninstall -D grunt-mocha 32 | #RUN npm i grunt-mocha@0.4.13 33 | RUN npm install 34 | 35 | # Run unit tests 36 | RUN node -v 37 | RUN npx mocha 38 | -------------------------------------------------------------------------------- /docker/Dockerfile.ubuntu-14.04: -------------------------------------------------------------------------------- 1 | FROM ubuntu:14.04.5 2 | 3 | # Because there is no package cache in the image, you need to run: 4 | RUN apt-get update 5 | 6 | # Install nodejs 7 | RUN apt-get install curl -y 8 | RUN apt-get install python-software-properties -y 9 | RUN curl -sL https://deb.nodesource.com/setup_8.x | bash - 10 | RUN apt-get update 11 | RUN apt-get install -y nodejs 12 | 13 | # Install tty-table 14 | RUN apt-get install git -y 15 | RUN git clone https://www.github.com/tecfu/tty-table 16 | 17 | # Install dev dependencies 18 | WORKDIR /tty-table 19 | RUN npm install 20 | 21 | # Run unit tests 22 | RUN node -v 23 | RUN npm run test 24 | -------------------------------------------------------------------------------- /docker/Dockerfile.ubuntu-18.04: -------------------------------------------------------------------------------- 1 | FROM ubuntu:18.04 2 | 3 | # Because there is no package cache in the image, you need to run: 4 | RUN apt update 5 | 6 | # Install nodejs 7 | RUN apt install curl gnupg -y 8 | RUN curl -sL https://deb.nodesource.com/setup_11.x | bash - 9 | RUN apt update 10 | RUN apt install -y nodejs 11 | 12 | # Install tty-table 13 | RUN apt install git -y 14 | RUN git clone https://www.github.com/tecfu/tty-table 15 | 16 | # Install dev dependencies 17 | WORKDIR /tty-table 18 | RUN npm install 19 | 20 | # Run unit tests & report coverage 21 | RUN node -v 22 | # RUN npx mocha 23 | RUN npm run coverage 24 | RUN npm run report-to-coverio 25 | RUN npm run report-to-coveralls 26 | -------------------------------------------------------------------------------- /docs/automattic-cli-table.md: -------------------------------------------------------------------------------- 1 | # Automattic/cli-table Adapter 2 | 3 | - tty-table can be used as a drop-in replacement for [Automattic/cli-table](https://github.com/Automattic/cli-table) 4 | 5 | ### Sample Usage 6 | (from Automattic/cli-table README.md): 7 | 8 | ``` 9 | 10 | var Table = require('tty-table/automattic-cli-table'); 11 | 12 | /* col widths */ 13 | var table = new Table({ 14 | head: ['Rel', 'Change', 'By', 'When'] 15 | , colWidths: [6, 21, 25, 17] 16 | }); 17 | table.push( 18 | ['v0.1', 'Testing something cool', 'rauchg@gmail.com', '7 minutes ago'] 19 | , ['v0.1', 'Testing something cool', 'rauchg@gmail.com', '8 minutes ago'] 20 | ); 21 | console.log(table.toString(); 22 | 23 | /* compact */ 24 | var table = new Table({ 25 | head: ['Rel', 'Change', 'By', 'When'] 26 | , colWidths: [6, 21, 25, 17] 27 | , style : {compact : true, 'padding-left' : 1} 28 | }); 29 | table.push( 30 | ['v0.1', 'Testing something cool', 'rauchg@gmail.com', '7 minutes ago'] 31 | , ['v0.1', 'Testing something cool', 'rauchg@gmail.com', '8 minutes ago'] 32 | , [] 33 | , ['v0.1', 'Testing something cool', 'rauchg@gmail.com', '8 minutes ago'] 34 | ); 35 | console.log(table.toString()); 36 | 37 | /* headless */ 38 | var headless_table = new Table(); 39 | headless_table.push(['v0.1', 'Testing something cool', 'rauchg@gmail.com', '7 minutes ago']); 40 | console.log(headless_table.toString()); 41 | 42 | /* vertical */ 43 | var vertical_table = new Table({ 44 | style : { 45 | head : ['green'] 46 | } 47 | }); 48 | vertical_table.push({ "Some Key": "Some Value"}, 49 | { "Another much longer key": "And its corresponding longer value"} 50 | ); 51 | console.log(vertical_table.toString()); 52 | 53 | /* cross */ 54 | var cross_table = new Table({ head: ["", "Header #1", "Header #2"] }); 55 | cross_table.push({ "Header #3": ["Value 1", "Value 2"] }, 56 | { "Header #4": ["Value 3", "Value 4"] }); 57 | console.log(cross_table.toString()); 58 | 59 | ``` 60 | 61 | - Outputs: 62 | 63 | ![Automattic/cli-table Example Output](https://cloud.githubusercontent.com/assets/7478359/15693270/c901a0ce-2748-11e6-8fcb-e946b6c608f1.png "Automattic/cli-table Output Example") 64 | 65 | -------------------------------------------------------------------------------- /docs/pull_request_template.md: -------------------------------------------------------------------------------- 1 | - Please format your commit messages according to the ["Conventional Commits"](https://www.conventionalcommits.org/en/v1.0.0/) specification 2 | 3 | If you aren't familiar with Conventional Commits, here's a good [article on the topic](https://dev.to/maniflames/how-conventional-commits-improved-my-git-skills-1jfk) 4 | 5 | TL/DR: 6 | 7 | - feat: a feature that is visible for end users. 8 | - fix: a bugfix that is visible for end users. 9 | - chore: a change that doesn't impact end users (e.g. chances to CI pipeline) 10 | - docs: a change in the README or documentation 11 | - refactor: a change in production code focused on readability, style and/or performance. 12 | -------------------------------------------------------------------------------- /docs/terminal.md: -------------------------------------------------------------------------------- 1 | # Using tty-table as a terminal application: 2 | 3 | ## Installation 4 | 5 | ``` 6 | $ sudo apt-get install nodejs 7 | $ npm install tty-table -g 8 | ``` 9 | 10 | ## Example Usage 11 | 12 | 13 | ### Inline 14 | 15 | - Pipe some inline data to tty-table: 16 | 17 | ``` 18 | echo '[["name","price"],["aapl",92.50],["ibm",120.15]]' | tty-table --format=json 19 | ``` 20 | 21 | ### Files 22 | 23 | - Pipe a csv file to tty-table via the command line: 24 | 25 | ``` 26 | $ cat examples/data/data.csv | tty-table 27 | ``` 28 | 29 | ### Streams 30 | 31 | - Pipe a stream JSON to tty-table via the command line: 32 | 33 | ``` 34 | $ node examples/data/streamer.js | tty-table --format==JSON 35 | ``` 36 | 37 | *CSV is the default input format. 38 | 39 | 40 | ## Example Stream Output 41 | 42 | ![Terminal Example](https://cloud.githubusercontent.com/assets/7478359/15691533/39f5fed4-273e-11e6-81a6-533bd8dbd1c4.gif "Terminal Example") 43 | 44 | -------------------------------------------------------------------------------- /examples/auto-resize-percentage-widths.js: -------------------------------------------------------------------------------- 1 | const example = require("../test/example-utils.js") 2 | const Table = require("../") 3 | 4 | const baseRows = [[ 5 | "aaa bbb ccc", 6 | "aaa bbb ccc ddd eee fff ggg hhh", 7 | "aaa bbb ccc ddd eee fff ggg hhh iii jjj kkk lll mmm nnn ooo ppp qqq rrr sss ttt" 8 | ]] 9 | const baseHeaders = [ 10 | { alias: "Short", width: "20%" }, 11 | { alias: "Medium Header", width: "30%" }, 12 | { alias: "A Very, Very Long Header", width: "50%" } 13 | ] 14 | const baseConfig = { 15 | marginLeft: 0 16 | } 17 | 18 | function resizeTestCase (width, overrideConfig, customRows, customHeaders) { 19 | const config = Object.assign({}, baseConfig, overrideConfig) 20 | const rows = (!customRows) ? baseRows : customRows 21 | const headers = (!customHeaders) ? baseHeaders : customHeaders 22 | 23 | console.log("") 24 | example.init(`Test-w=${width}-${JSON.stringify(overrideConfig)}`, width, config) 25 | 26 | const table = new Table(headers, rows, config) 27 | const output = table.render() 28 | const widest = example.getMaxLineWidth(output) 29 | 30 | if (widest > width) { 31 | example.error(`Table Too Wide: target=${width} widest-line=${widest}`) 32 | } 33 | console.log(output) 34 | return output 35 | } 36 | 37 | const NONE = undefined 38 | const DEFAULT_WIDTH = "25-125" 39 | const DEFAULT_TRUNCATE = [NONE] 40 | const DEFAULT_RESIZE = [NONE] 41 | const DEFAULT_MIN_WIDTH = [NONE] 42 | 43 | const program = require("commander").description("test resizing fonctionality") 44 | .option("--color", "") 45 | .option("-w, --width ", "simulated console width (csv/ranges)", DEFAULT_WIDTH) 46 | .option("-t, --truncate ", "truncation config (csv/ranges)", DEFAULT_TRUNCATE) 47 | .option("-r, --resize ", "resize config (csv/ranges)", DEFAULT_RESIZE) 48 | .option("-m, --min-col-width ", "resize config (csv/ranges)", DEFAULT_MIN_WIDTH) 49 | .parse(process.argv) 50 | 51 | for (const width of example.parseNumericList(program.width)) { 52 | for (const truncate of example.parseList(program.truncate)) { 53 | for (const resize of example.parseList(program.resize)) { 54 | for (const minColWidth of example.parseList(program.minColWidth)) { 55 | const config = {} 56 | example.setOption(config, "truncate", truncate) 57 | example.setOption(config, "resize", resize) 58 | example.setOption(config, "minColWidth", minColWidth) 59 | resizeTestCase(width, config, null, null) 60 | } 61 | } 62 | } 63 | } 64 | 65 | example.dumpMessages() 66 | -------------------------------------------------------------------------------- /examples/auto-resize-single-column-widths.js: -------------------------------------------------------------------------------- 1 | const example = require("../test/example-utils.js") 2 | const Table = require("../") 3 | 4 | const baseConfig = { 5 | marginLeft: 0, 6 | marginTop: 0 7 | } 8 | const defaultSampleText = "this is a test" 9 | 10 | const program = require("commander").description("example test script") 11 | .option("-c, --columns ", "list number/ranges to test. ie 1-25 or 10", "1-25") 12 | .option("-t, --text ", "sample text", defaultSampleText) 13 | .option("--color", "to support chalk meta argument") 14 | .parse(process.argv) 15 | 16 | function testColumns (cols, sampleText, overrideConfig) { 17 | const config = Object.assign({}, baseConfig, overrideConfig || {}) 18 | const rows = [[]] 19 | 20 | for (let i = 0; i < cols; i++) { 21 | rows[0].push(sampleText) 22 | } 23 | 24 | const table = new Table(rows, config) 25 | const output = table.render() 26 | 27 | example.init(`Test ${cols} columns`, (cols * 2) + 1) // need to account for border 28 | console.log(output) 29 | } 30 | 31 | for (const cols of example.parseNumericList(program.columns)) { 32 | for (const text of example.parseList(program.text)) { 33 | testColumns(cols, text) 34 | } 35 | } 36 | -------------------------------------------------------------------------------- /examples/auto-resize-undeclared-widths.js: -------------------------------------------------------------------------------- 1 | const example = require("../test/example-utils.js") 2 | const Table = require("../") 3 | 4 | const baseRows = [[ 5 | "aaa bbb ccc", 6 | "aaa bbb ccc ddd eee fff ggg hhh", 7 | "aaa bbb ccc ddd eee fff ggg hhh iii jjj kkk lll mmm nnn ooo ppp qqq rrr sss ttt" 8 | ]] 9 | const baseHeaders = [ 10 | { alias: "Short" }, 11 | { alias: "Medium Header" }, 12 | { alias: "A Very, Very Long Header" } 13 | ] 14 | const baseConfig = { 15 | marginTop: 0 16 | } 17 | 18 | function resizeTestCase (width, overrideConfig, customRows, customHeaders) { 19 | const config = Object.assign({}, baseConfig, overrideConfig) 20 | const rows = (!customRows) ? baseRows : customRows 21 | const headers = (!customHeaders) ? baseHeaders : customHeaders 22 | 23 | console.log("") 24 | example.init(`Test-w=${width}-${JSON.stringify(overrideConfig)}`, width, config) 25 | 26 | const table = new Table(headers, rows, config) 27 | const output = table.render() 28 | const widest = example.getMaxLineWidth(output) 29 | 30 | if (widest > width) { 31 | example.error(`Table Too Wide: target=${width} widest-line=${widest}`) 32 | } 33 | console.log(output) 34 | return output 35 | } 36 | 37 | const NONE = undefined 38 | const DEFAULT_WIDTH = "25-119" 39 | const DEFAULT_TRUNCATE = [NONE] 40 | const DEFAULT_RESIZE = [NONE] 41 | const DEFAULT_MIN_WIDTH = [NONE] 42 | 43 | const program = require("commander").description("test resizing fonctionality") 44 | .option("--color", "") 45 | .option("-w, --width ", "simulated console width (csv/ranges)", DEFAULT_WIDTH) 46 | .option("-t, --truncate ", "truncation config (csv/ranges)", DEFAULT_TRUNCATE) 47 | .option("-r, --resize ", "resize config (csv/ranges)", DEFAULT_RESIZE) 48 | .option("-m, --min-col-width ", "resize config (csv/ranges)", DEFAULT_MIN_WIDTH) 49 | .parse(process.argv) 50 | 51 | for (const width of example.parseNumericList(program.width)) { 52 | for (const truncate of example.parseList(program.truncate)) { 53 | for (const resize of example.parseList(program.resize)) { 54 | for (const minColWidth of example.parseList(program.minColWidth)) { 55 | const config = {} 56 | example.setOption(config, "truncate", truncate) 57 | example.setOption(config, "resize", resize) 58 | example.setOption(config, "minColWidth", minColWidth) 59 | resizeTestCase(width, config, undefined, null) 60 | } 61 | } 62 | } 63 | } 64 | 65 | example.dumpMessages() 66 | -------------------------------------------------------------------------------- /examples/auto-with-percentage.js: -------------------------------------------------------------------------------- 1 | const example = require("../test/example-utils.js") 2 | const Table = require("../") 3 | 4 | const baseRows = [[ 5 | "aaa bbb ccc", 6 | "aaa bbb ccc ddd eee fff ggg hhh" 7 | ]] 8 | 9 | const baseHeaders = [ 10 | { alias: "Fixed header", width: "25%" }, 11 | { alias: "Auto header" } 12 | ] 13 | 14 | example.init("Test non divisible column width", 100) 15 | const t1 = Table(baseHeaders, baseRows) 16 | 17 | console.log(t1.render()) 18 | example.dumpMessages() 19 | -------------------------------------------------------------------------------- /examples/auttomatic-cli-table-tests.js: -------------------------------------------------------------------------------- 1 | require("../test/example-utils.js").quickInit() 2 | const Table = require("../")("automattic-cli-table") 3 | 4 | /* col widths */ 5 | const t1 = new Table({ 6 | head: ["Rel", "Change", "By", "When"], 7 | colWidths: [6, 21, 25, 17] 8 | }) 9 | t1.push( 10 | ["v0.1", "Testing something cool", "rauchg@gmail.com", "7 minutes ago"] 11 | , ["v0.1", "Testing something cool", "rauchg@gmail.com", "8 minutes ago"] 12 | ) 13 | console.log(t1.toString()) 14 | 15 | /* compact */ 16 | const t2 = new Table({ 17 | head: ["Rel", "Change", "By", "When"], 18 | colWidths: [6, 21, 25, 17], 19 | style: { compact: true, "padding-left": 1 } 20 | }) 21 | t2.push( 22 | ["v0.1", "Testing something cool", "rauchg@gmail.com", "7 minutes ago"] 23 | , ["v0.1", "Testing something cool", "rauchg@gmail.com", "8 minutes ago"] 24 | , [] 25 | , ["v0.1", "Testing something cool", "rauchg@gmail.com", "8 minutes ago"] 26 | ) 27 | console.log(t2.toString()) 28 | 29 | /* headless */ 30 | var headlessTable = new Table() 31 | headlessTable.push(["v0.1", "Testing something cool", "rauchg@gmail.com", "7 minutes ago"]) 32 | console.log(headlessTable.toString()) 33 | 34 | /* vertical */ 35 | var verticalTable = new Table({ 36 | style: { 37 | head: ["green"] 38 | } 39 | }) 40 | verticalTable.push({ "Some Key": "Some Value" }, 41 | { "Another much longer key": "And its corresponding longer value" } 42 | ) 43 | console.log(verticalTable.toString()) 44 | 45 | /* cross */ 46 | var crossTable = new Table({ head: ["", "Header #1", "Header #2"] }) 47 | crossTable.push({ "Header #3": ["Value 1", "Value 2"] }, 48 | { "Header #4": ["Value 3", "Value 4"] }) 49 | console.log(crossTable.toString()) 50 | 51 | /* additional to improve code coverage */ 52 | const misc = new Table({ 53 | head: ["Rel", "Change", "By", "When"], 54 | colWidths: [6, 21, 25, 17], 55 | colAligns: ["left", "left", "right", "right"], 56 | style: { compact: true, "padding-right": 1, body: ["green"] } 57 | }) 58 | misc.push( 59 | ["v0.1", "Testing something cool", "rauchg@gmail.com", "7 minutes ago"] 60 | , ["v0.1", "Testing something cool", "rauchg@gmail.com", "8 minutes ago"] 61 | , [] 62 | , ["v0.1", "Testing something cool", "rauchg@gmail.com", "8 minutes ago"] 63 | ) 64 | console.log(misc.toString()) 65 | -------------------------------------------------------------------------------- /examples/browser-example.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 92 | 93 | 94 |

Source: examples/browser-example.html

95 | Only known to be supported in Chrome > 79. 96 |
 97 |       
 98 |     
99 | 100 | 101 | -------------------------------------------------------------------------------- /examples/cli-test.js: -------------------------------------------------------------------------------- 1 | require("../test/example-utils.js").quickInit() 2 | const pkg = require("../package.json") 3 | const cp = require("child_process") 4 | const pwd = __dirname 5 | 6 | const output1 = cp.execSync(`cat ${pwd}/data/data.csv | node ${pwd}/../adapters/terminal-adapter.js`, { 7 | encoding: "utf8", 8 | env: { ...process.env, COLUMNS: pkg.defaultTestColumns } // since child process wont inherit process.stdout.columns 9 | }) 10 | console.log(output1) 11 | 12 | const output2 = cp.execSync(`cat ${pwd}/data/data.json | node ${pwd}/../adapters/terminal-adapter.js --format json`, { 13 | encoding: "utf8", 14 | env: { ...process.env, COLUMNS: pkg.defaultTestColumns } // since child process wont inherit process.stdout.columns 15 | }) 16 | console.log(output2) 17 | 18 | const output3 = cp.execSync(`cat ${pwd}/data/data-wide.csv | node ${pwd}/../adapters/terminal-adapter.js --options-width 20`, { 19 | encoding: "utf8", 20 | env: { ...process.env, COLUMNS: pkg.defaultTestColumns } // since child process wont inherit process.stdout.columns 21 | }) 22 | console.log(output3) 23 | -------------------------------------------------------------------------------- /examples/config/header.js: -------------------------------------------------------------------------------- 1 | module.exports = [ 2 | { 3 | value: "ticker", 4 | color: "cyan" 5 | }, 6 | { 7 | value: "last", 8 | color: "red", 9 | width: 10, 10 | formatter: function (value) { 11 | var str = `$${value.toFixed(2)}` 12 | if (value > 138) { 13 | str = this.style(str, "green", "bold") 14 | } 15 | return str 16 | } 17 | } 18 | ] 19 | -------------------------------------------------------------------------------- /examples/config/header.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "value": "ticker", 4 | "color": "cyan" 5 | }, 6 | { 7 | "value": "last", 8 | "color": "red" 9 | } 10 | ] 11 | -------------------------------------------------------------------------------- /examples/constructor-types.js: -------------------------------------------------------------------------------- 1 | const Table = require("../") 2 | 3 | const header = [ 4 | { 5 | value: "item" 6 | } 7 | ] 8 | 9 | // Example with arrays as rows 10 | const rows1 = [ 11 | [1] 12 | ] 13 | 14 | const rows2 = [ 15 | [{ value: 1 }] 16 | ] 17 | 18 | const footer = [ 19 | function (cellValue, columnIndex, rowIndex, rowData) { 20 | const total = rowData.reduce((prev, curr) => { 21 | return prev + (typeof curr[0] === "object") ? curr[0].value : curr[0] 22 | }, 0) 23 | return `${(total / rowData.length * 100).toFixed(2)}%` 24 | } 25 | ] 26 | 27 | const options = { 28 | borderColor: "green", 29 | width: "80%" 30 | } 31 | 32 | // header, rows, footer, and options 33 | const A1 = Table(header, rows1, footer, options).render() 34 | const A2 = Table(header, rows2, footer, options).render() 35 | 36 | // header, rows, footer 37 | const B1 = Table(header, rows1, footer).render() 38 | const B2 = Table(header, rows2, footer).render() 39 | 40 | // header, rows, options 41 | const C1 = Table(header, rows1, options).render() 42 | const C2 = Table(header, rows2, options).render() 43 | 44 | // header, rows (rows, footer is not an option) 45 | const D1 = Table(header, rows1).render() 46 | const D2 = Table(header, rows2).render() 47 | 48 | // rows, options 49 | const E1 = Table(rows1, options).render() 50 | const E2 = Table(rows2, options).render() 51 | 52 | // rows 53 | const F1 = Table(rows1).render() 54 | const F2 = Table(rows2).render() 55 | 56 | // adapter called: i.e. `require('tty-table')('automattic-cli')` 57 | 58 | console.log(A1, A2, B1, B2, C1, C2, D1, D2, E1, E2, F1, F2) 59 | -------------------------------------------------------------------------------- /examples/data/data-wide.csv: -------------------------------------------------------------------------------- 1 | F0,F1,F2,F3,F4,F5,F6,F7,F8,F9,F10 2 | F0,F1,F2,F3,F4,F5,F6,F7,F8,F9,F101212121212121212122121212121212121212121212121 3 | -------------------------------------------------------------------------------- /examples/data/data.csv: -------------------------------------------------------------------------------- 1 | SYMBOL,LAST (USD) 2 | AAPL,92.50 3 | IBM,120.15 4 | WMT,65.23 5 | DIS,98.42 6 | GE,30.85 7 | -------------------------------------------------------------------------------- /examples/data/data.json: -------------------------------------------------------------------------------- 1 | [ 2 | ["SYMBOL","LAST (USD)"], 3 | ["AAPL",92.5], 4 | ["IBM",120.15], 5 | ["WMT",65.23], 6 | ["DIS",98.42], 7 | ["GE",30.85] 8 | ] 9 | -------------------------------------------------------------------------------- /examples/data/delimiter-inside-quoted-fields.csv: -------------------------------------------------------------------------------- 1 | aapl,"Hello, World" 2 | ibm,"120,15" 3 | -------------------------------------------------------------------------------- /examples/data/fake-stream.js: -------------------------------------------------------------------------------- 1 | const tickers = [ 2 | ["AAPL", 138], 3 | ["IBM", 120], 4 | ["WMT", 68], 5 | ["ABX", 13], 6 | ["MSFT", 35] 7 | ] 8 | 9 | const cycle = (iterator, tickers) => { 10 | // change ticker values 11 | tickers = tickers.map(value => { 12 | const sign = (Math.random()) < 0.5 ? -1 : 1 13 | const increment = Math.random() 14 | const newVal = (value[1] + sign * increment).toFixed(2) * 1 15 | return [value[0], newVal] 16 | }) 17 | 18 | console.log(JSON.stringify(tickers)) 19 | iterator++ 20 | 21 | setTimeout(function () { 22 | cycle(iterator, tickers) 23 | }, 500) 24 | } 25 | 26 | process.stdout.on("error", () => { 27 | process.exit(1) 28 | }) 29 | 30 | cycle(1, tickers) 31 | -------------------------------------------------------------------------------- /examples/data/streamer.js: -------------------------------------------------------------------------------- 1 | const header = ["SYMBOL", "LAST"] 2 | const baseline = { 3 | aapl: 92, 4 | ibm: 120.72, 5 | wmt: 68.93, 6 | abx: 13.36, 7 | msft: 35.26 8 | } 9 | 10 | setInterval(function () { 11 | // //Add imaginary ticker 12 | // let newTicker = Math.random().toString(36).substring(7); 13 | // baseline[newTicker] = Math.random(); 14 | // 15 | // //Remove a random ticker 16 | // let keys = Object.keys(baseline); 17 | // let random = Math.floor(Math.random() * keys.length) + 0; 18 | // delete baseline[keys[random]]; 19 | 20 | const array = [header] 21 | 22 | for (const i in baseline) { 23 | // give each symbol a 30% chance of changing 24 | if (Math.random() >= 0.7) { 25 | baseline[i] = (baseline[i] + ((Math.random() > 0.5) ? 0.01 : -0.01)).toFixed(2) * 1 26 | } 27 | array.push([i, `$ ${baseline[i].toFixed(2)}`]) 28 | } 29 | 30 | const string = JSON.stringify(array) 31 | console.log(string) 32 | }, 500) 33 | 34 | process.stdout.on("error", function () { 35 | process.exit(1) 36 | }) 37 | -------------------------------------------------------------------------------- /examples/empty-rows.js: -------------------------------------------------------------------------------- 1 | require("../test/example-utils.js").quickInit() 2 | const Table = require("../") 3 | 4 | const header = [ 5 | { value: "col1" }, 6 | { value: "col2" }, 7 | { value: "col3" } 8 | ] 9 | const rows = [] 10 | const t = Table(header, rows) 11 | console.log(t.render()) 12 | -------------------------------------------------------------------------------- /examples/hide-header.js: -------------------------------------------------------------------------------- 1 | require("../test/example-utils.js").quickInit() 2 | const Table = require("../") 3 | 4 | const headerA = [ 5 | { value: "column 1", width: 20 }, { width: 10 }, { width: 30 } 6 | ] 7 | 8 | const headerB = [ 9 | { width: 20 }, { width: 10 }, { width: 30 } 10 | ] 11 | 12 | const rows = [ 13 | ["e", "pluribus", "unum"] 14 | ] 15 | 16 | const out = [] 17 | 18 | out.push(Table(headerA, rows, {}).render()) // show automatically 19 | out.push(Table(headerA, rows, { showHeader: true }).render()) // show 20 | out.push(Table(headerA, rows, { showHeader: false }).render()) // hide 21 | 22 | out.push(Table(headerB, rows, {}).render()) // hide automatically 23 | out.push(Table(headerB, rows, { showHeader: true }).render()) // show 24 | out.push(Table(headerB, rows, { showHeader: false }).render()) // hide 25 | 26 | console.log(out.join("\n")) 27 | -------------------------------------------------------------------------------- /examples/multiple-tables.js: -------------------------------------------------------------------------------- 1 | require("../test/example-utils.js").quickInit() 2 | const Table = require("../") 3 | 4 | const header = [ 5 | { value: "col1" }, 6 | { value: "col2" } 7 | ] 8 | const rows = [[10001, 20002]] 9 | 10 | const header2 = [ 11 | { value: "col1" }, 12 | { value: "col2" }, 13 | { value: "col3" } 14 | ] 15 | const rows2 = [ 16 | [1, 2, 3], 17 | [3, 34, 99] 18 | ] 19 | 20 | const t1 = Table(header, rows) 21 | const t2 = Table(header2, rows2) 22 | console.log(t2.render()) 23 | console.log(t1.render()) 24 | -------------------------------------------------------------------------------- /examples/no-border-no-header.js: -------------------------------------------------------------------------------- 1 | require("../test/example-utils.js").quickInit() 2 | const Table = require("../") 3 | 4 | // No empty space where horizontal border would be 5 | const rows = [ 6 | ["xxxyyyzzz", "aaaaa", "bbbbbb", "11111111"], 7 | ["zzzxxxyyy", "bbbbbb", "cccccc", "2222222"] 8 | ] 9 | const t = Table(rows, { 10 | borderStyle: "none", 11 | compact: true, 12 | align: "left", 13 | color: "white", 14 | marginTop: 0, 15 | marginLeft: 0 16 | }) 17 | console.log(t.render()) 18 | -------------------------------------------------------------------------------- /examples/null-undefined-values.js: -------------------------------------------------------------------------------- 1 | require("../test/example-utils.js").quickInit() 2 | const Table = require("../") 3 | 4 | const header = [ 5 | { 6 | value: "item", 7 | formatter: function (value) { 8 | return this.style(value, "cyan") 9 | } 10 | }, 11 | { value: "price" }, 12 | { value: "organic" } 13 | ] 14 | 15 | const options = { 16 | borderStyle: "solid", 17 | paddingBottom: 0, 18 | headerAlign: "center", 19 | align: "center", 20 | color: "green", 21 | footerColor: "yellow", 22 | footerAlign: "right" 23 | } 24 | 25 | // Example with arrays as rows 26 | const rows = [ 27 | [], 28 | ["special sauce", 0.10, "yes"], 29 | [null, 1.50, "no", "extra element", "another extra element"], 30 | ["macaroni and cheese", 3.75], 31 | [0, 0, 0] 32 | ] 33 | 34 | // Example with objects as rows 35 | const rows2 = [ 36 | {}, 37 | { 38 | item: "special sauce", 39 | price: 0.10, 40 | organic: "yes" 41 | }, 42 | { 43 | item: null, 44 | price: 1.50, 45 | organic: "no" 46 | }, 47 | { 48 | item: "macaroni and cheese", 49 | price: 3.75 50 | }, 51 | { 52 | item: 0, 53 | price: 0, 54 | organic: 0 55 | } 56 | ] 57 | 58 | const footer = [ 59 | "TOTAL", 60 | (cellValue, columnIndex, rowIndex, rowData) => { 61 | return rowData.reduce((prev, curr) => { 62 | return (curr[1]) ? prev + curr[1] : prev 63 | }, 0).toFixed(2) 64 | }, 65 | function (cellValue, columnIndex, rowIndex, rowData) { 66 | const total = rowData.reduce((prev, curr) => { 67 | return prev + ((curr[2] === "yes") ? 1 : 0) 68 | }, 0) 69 | return `${(total / rowData.length * 100).toFixed(2)}%` 70 | } 71 | ] 72 | 73 | const t1 = Table(header, rows, footer, options) 74 | const t2 = Table(header, rows2, footer, options) 75 | 76 | t1.push( 77 | ["chocolate cake", 4.65, "no"] 78 | ) 79 | t2.push( 80 | { item: "chocolate cake", price: 4.65, organic: "no" } 81 | ) 82 | 83 | console.log(t1.render()) 84 | console.log(t2.render()) 85 | -------------------------------------------------------------------------------- /examples/placeholder-for-wide-chars.js: -------------------------------------------------------------------------------- 1 | require("../test/example-utils.js").quickInit(25, true) 2 | const Table = require("../") 3 | 4 | /** 5 | * In this example the wide characters in the first column are replaced 6 | * with a placeholder when the column widths are inferred. 7 | */ 8 | const rows = [[ 9 | "aaa bbb ccc", 10 | "aaa bbb ccc ddd eee fff ggg hhh", 11 | "aaa bbb ccc ddd eee fff ggg hhh iii jjj kkk lll mmm nnn ooo ppp qqq rrr sss ttt" 12 | ], [ 13 | "汉堡包", 14 | "特制的酱汁", 15 | "2玉米饼, 大米和豆类, 大米和豆类, 大米和豆类, 奶酪" 16 | ]] 17 | 18 | const output1 = new Table(rows, { 19 | marginLeft: 0, 20 | paddingLeft: 0, 21 | paddingRight: 0, 22 | marginTop: 0 23 | }).render() 24 | 25 | console.log(output1) 26 | -------------------------------------------------------------------------------- /examples/styles-and-formatting.js: -------------------------------------------------------------------------------- 1 | require("../test/example-utils.js").quickInit() 2 | const Table = require("../") 3 | 4 | const header = [ 5 | { 6 | value: "item", 7 | headerColor: "cyan", 8 | color: "white", 9 | align: "left", 10 | width: "20%" 11 | }, 12 | { 13 | value: "description", 14 | width: "40%", 15 | headerColor: "magenta", 16 | color: "yellow" 17 | }, 18 | { 19 | value: "peru_price", 20 | alias: "peru", 21 | color: "red", 22 | width: "15%", 23 | formatter: function (value) { 24 | const str = `$${value.toFixed(2)}` 25 | return (value > 5) ? this.style(str, "green", "bold") 26 | : this.style(str, "red", "bold") 27 | } 28 | }, 29 | { 30 | value: "us_price", 31 | alias: "usa", 32 | color: "red", 33 | width: "15%", 34 | formatter: function (value) { 35 | const str = `$${value.toFixed(2)}` 36 | return (value > 5) ? this.style(str, "green", "bold") 37 | : this.style(str, "red", "underline") 38 | } 39 | }, 40 | { 41 | alias: "vegan", 42 | value: "organic", 43 | align: "right", 44 | width: "10%", 45 | formatter: function (value) { 46 | if (value === "yes") { 47 | value = this.style(value, "bgGreen", "black") 48 | } else { 49 | value = this.style(value, "bgRed", "white") 50 | } 51 | return value 52 | } 53 | } 54 | ] 55 | 56 | // Example with objects as rows 57 | const rows = [ 58 | { 59 | item: "tallarin verde", 60 | description: "Peruvian version of spaghetti with pesto. Includes a thin piece of fried chicken breast", 61 | peru_price: 2.50, 62 | us_price: 15.50, 63 | organic: "no" 64 | }, 65 | { 66 | item: "aji de gallina", 67 | description: "Heavy aji cream sauce, rice, and chicken with a halved hard-boiled egg", 68 | peru_price: 1.80, 69 | us_price: 14.50, 70 | organic: "no" 71 | } 72 | ] 73 | 74 | // Example with arrays as rows 75 | const rows2 = [ 76 | ["tallarin verde", 2.50, 15.50, "no"], 77 | ["aji de gallina", 1.80, 14.50, "no"] 78 | ].map((arr, index) => { 79 | arr.splice(1, 0, rows[index].description); return arr 80 | }) 81 | 82 | const footer = [ 83 | "TOTAL", 84 | "", 85 | function (cellValue, columnIndex, rowIndex, rowData) { 86 | const total = rowData.reduce((prev, curr) => { 87 | return prev + curr[2] 88 | }, 0) 89 | .toFixed(2) 90 | return this.style(`$${total}`, "italic") 91 | }, 92 | function (cellValue, columnIndex, rowIndex, rowData) { 93 | const total = rowData.reduce((prev, curr) => { 94 | return prev + curr[3] 95 | }, 0) 96 | .toFixed(2) 97 | return this.style(`$${total}`, "italic") 98 | }, 99 | function (cellValue, columnIndex, rowIndex, rowData) { 100 | const total = rowData.reduce((prev, curr) => { 101 | return prev + ((curr[4] === "yes") ? 1 : 0) 102 | }, 0) 103 | return `${(total / rowData.length * 100).toFixed(2)}%` 104 | } 105 | ] 106 | 107 | const options = { 108 | borderStyle: "solid", 109 | borderColor: "green", 110 | paddingBottom: 0, 111 | headerAlign: "center", 112 | headerColor: "green", 113 | align: "center", 114 | color: "white", 115 | width: "80%" 116 | } 117 | 118 | const t1 = Table(header, rows, footer, options).render() 119 | console.log(t1) 120 | 121 | const t2 = Table(header, rows2, footer, options).render() 122 | console.log(t2) 123 | 124 | const header3 = [ 125 | { 126 | value: "price", 127 | formatter: function (cellValue, columnIndex, rowIndex, rowData, inputData) { 128 | const row = inputData[rowIndex] // How to get the whole row 129 | let _color 130 | 131 | if (!row.enabled) _color = "gray" 132 | if (row.important) _color = "red" 133 | 134 | return this.style(cellValue, _color) 135 | } 136 | }, 137 | { 138 | value: "item", 139 | color: "underline", 140 | formatter: function (cellValue) { 141 | return (/banana/.test(cellValue)) ? this.resetStyle(cellValue) : cellValue 142 | } 143 | } 144 | ] 145 | 146 | const rows3 = [ 147 | { 148 | price: 1.99, 149 | item: " banana", 150 | important: true, 151 | enabled: true 152 | }, 153 | { 154 | price: 2.99, 155 | item: " grapes", 156 | important: false, 157 | enabled: false 158 | } 159 | ] 160 | 161 | const t3 = Table(header3, rows3, { width: 50, borderStyle: "none", compact: true }).render() 162 | console.log(t3) 163 | 164 | const t4 = Table(header3, rows3, { width: 50, paddingTop: 2, paddingBottom: 2 }).render() 165 | console.log(t4) 166 | 167 | header3[0].alias = header3[0].value 168 | header3[1].alias = header3[1].value 169 | 170 | delete header3[0].value 171 | delete header3[1].value 172 | 173 | const t5 = Table(header3, rows3, { width: 50, paddingTop: 2, paddingBottom: 2 }).render() 174 | console.log(t5) 175 | 176 | delete header3[0].alias 177 | delete header3[1].alias 178 | 179 | // will have a different column width because no header to derive from 180 | const t6 = Table(header3, rows3, { width: 50, paddingTop: 2, paddingBottom: 2 }).render() 181 | console.log(t6) 182 | -------------------------------------------------------------------------------- /examples/template-literal-cell-value.js: -------------------------------------------------------------------------------- 1 | require("../test/example-utils.js").quickInit() 2 | const Table = require("../") 3 | 4 | const rows = [[`The use of the cli-table is deprecated. 5 | Migrate over -> cli-table. Run: 6 | npm un cli-table -g 7 | npm i tty-table -g`]] 8 | 9 | const t1 = Table([], rows, { 10 | borderStyle: "solid", 11 | borderColor: "yellow", 12 | paddingBottom: 0, 13 | headerAlign: "center", 14 | headerColor: "yellow", 15 | align: "center", 16 | color: "white", 17 | width: "50%" 18 | }) 19 | 20 | const str1 = t1.render() 21 | console.log(str1) 22 | -------------------------------------------------------------------------------- /examples/text-wrapping.js: -------------------------------------------------------------------------------- 1 | const example = require("../test/example-utils.js") 2 | const Table = require("../") 3 | const style = Table.style 4 | 5 | function test (testName, tableWidth, rows, config) { 6 | console.log(`\n${style(testName, "cyan")}`) 7 | example.init(`Test: ${testName}`, tableWidth) 8 | const table = new Table(rows, config) 9 | const output = table.render() 10 | 11 | console.log(output) 12 | console.log("") 13 | } 14 | 15 | test("Embedded ansi column width", 50, [ 16 | [`${style("some red words", "red")}`, `some ${style("bold", "bold")} and ${style("dim", "dim")} words`, "plain test"], 17 | ["some plain words", `some ${style("bold", "bold")} and ${style("dim", "dim")} words`, "plain test"] 18 | ], {}) 19 | 20 | test("Embedded ansi breaking", 20, [ 21 | // ["plain text", `a few red words in here`], 22 | ["plain text", `a ${style("few red words in", "red")} here`] 23 | ], {}) 24 | 25 | test("Handle line breaks", 20, [ 26 | ["plain text", `a ${style("few red\n words \nin", "red")} \nhere`] 27 | ], {}) 28 | 29 | test("Embedded emojis", 25, [[ 30 | "aaa bbb ccc", 31 | "aaa bbb ccc 😀😃😄😁 eee fff ggg hhh", 32 | "aaa bbb ccc ddd eee fff ggg hhh iii jjj kkk lll mmm nnn ooo ppp qqq rrr sss ttt" 33 | ]], { 34 | paddingLeft: 0, 35 | paddingRight: 0, 36 | marginTop: 0 37 | }) 38 | -------------------------------------------------------------------------------- /examples/truncated-lines.js: -------------------------------------------------------------------------------- 1 | require("../test/example-utils.js").quickInit() 2 | const Table = require("../") 3 | 4 | const header = [ 5 | { 6 | value: "item name", 7 | formatter: function (value) { 8 | return this.style(value, "cyan") 9 | }, 10 | width: 10 11 | }, 12 | { 13 | value: "price", 14 | width: 10 15 | }, 16 | { 17 | value: "100% organic", 18 | width: 10 19 | } 20 | ] 21 | 22 | const options = { 23 | borderStyle: "solid", 24 | paddingBottom: 0, 25 | headerAlign: "center", 26 | align: "center", 27 | color: "green", 28 | truncate: "..." 29 | } 30 | 31 | // test truncation with elipsis 32 | const t1 = Table(header, [], options) 33 | t1.push( 34 | ["chocolate cake", 4.65, "no"] 35 | ) 36 | const str1 = t1.render() 37 | console.log(str1) 38 | 39 | // test truncation with long value 40 | const t2 = Table(header, [], options) 41 | t2.push( 42 | ["pound cake", 123456789123456789, "no"] 43 | ) 44 | const str2 = t2.render() 45 | console.log(str2) 46 | 47 | // test with padding 48 | const options3 = Object.assign({}, options) 49 | options3.paddingLeft = 2 50 | options3.paddingRight = 2 51 | const t3 = Table(header, [], options3) 52 | t3.push( 53 | ["pound cake", 123456789123456789, "no"] 54 | ) 55 | const str3 = t3.render() 56 | console.log(str3) 57 | 58 | // test truncation with boolean false 59 | const options4 = Object.assign({}, options) 60 | options4.truncate = false 61 | const t4 = Table(header, [], options4) 62 | t4.push( 63 | ["chocolate cake", 4.65, "no"] 64 | ) 65 | const str4 = t4.render() 66 | console.log(str4) 67 | 68 | // test truncation with boolean true 69 | const options5 = Object.assign({}, options) 70 | options5.truncate = true 71 | const t5 = Table(header, [], options5) 72 | t5.push( 73 | ["chocolate cake", 5.65, "no"] 74 | ) 75 | const str5 = t5.render() 76 | console.log(str5) 77 | 78 | // test with asian characters 79 | const t6 = Table([ 80 | { width: 5 }, { width: 4 }, { width: 5 } 81 | ], [], { 82 | truncate: "...", 83 | paddingLeft: 0, 84 | paddingRight: 0 85 | }) 86 | 87 | t6.push( 88 | ["特制的酱汁", 0.10], 89 | ["2玉米饼, 大米和豆类, 奶酪", 9.80, ""], 90 | ["苹果片", 1.00, "yes"] 91 | ) 92 | 93 | const str6 = t6.render() 94 | console.log(str6) 95 | 96 | // customize truncation in individual cell 97 | const header7 = [ 98 | { 99 | value: "item name", 100 | formatter: function (cellValue, columnIndex, rowIndex) { 101 | if (rowIndex === 1) { 102 | this.configure({ 103 | truncate: false, 104 | align: "right" 105 | }) 106 | } 107 | return cellValue 108 | }, 109 | width: 10 110 | } 111 | ] 112 | 113 | const options7 = { 114 | headerAlign: "center", 115 | align: "center", 116 | truncate: "..." 117 | } 118 | 119 | const t7 = Table(header7, [], options7) 120 | 121 | t7.push( 122 | ["chocolate cake"], 123 | ["jewish coffee cake"], 124 | ["birthday cake"], 125 | ["-"] 126 | ) 127 | 128 | const str7 = t7.render() 129 | console.log(str7) 130 | -------------------------------------------------------------------------------- /examples/wide-characters.js: -------------------------------------------------------------------------------- 1 | require("../test/example-utils.js").quickInit() 2 | const Table = require("../") 3 | 4 | const header = [ 5 | { value: "项目" }, 6 | { value: "价格" }, 7 | { value: "有机" } 8 | ] 9 | const rows = [ 10 | ["汉堡包", 2.50, null], 11 | ["特制的酱汁", 0.10], 12 | ["2玉米饼, 大米和豆类, 奶酪", 9.80, ""], 13 | ["苹果片", 1.00, "yes"], 14 | [null, 1.50, "no"], 15 | ["意大利粉, 火腿, 意大利干酪", 3.75, "no"] 16 | ] 17 | const t1 = Table(header, rows, { 18 | borderStyle: "solid", 19 | headerAlign: "right", 20 | align: "center", 21 | color: "white" 22 | }) 23 | console.log(t1.render()) 24 | 25 | const rows2 = [ 26 | ["abc"], 27 | ["abć"], 28 | ["ab한"] 29 | ] 30 | const t2 = Table(header, rows2, { 31 | borderStyle: "dashed", 32 | paddingBottom: 0, 33 | paddingLeft: 2, 34 | paddingRight: 2, 35 | headerAlign: "right", 36 | align: "center", 37 | color: "white" 38 | }) 39 | console.log(t2.render()) 40 | -------------------------------------------------------------------------------- /npm_scripts/run-examples.js: -------------------------------------------------------------------------------- 1 | const fs = require("fs") 2 | const glob = require("glob") 3 | const cp = require("child_process") 4 | const pkg = require("../package.json") 5 | const savedTestDir = `${__dirname}/../test/saved_test_outputs` 6 | 7 | const mode = (process.argv[2] && process.argv[2]) ? process.argv[2] : "view" 8 | 9 | 10 | // get list of all example scripts 11 | const all = glob.sync("examples/*.js") 12 | 13 | // files not to test 14 | const testExclude = glob.sync("examples/example-*.js").map(path => path.split("/").pop()) 15 | 16 | // files not to show as examples 17 | const viewExclude = [ 18 | "auto-resize-percentage-widths.js", 19 | "auto-resize-undeclared-widths.js", 20 | "example-script.js" 21 | ] 22 | debugger 23 | const exclude = (mode === "test") ? testExclude : viewExclude 24 | const list = all.filter(file => !exclude.includes(file.split("/").pop())) 25 | 26 | list.forEach( filepath => { 27 | // exports process.stdout.columns 28 | const stdout = cp.execSync(`node ./${filepath} --color=always`, { 29 | encoding: "utf8", 30 | env: { ...process.env, "COLUMNS": pkg.defaultTestColumns } // since child process wont inherit process.stdout.columns 31 | }) 32 | 33 | const subname = filepath.split("/").pop().split(".")[0] 34 | const filename = `${subname}-output.txt` 35 | const savepath = `${savedTestDir}/${filename}` 36 | 37 | console.log(stdout) 38 | 39 | if (mode === "save") { 40 | fs.writeFileSync(savepath, stdout) 41 | console.log(`Wrote output to text file: ${savepath}\n`) 42 | } 43 | }) 44 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tty-table", 3 | "version": "4.2.3", 4 | "description": "Node cli table", 5 | "main": "src/main.js", 6 | "types": "src/factory.d.ts", 7 | "engines": { 8 | "node": ">=8.0.0" 9 | }, 10 | "bin": { 11 | "tty-table": "adapters/terminal-adapter.js" 12 | }, 13 | "files": [ 14 | "adapters/", 15 | "src/", 16 | "LICENSE.txt" 17 | ], 18 | "preferGlobal": false, 19 | "scripts": { 20 | "dist": "npx grunt browserify && npx rollup -c", 21 | "coverage": "npx nyc mocha", 22 | "test": "npx mocha", 23 | "report-to-coveralls": "npx nyc report --reporter=text-lcov | npx coveralls", 24 | "report-to-coverio": "npx nyc report --reporter=text-lcov > coverage.lcov && ./node_modules/.bin/codecov -t ffe0f46d-c939-4302-b199-0f2de3e8c18a", 25 | "save-tests": "node npm_scripts/run-examples.js save", 26 | "view-examples": "node npm_scripts/run-examples.js view", 27 | "lint": "npx eslint adapters/*.js src/*.js", 28 | "lint-fix": "npx eslint adapters/*.js src/*.js --fix", 29 | "lint-examples": "npx eslint examples/", 30 | "lint-fix-examples": "npx eslint examples/ --fix", 31 | "prepublishOnly": "npm run dist", 32 | "tags": "npx grunt tags", 33 | "watch-tags": "npx grunt watch" 34 | }, 35 | "repository": { 36 | "type": "git", 37 | "url": "https://github.com/tecfu/tty-table.git" 38 | }, 39 | "keywords": [ 40 | "table", 41 | "table in bash", 42 | "cli-table", 43 | "terminal table", 44 | "console table", 45 | "cli table", 46 | "console.table", 47 | "ascii table" 48 | ], 49 | "author": "Tecfu", 50 | "license": "MIT", 51 | "bugs": { 52 | "url": "https://github.com/tecfu/tty-table/issues" 53 | }, 54 | "homepage": "https://github.com/tecfu/tty-table", 55 | "dependencies": { 56 | "chalk": "^4.1.2", 57 | "csv": "^5.5.3", 58 | "kleur": "^4.1.5", 59 | "smartwrap": "^2.0.2", 60 | "strip-ansi": "^6.0.1", 61 | "wcwidth": "^1.0.1", 62 | "yargs": "^17.7.1" 63 | }, 64 | "devDependencies": { 65 | "@rollup/plugin-commonjs": "^11.0.2", 66 | "@rollup/plugin-node-resolve": "^7.1.1", 67 | "@rollup/plugin-replace": "^2.3.1", 68 | "axios-error": "^1.0.4", 69 | "babel-core": "^6.26.3", 70 | "babel-preset-babili": "0.1.4", 71 | "babel-preset-es2015": "^6.24.1", 72 | "babelify": "^8.0.0", 73 | "browserify": "^16.5.0", 74 | "chai": "^4.2.0", 75 | "codecov": "^3.6.5", 76 | "commander": "^4.1.1", 77 | "coveralls": "^3.0.9", 78 | "eslint": "^6.8.0", 79 | "eslint-config-standard": "^14.1.0", 80 | "eslint-plugin-import": "^2.20.1", 81 | "eslint-plugin-node": "^11.0.0", 82 | "eslint-plugin-promise": "^4.2.1", 83 | "eslint-plugin-standard": "^4.0.1", 84 | "glob": "^7.1.6", 85 | "grunt": "^1.1.0", 86 | "grunt-cli": "^1.3.2", 87 | "grunt-contrib-watch": "^1.1.0", 88 | "grunt-shell": "^3.0.1", 89 | "husky": "^4.2.5", 90 | "mocha": "^6.1.4", 91 | "mocha-lcov-reporter": "^1.3.0", 92 | "nyc": "^15.0.0", 93 | "rollup": "^1.31.1" 94 | }, 95 | "nyc": { 96 | "all": false, 97 | "include": [ 98 | "src/*.js", 99 | "adapters/*.js" 100 | ] 101 | }, 102 | "defaultTestColumns": 90 103 | } 104 | -------------------------------------------------------------------------------- /packaging.md: -------------------------------------------------------------------------------- 1 | # Building distributable packages 2 | 3 | ***Recommended: [FPM](https://github.com/jordansissel/fpm)*** 4 | 5 | - Arch Linux 6 | 7 | ``` 8 | fpm -s npm -t pacman tty-table 9 | ``` 10 | 11 | - Debian/Ubuntu 12 | 13 | ``` 14 | fpm -s npm -t deb tty-table 15 | ``` 16 | 17 | - Mac OS X 18 | 19 | ``` 20 | fpm -s npm -t osx tty-table 21 | ``` 22 | 23 | - Redhat 24 | 25 | ``` 26 | fpm -s npm -t rpm tty-table 27 | ``` 28 | 29 | - Solaris 30 | 31 | ``` 32 | fpm -s npm -t solaris tty-table 33 | ``` 34 | 35 | 36 | -------------------------------------------------------------------------------- /rollup.config.js: -------------------------------------------------------------------------------- 1 | import commonjs from '@rollup/plugin-commonjs'; 2 | import resolve from '@rollup/plugin-node-resolve'; 3 | import replace from '@rollup/plugin-replace' 4 | 5 | export default [ 6 | { 7 | input: './adapters/default-adapter.js', 8 | output: { 9 | name: 'ttyTable', 10 | file: 'dist/tty-table.esm.js', 11 | format: 'esm' 12 | }, 13 | plugins: [ 14 | resolve({ 15 | browser: true, 16 | preferBuiltins: false 17 | }), 18 | commonjs({ 19 | namedExports: { 20 | 'src/factory': ['Table'] 21 | } 22 | }), 23 | replace({ 24 | 'process': '{}', 25 | 'process.env': '{}', 26 | 'process.exit': '()' 27 | }), 28 | ] 29 | }, 30 | // solid border is corrupt, using browserify instead 31 | //{ 32 | // input: 'dist/tty-table.esm.js', 33 | // output: { 34 | // name: 'ttyTable', 35 | // file: 'dist/tty-table.iife.js', 36 | // format: 'iife' 37 | // }, 38 | // plugins: [ 39 | // resolve({ 40 | // browser: true, 41 | // preferBuiltins: false 42 | // }) 43 | // ] 44 | //}, 45 | // can't get rollup to add `require`, `exports`, using browserify instead 46 | //{ 47 | // input: 'dist/tty-table.esm.js', 48 | // output: { 49 | // name: 'ttyTable', 50 | // file: 'dist/tty-table.cjs.js', 51 | // format: 'cjs' 52 | // }, 53 | // plugins: [ 54 | // resolve({ 55 | // browser: true, 56 | // preferBuiltins: false 57 | // }) 58 | // ] 59 | //} 60 | ] 61 | -------------------------------------------------------------------------------- /src/defaults.js: -------------------------------------------------------------------------------- 1 | // @TODO split defaults into table and cell settings 2 | const defaults = { 3 | borderCharacters: { 4 | invisible: [ 5 | { v: " ", l: " ", j: " ", h: " ", r: " " }, 6 | { v: " ", l: " ", j: " ", h: " ", r: " " }, 7 | { v: " ", l: " ", j: " ", h: " ", r: " " } 8 | ], 9 | solid: [ 10 | { v: "│", l: "┌", j: "┬", h: "─", r: "┐" }, 11 | { v: "│", l: "├", j: "┼", h: "─", r: "┤" }, 12 | { v: "│", l: "└", j: "┴", h: "─", r: "┘" } 13 | ], 14 | dashed: [ 15 | { v: "|", l: "+", j: "+", h: "-", r: "+" }, 16 | { v: "|", l: "+", j: "+", h: "-", r: "+" }, 17 | { v: "|", l: "+", j: "+", h: "-", r: "+" } 18 | ], 19 | none: [ 20 | { v: "", l: "", j: "", h: "", r: "" }, 21 | { v: "", l: "", j: "", h: "", r: "" }, 22 | { v: "", l: "", j: "", h: "", r: "" } 23 | ] 24 | }, 25 | align: "center", 26 | borderColor: null, 27 | borderStyle: "solid", 28 | color: false, 29 | COLUMNS: 80, // if !process.stdout.columns assume redirecting to write stream 80 columns is VT200 standard 30 | compact: false, 31 | defaultErrorValue: "�", 32 | // defaultValue: "\u001b[31m?\u001b[39m", 33 | defaultValue: " ?", 34 | errorOnNull: false, 35 | FIXED_WIDTH: false, 36 | footerAlign: "center", 37 | footerColor: false, 38 | formatter: null, 39 | headerAlign: "center", 40 | headerColor: "yellow", 41 | isNull: false, // undocumented cell setting 42 | marginLeft: 2, 43 | marginTop: 1, 44 | paddingBottom: 0, 45 | paddingLeft: 1, 46 | paddingRight: 1, 47 | paddingTop: 0, 48 | showHeader: null, // undocumented 49 | truncate: false, 50 | width: "100%", 51 | GUTTER: 1, // undocumented 52 | columnSettings: [], 53 | // save so cell options can be merged into column options 54 | table: { 55 | body: "", 56 | columnInnerWidths: [], 57 | columnWidths: [], 58 | columns: [], 59 | footer: "", 60 | header: "", // post-rendered strings. 61 | height: 0, 62 | typeLocked: false // once a table type is selected can't switch 63 | } 64 | } 65 | 66 | // support deprecated border style values 67 | defaults.borderCharacters["0"] = defaults.borderCharacters.none 68 | defaults.borderCharacters["1"] = defaults.borderCharacters.solid 69 | defaults.borderCharacters["2"] = defaults.borderCharacters.dashed 70 | 71 | module.exports = defaults 72 | -------------------------------------------------------------------------------- /src/factory.d.ts: -------------------------------------------------------------------------------- 1 | import {Table, Formatter, Header, Options} from "./factory"; 2 | 3 | export = TtyTable; 4 | 5 | declare function TtyTable(headers: (string | Header | Formatter)[], body: string[][] | object[], footers: (string | Header | Formatter)[], config?: Options): Table; 6 | declare function TtyTable(header: (string | Header | Formatter)[], body: any[], config?: Options): Table; 7 | declare function TtyTable(body: any[], config?: Options): Table; 8 | 9 | declare namespace TtyTable { 10 | 11 | interface Formatter { 12 | (cellValue: any, columnIndex: number, rowIndex: number, rowData: any, inputData: any): string; 13 | } 14 | 15 | export interface Header { 16 | alias?: string; 17 | align?: string; 18 | color?: string; 19 | footerAlign?: string; 20 | footerColor?: string; 21 | formatter?: Formatter; 22 | headerAlign?: string; 23 | headerColor?: string; 24 | marginLeft?: number; 25 | marginTop?: number; 26 | paddingBottom?: number; 27 | paddingLeft?: number; 28 | paddingRight?: number; 29 | paddingTop?: number; 30 | value: string; 31 | width?: string | number; 32 | } 33 | 34 | export interface Options { 35 | borderStyle?: string; 36 | borderColor?: string; 37 | color?: string; 38 | compact?: boolean; 39 | defaultErrorValue?: string; 40 | defaultValue?: string; 41 | errorOnNull?: boolean; 42 | truncate?: string | boolean; 43 | width?: string; 44 | footerColor?: string; 45 | } 46 | 47 | export interface Config extends Options { 48 | borderCharacters: object; 49 | showHeader: boolean; 50 | tableId: string; 51 | table: TableState; 52 | } 53 | 54 | export interface Table extends Array { 55 | render(): string; 56 | } 57 | 58 | class TableState { 59 | body: string; 60 | columnInnerWidths: number[]; 61 | columnWidths: number[]; 62 | columns: number[]; 63 | footer: string; 64 | header: string; 65 | height: number; 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /src/factory.js: -------------------------------------------------------------------------------- 1 | const defaults = require("./defaults.js") 2 | const Render = require("./render.js") 3 | const Style = require("./style.js") 4 | let counter = 0 5 | 6 | const Factory = function (paramsArr) { 7 | const _configKey = Symbol.config 8 | let header = [] 9 | const body = [] 10 | let footer = [] 11 | let options = {} 12 | 13 | // handle different parameter scenarios 14 | switch (true) { 15 | // header, rows, footer, and options 16 | case (paramsArr.length === 4): 17 | header = paramsArr[0] 18 | body.push(...paramsArr[1]) // creates new array to store our rows (body) 19 | footer = paramsArr[2] 20 | options = paramsArr[3] 21 | break 22 | 23 | // header, rows, footer 24 | case (paramsArr.length === 3 && paramsArr[2] instanceof Array): 25 | header = paramsArr[0] 26 | body.push(...paramsArr[1]) // creates new array to store our rows 27 | footer = paramsArr[2] 28 | break 29 | 30 | // header, rows, options 31 | case (paramsArr.length === 3 && typeof paramsArr[2] === "object"): 32 | header = paramsArr[0] 33 | body.push(...paramsArr[1]) // creates new array to store our rows 34 | options = paramsArr[2] 35 | break 36 | 37 | // header, rows (rows, footer is not an option) 38 | case (paramsArr.length === 2 && paramsArr[1] instanceof Array): 39 | header = paramsArr[0] 40 | body.push(...paramsArr[1]) // creates new array to store our rows 41 | break 42 | 43 | // rows, options 44 | case (paramsArr.length === 2 && typeof paramsArr[1] === "object"): 45 | body.push(...paramsArr[0]) // creates new array to store our rows 46 | options = paramsArr[1] 47 | break 48 | 49 | // rows 50 | case (paramsArr.length === 1 && paramsArr[0] instanceof Array): 51 | body.push(...paramsArr[0]) 52 | break 53 | 54 | // adapter called: i.e. `require('tty-table')('automattic-cli')` 55 | case (paramsArr.length === 1 && typeof paramsArr[0] === "string"): 56 | return require(`../adapters/${paramsArr[0]}`) 57 | 58 | /* istanbul ignore next */ 59 | default: 60 | console.log("Error: Bad params. \nSee docs at github.com/tecfu/tty-table") 61 | process.exit() 62 | } 63 | 64 | // for "deep" copy, use JSON.parse 65 | const cloneddefaults = JSON.parse(JSON.stringify(defaults)) 66 | const config = Object.assign({}, cloneddefaults, options) 67 | 68 | // backfixes for shortened option names 69 | config.align = config.alignment || config.align 70 | config.headerAlign = config.headerAlignment || config.headerAlign 71 | 72 | // for truncate true is equivalent to empty string 73 | if (config.truncate === true) config.truncate = "" 74 | 75 | // if borderColor customized, color the border character set 76 | if (config.borderColor) { 77 | config.borderCharacters[config.borderStyle] 78 | = config.borderCharacters[config.borderStyle].map(function (obj) { 79 | Object.keys(obj).forEach(function (key) { 80 | obj[key] = Style.style(obj[key], config.borderColor) 81 | }) 82 | return obj 83 | }) 84 | } 85 | 86 | // save a copy for merging columnSettings into cell options 87 | config.columnSettings = header.slice(0) 88 | 89 | // header 90 | config.table.header = header 91 | 92 | // match header geometry with body array 93 | config.table.header = [config.table.header] 94 | 95 | // footer 96 | config.table.footer = footer 97 | 98 | // counting table enables fixed column widths for streams, 99 | // variable widths for multiple tables simulateously 100 | if (config.terminalAdapter !== true) { 101 | counter++ // fix columnwidths for streams 102 | } 103 | config.tableId = counter 104 | 105 | // create a new object with an Array prototype 106 | const tableObject = Object.create(body) 107 | 108 | // save configuration to new object 109 | tableObject[_configKey] = config 110 | 111 | /** 112 | * Add method to render table to a string 113 | * @returns {String} 114 | * @memberof Table 115 | * @example 116 | * ```js 117 | * let str = t1.render(); 118 | * console.log(str); //outputs table 119 | * ``` 120 | */ 121 | tableObject.render = function () { 122 | const output = Render.stringifyData(this[_configKey], this.slice(0)) // get string output 123 | tableObject.height = this[_configKey].height 124 | return output 125 | } 126 | 127 | return tableObject 128 | } 129 | 130 | const Table = function () { 131 | return new Factory(arguments) 132 | } 133 | 134 | Table.resetStyle = Style.resetStyle 135 | Table.style = Style.styleEachChar 136 | 137 | module.exports = Table 138 | -------------------------------------------------------------------------------- /src/format.js: -------------------------------------------------------------------------------- 1 | const stripAnsi = require("strip-ansi") 2 | const smartwrap = require("smartwrap") 3 | const wcwidth = require("wcwidth") 4 | 5 | const addPadding = (config, width) => { 6 | return width + config.paddingLeft + config.paddingRight 7 | } 8 | 9 | /** 10 | * Returns the widest cell give a collection of rows 11 | * 12 | * @param object columnOptions 13 | * @param array rows 14 | * @param integer columnIndex 15 | * @returns string 16 | */ 17 | const getMaxLength = (columnOptions, rows, columnIndex) => { 18 | let iterable 19 | 20 | // add header value, alias to calculate width when applicable 21 | if (columnOptions && (columnOptions.value || columnOptions.alias)) { 22 | // string we use from header 23 | let val = columnOptions.alias || columnOptions.value 24 | val = val.toString() 25 | // create a row with value in the current columnIndex 26 | const headerRow = Array(rows[0].length) 27 | headerRow[columnIndex] = val 28 | // add header row to new array we will check for max value width 29 | iterable = rows.slice() 30 | iterable.push(headerRow) 31 | } else { 32 | // no header value, just use rows to derive max width 33 | iterable = rows 34 | } 35 | 36 | const widest = iterable.reduce((prev, row) => { 37 | if (row[columnIndex]) { 38 | // check cell value is object or scalar 39 | const value = (row[columnIndex].value) ? row[columnIndex].value : row[columnIndex] 40 | const width = Math.max( 41 | ...stripAnsi(value.toString()).split(/[\n\r]/).map((s) => wcwidth(s)) 42 | ) 43 | return (width > prev) ? width : prev 44 | } 45 | return prev 46 | }, 0) 47 | 48 | return widest 49 | } 50 | 51 | /** 52 | * Get total width available to this table instance 53 | * 54 | * 55 | */ 56 | const getAvailableWidth = config => { 57 | if (process && ((process.stdout && process.stdout.columns) || (process.env && process.env.COLUMNS))) { 58 | // forked calls that do not inherit process.stdout must use process.env 59 | let viewport = (process.stdout && process.stdout.columns) ? process.stdout.columns : process.env.COLUMNS 60 | viewport = viewport - config.marginLeft 61 | 62 | // table width percentage of (viewport less margin) 63 | if (config.width !== "auto" && /^\d+%$/.test(config.width)) { 64 | return Math.min(1, (config.width.slice(0, -1) * 0.01)) * viewport 65 | } 66 | 67 | // table width fixed 68 | if (config.width !== "auto" && /^\d+$/.test(config.width)) { 69 | config.FIXED_WIDTH = true 70 | return config.width 71 | } 72 | 73 | // table width equals viewport less margin 74 | // @TODO deprecate and remove "auto", which was never documented so should not be 75 | // an issue 76 | return viewport 77 | } 78 | 79 | // browser 80 | /* istanbul ignore next */ 81 | if (typeof window !== "undefined") return window.innerWidth // eslint-disable-line 82 | 83 | // process.stdout.columns does not exist. assume redirecting to write stream 84 | // use 80 columns, which is VT200 standard 85 | return config.COLUMNS - config.marginLeft 86 | } 87 | 88 | module.exports.getStringLength = string => { 89 | // stripAnsi(string.replace(/[^\x00-\xff]/g,'XX')).length 90 | return wcwidth(stripAnsi(string)) 91 | } 92 | 93 | module.exports.wrapCellText = ( 94 | config, 95 | cellValue, 96 | columnIndex, 97 | cellOptions, 98 | rowType 99 | ) => { 100 | // ANSI chararacters that demarcate the start/end of a line 101 | const startAnsiRegexp = /^(\033\[[0-9;]*m)+/ 102 | const endAnsiRegexp = /(\033\[[0-9;]*m)+$/ 103 | 104 | // coerce cell value to string 105 | let string = cellValue.toString() 106 | 107 | // store matching ANSI characters 108 | const startMatches = string.match(startAnsiRegexp) || [""] 109 | 110 | // remove ANSI start-of-line chars 111 | string = string.replace(startAnsiRegexp, "") 112 | 113 | // store matching ANSI characters so can be later re-attached 114 | const endMatches = string.match(endAnsiRegexp) || [""] 115 | 116 | // remove ANSI end-of-line chars 117 | string = string.replace(endAnsiRegexp, "") 118 | 119 | let alignTgt 120 | 121 | switch (rowType) { 122 | case ("header"): 123 | alignTgt = "headerAlign" 124 | break 125 | case ("body"): 126 | alignTgt = "align" 127 | break 128 | default: 129 | alignTgt = "footerAlign" 130 | } 131 | 132 | // equalize padding for centered lines 133 | if (cellOptions[alignTgt] === "center") { 134 | cellOptions.paddingLeft = cellOptions.paddingRight = Math.max( 135 | cellOptions.paddingRight, 136 | cellOptions.paddingLeft, 137 | 0 138 | ) 139 | } 140 | 141 | const columnWidth = config.table.columnWidths[columnIndex] 142 | 143 | // innerWidth is the width available for text within the cell 144 | const innerWidth = columnWidth 145 | - cellOptions.paddingLeft 146 | - cellOptions.paddingRight 147 | - config.GUTTER 148 | 149 | if (typeof config.truncate === "string") { 150 | string = exports.truncate(string, cellOptions, innerWidth) 151 | } else { 152 | string = exports.wrap(string, cellOptions, innerWidth) 153 | } 154 | 155 | // format each line 156 | const cell = string.split("\n").map(line => { 157 | line = line.trim() 158 | 159 | const lineLength = exports.getStringLength(line) 160 | 161 | // alignment 162 | if (lineLength < columnWidth) { 163 | let emptySpace = columnWidth - lineLength 164 | 165 | switch (true) { 166 | case (cellOptions[alignTgt] === "center"): 167 | emptySpace-- 168 | const padBoth = Math.floor(emptySpace / 2) 169 | const padRemainder = emptySpace % 2 170 | line = Array(padBoth + 1).join(" ") 171 | + line 172 | + Array(padBoth + 1 + padRemainder).join(" ") 173 | break 174 | 175 | case (cellOptions[alignTgt] === "right"): 176 | line = Array(emptySpace - cellOptions.paddingRight).join(" ") 177 | + line 178 | + Array(cellOptions.paddingRight + 1).join(" ") 179 | break 180 | 181 | default: 182 | line = Array(cellOptions.paddingLeft + 1).join(" ") 183 | + line 184 | + Array(emptySpace - cellOptions.paddingLeft).join(" ") 185 | } 186 | } 187 | 188 | // put ANSI color codes BACK on the beginning and end of string 189 | return startMatches[0] + line + endMatches[0] 190 | }) 191 | 192 | return { cell, innerWidth } 193 | } 194 | 195 | module.exports.truncate = (string, cellOptions, maxWidth) => { 196 | const stringWidth = wcwidth(string) 197 | 198 | if (maxWidth < stringWidth) { 199 | // @TODO give user option to decide if they want to break words on wrapping 200 | string = smartwrap(string, { 201 | width: maxWidth - cellOptions.truncate.length, 202 | breakword: true 203 | }).split("\n")[0] 204 | string = string + cellOptions.truncate 205 | } 206 | 207 | return string 208 | } 209 | 210 | module.exports.wrap = (string, cellOptions, innerWidth) => { 211 | const outstring = smartwrap(string, { 212 | errorChar: cellOptions.defaultErrorValue, 213 | minWidth: 1, 214 | trim: true, 215 | width: innerWidth 216 | }) 217 | 218 | return outstring 219 | } 220 | 221 | module.exports.getColumnWidths = (config, rows) => { 222 | const availableWidth = getAvailableWidth(config) 223 | 224 | // iterate over the header if we have it, iterate over the first row 225 | // if we do not (to step through the correct number of columns) 226 | const iterable = (config.table.header[0] && config.table.header[0].length > 0) 227 | ? config.table.header[0] : rows[0] 228 | 229 | let widths = iterable.map((column, columnIndex) => { 230 | let result 231 | 232 | switch (true) { 233 | // column width is a percentage of table width specified in column header 234 | case (typeof column === "object" && (/^\d+%$/.test(column.width))): 235 | result = (column.width.slice(0, -1) * 0.01) * availableWidth 236 | result = addPadding(config, result) 237 | break 238 | 239 | // column width is specified in column header 240 | case (typeof column === "object" && (/^\d+$/.test(column.width))): 241 | result = column.width 242 | break 243 | 244 | // 'auto' sets column width to its longest value in the initial data set 245 | default: 246 | const columnOptions = (config.table.header[0][columnIndex]) 247 | ? config.table.header[0][columnIndex] : {} 248 | const measurableRows = (rows.length) ? rows : config.table.header[0] 249 | 250 | result = getMaxLength(columnOptions, measurableRows, columnIndex) 251 | 252 | // add spaces for padding if not centered 253 | // @TODO test with if not centered conditional 254 | result = addPadding(config, result) 255 | } 256 | 257 | // add space for gutter 258 | result = result + config.GUTTER 259 | return result 260 | }) 261 | 262 | // calculate sum of all column widths (including marginLeft) 263 | const totalWidth = widths.reduce((prev, current) => prev + current) 264 | 265 | // proportionately resize columns when necessary 266 | if (totalWidth > availableWidth || config.FIXED_WIDTH) { 267 | // proportion wont be exact fit, but this method keeps us safe 268 | const proportion = (availableWidth / totalWidth).toFixed(2) - 0.01 269 | const relativeWidths = widths.map(value => Math.max(2, Math.floor(proportion * value))) 270 | if (config.FIXED_WIDTH) return relativeWidths 271 | 272 | // when proportion < 0 column cant be resized and totalWidth must overflow viewport 273 | if (proportion > 0) { 274 | const totalRelativeWidths = relativeWidths.reduce((prev, current) => prev + current) 275 | widths = (totalRelativeWidths < totalWidth) ? relativeWidths : widths 276 | } 277 | } else { 278 | widths = widths.map(Math.floor) 279 | } 280 | 281 | return widths 282 | } 283 | -------------------------------------------------------------------------------- /src/main.js: -------------------------------------------------------------------------------- 1 | if (require.main === module) { 2 | // called directly in terminal 3 | /* istanbul ignore next */ 4 | require("./../adapters/terminal-adapter.js") 5 | } else { 6 | // called as a module 7 | module.exports = require("./../adapters/default-adapter.js") 8 | } 9 | -------------------------------------------------------------------------------- /src/render.js: -------------------------------------------------------------------------------- 1 | const Style = require("./style.js") 2 | const Format = require("./format.js") 3 | 4 | /** 5 | * Converts arrays of data into arrays of cell strings 6 | * @param {TtyTable.Config} config 7 | * @param {Array|object|TtyTable.Formatter>} inputData 8 | * @returns {Array} 9 | */ 10 | module.exports.stringifyData = (config, inputData) => { 11 | const sections = { 12 | header: [], 13 | body: [], 14 | footer: [] 15 | } 16 | const marginLeft = Array(config.marginLeft + 1).join(" ") 17 | const borderStyle = config.borderCharacters[config.borderStyle] 18 | const borders = [] 19 | 20 | // support backwards compatibility cli-table's multiple constructor geometries 21 | // @TODO deprecate and support only a single format 22 | const constructorType = exports.getConstructorGeometry(inputData[0] || [], config) 23 | const rows = exports.coerceConstructorGeometry(config, inputData, constructorType) 24 | 25 | // when streaming values to tty-table, we don't want column widths to change 26 | // from one rows set to the next, so we save the first set of widths and reuse 27 | if (!global.columnWidths) { 28 | global.columnWidths = {} 29 | } 30 | 31 | if (global.columnWidths[config.tableId]) { 32 | config.table.columnWidths = global.columnWidths[config.tableId] 33 | } else { 34 | global.columnWidths[config.tableId] = config.table.columnWidths = Format.getColumnWidths(config, rows) 35 | } 36 | 37 | // stringify header cells 38 | // hide header if no column names or if specified in config 39 | switch (true) { 40 | case (config.showHeader !== null && !config.showHeader): // explicitly false, hide 41 | sections.header = [] 42 | break 43 | 44 | case (config.showHeader === true): // explicitly true, show 45 | case (!!config.table.header[0].find(obj => obj.value || obj.alias)): // atleast one named column, show header 46 | sections.header = config.table.header.map(row => { 47 | return exports.buildRow(config, row, "header", null, rows, inputData) 48 | }) 49 | break 50 | 51 | default: // no named columns, hide 52 | sections.header = [] 53 | } 54 | 55 | // stringify body cells 56 | sections.body = rows.map((row, rowIndex) => { 57 | return exports.buildRow(config, row, "body", rowIndex, rows, inputData) 58 | }) 59 | 60 | // stringify footer cells 61 | sections.footer = (config.table.footer instanceof Array && config.table.footer.length > 0) ? [config.table.footer] : [] 62 | 63 | sections.footer = sections.footer.map(row => { 64 | return exports.buildRow(config, row, "footer", null, rows, inputData) 65 | }) 66 | 67 | // apply borders 68 | // 0=top, 1=middle, 2=bottom 69 | for (let a = 0; a < 3; a++) { 70 | // add left border 71 | borders[a] = borderStyle[a].l 72 | 73 | // add joined borders for each column 74 | config.table.columnWidths.forEach((columnWidth, index, arr) => { 75 | // Math.max because otherwise columns 1 wide wont have horizontal border 76 | borders[a] += Array(Math.max(columnWidth, 2)).join(borderStyle[a].h) 77 | borders[a] += ((index + 1 < arr.length) ? borderStyle[a].j : "") 78 | }) 79 | 80 | // add right border 81 | borders[a] += borderStyle[a].r 82 | 83 | // no trailing space on footer 84 | borders[a] = (a < 2) ? `${marginLeft + borders[a]}\n` : marginLeft + borders[a] 85 | } 86 | 87 | // top horizontal border 88 | let output = borders[0] 89 | 90 | // for each section (header,body,footer) 91 | Object.keys(sections).forEach((p, i) => { 92 | // for each row in the section 93 | while (sections[p].length) { 94 | const row = sections[p].shift() 95 | 96 | // if(row.length === 0) {break} 97 | 98 | row.forEach(line => { 99 | // vertical row borders 100 | output = `${output 101 | + marginLeft 102 | // left vertical border 103 | + borderStyle[1].v 104 | // join cells on vertical border 105 | + line.join(borderStyle[1].v) 106 | // right vertical border 107 | + borderStyle[1].v 108 | // end of line 109 | }\n` 110 | }) 111 | 112 | // bottom horizontal row border 113 | switch (true) { 114 | // skip if end of body and no footer 115 | case (sections[p].length === 0 116 | && i === 1 117 | && sections.footer.length === 0): 118 | break 119 | 120 | // skip if end of footer 121 | case (sections[p].length === 0 122 | && i === 2): 123 | break 124 | 125 | // skip if compact 126 | case (config.compact && p === "body" && !row.empty): 127 | break 128 | 129 | // skip if border style is "none" 130 | case (config.borderStyle === "none" && config.compact): 131 | break 132 | 133 | default: 134 | output += borders[1] 135 | } 136 | } 137 | }) 138 | 139 | // bottom horizontal border 140 | output += borders[2] 141 | 142 | const finalOutput = Array(config.marginTop + 1).join("\n") + output 143 | 144 | // record the height of the output 145 | config.height = finalOutput.split(/\r\n|\r|\n/).length 146 | 147 | return finalOutput 148 | } 149 | 150 | module.exports.buildRow = (config, row, rowType, rowIndex, rowData, inputData) => { 151 | let minRowHeight = 0 152 | 153 | // tag row as empty if empty, used for `compact` option 154 | if (row.length === 0 && config.compact) { 155 | row.empty = true 156 | return row 157 | } 158 | 159 | // force row to have correct number of columns 160 | const lengthDifference = config.table.columnWidths.length - row.length 161 | if (lengthDifference > 0) { 162 | // array (row) lacks elements, add until equal 163 | row = row.concat(Array.apply(null, new Array(lengthDifference)).map(() => null)) 164 | } else if (lengthDifference < 0) { 165 | // array (row) has too many elements, remove until equal 166 | row.length = config.table.columnWidths.length 167 | } 168 | 169 | // convert each element in row to cell format 170 | row = row.map((elem, elemIndex) => { 171 | const cell = exports.buildCell(config, elem, elemIndex, rowType, rowIndex, rowData, inputData) 172 | minRowHeight = (minRowHeight < cell.length) ? cell.length : minRowHeight 173 | return cell 174 | }) 175 | 176 | // apply top and bottom padding to row 177 | minRowHeight = (rowType === "header") ? minRowHeight 178 | : minRowHeight + (config.paddingBottom + config.paddingTop) 179 | 180 | const linedRow = Array.apply(null, { length: minRowHeight }) 181 | .map(Function.call, () => []) 182 | 183 | row.forEach(function (cell, a) { 184 | const whitespace = Array(config.table.columnWidths[a]).join(" ") 185 | 186 | if (rowType === "body") { 187 | // add whitespace for top padding 188 | for (let i = 0; i < config.paddingTop; i++) { 189 | cell.unshift(whitespace) 190 | } 191 | 192 | // add whitespace for bottom padding 193 | for (let i = 0; i < config.paddingBottom; i++) { 194 | cell.push(whitespace) 195 | } 196 | } 197 | 198 | // a `row` is divided by columns (horizontally) 199 | // a `linedRow` becomes the row divided instead into an array of vertical lines 200 | // each nested line divided by columns 201 | for (let i = 0; i < minRowHeight; i++) { 202 | linedRow[i].push((typeof cell[i] !== "undefined") 203 | ? cell[i] : whitespace) 204 | } 205 | }) 206 | 207 | return linedRow 208 | } 209 | 210 | module.exports.buildCell = (config, elem, columnIndex, rowType, rowIndex, rowData, inputData) => { 211 | let cellValue = null 212 | 213 | const cellOptions = Object.assign( 214 | { reset: false }, 215 | config, 216 | (rowType === "body") ? config.columnSettings[columnIndex] : {}, // ignore columnSettings for footer 217 | (typeof elem === "object") ? elem : {} 218 | ) 219 | 220 | if (rowType === "header") { 221 | config.table.columns.push(cellOptions) 222 | cellValue = cellOptions.alias || cellOptions.value || "" 223 | } else { 224 | // set cellValue 225 | switch (true) { 226 | case (typeof elem === "undefined" || elem === null): 227 | // replace undefined/null elem values with placeholder 228 | cellValue = (config.errorOnNull) ? config.defaultErrorValue : config.defaultValue 229 | // @TODO add to elem defaults 230 | cellOptions.isNull = true 231 | break 232 | 233 | case (typeof elem === "object" && elem !== null && typeof elem.value !== "undefined"): 234 | cellValue = elem.value 235 | break 236 | 237 | case (typeof elem === "function"): 238 | cellValue = elem.bind({ 239 | configure: function (object) { 240 | return Object.assign(cellOptions, object) 241 | }, 242 | style: Style.style, 243 | resetStyle: Style.resetStyle 244 | })( 245 | (!cellOptions.isNull) ? cellValue : "", 246 | columnIndex, 247 | rowIndex, 248 | rowData, 249 | inputData 250 | ) 251 | break 252 | 253 | default: 254 | // elem is assumed to be a scalar 255 | cellValue = elem 256 | } 257 | 258 | // run formatter 259 | if (typeof cellOptions.formatter === "function") { 260 | cellValue = cellOptions.formatter 261 | .bind({ 262 | configure: function (object) { 263 | return Object.assign(cellOptions, object) 264 | }, 265 | style: Style.style, 266 | resetStyle: Style.resetStyle 267 | })( 268 | (!cellOptions.isNull) ? cellValue : "", 269 | columnIndex, 270 | rowIndex, 271 | rowData, 272 | inputData 273 | ) 274 | } 275 | } 276 | 277 | // colorize cellValue 278 | // we don't want the formatter to pass a styled cell value with ANSI codes 279 | // (in case user wants to do math or string operations to cell value), so 280 | // we apply default styles to the cell after it runs through the formatter 281 | // and omit those default styles if the user applied `this.resetStyle` 282 | if (!cellOptions.reset) { 283 | cellValue = Style.colorizeCell(cellValue, cellOptions, rowType) 284 | } 285 | 286 | // textwrap cellValue 287 | const { cell, innerWidth } = Format.wrapCellText(cellOptions, cellValue, columnIndex, cellOptions, rowType) 288 | 289 | if (rowType === "header") { 290 | config.table.columnInnerWidths.push(innerWidth) 291 | } 292 | 293 | return cell 294 | } 295 | 296 | /** 297 | * Check for a backwards compatible (cli-table) constructor 298 | */ 299 | module.exports.getConstructorGeometry = (row, config) => { 300 | let type 301 | 302 | // rows passed as an object 303 | if (typeof row === "object" && !(row instanceof Array)) { 304 | const keys = Object.keys(row) 305 | 306 | if (config.adapter === "automattic") { 307 | // detected cross table 308 | const key = keys[0] 309 | 310 | if (row[key] instanceof Array) { 311 | type = "automattic-cross" 312 | } else { 313 | // detected vertical table 314 | type = "automattic-vertical" 315 | } 316 | } else { 317 | // detected horizontal table 318 | type = "o-horizontal" 319 | } 320 | } else { 321 | // rows passed as an array 322 | type = "a-horizontal" 323 | } 324 | 325 | return type 326 | } 327 | 328 | /** 329 | * Coerce backwards compatible constructor styles 330 | */ 331 | module.exports.coerceConstructorGeometry = (config, rows, constructorType) => { 332 | let output = [] 333 | switch (constructorType) { 334 | case ("automattic-cross"): 335 | // assign header styles to first column 336 | config.columnSettings[0] = config.columnSettings[0] || {} 337 | config.columnSettings[0].color = config.headerColor 338 | 339 | output = rows.map(obj => { 340 | const arr = [] 341 | const key = Object.keys(obj)[0] 342 | arr.push(key) 343 | return arr.concat(obj[key]) 344 | }) 345 | break 346 | 347 | case ("automattic-vertical"): 348 | // assign header styles to first column 349 | config.columnSettings[0] = config.columnSettings[0] || {} 350 | config.columnSettings[0].color = config.headerColor 351 | 352 | output = rows.map(function (value) { 353 | const key = Object.keys(value)[0] 354 | return [key, value[key]] 355 | }) 356 | break 357 | 358 | case ("o-horizontal"): 359 | // cell property names are specified in header columns 360 | if (config.table.header[0].length 361 | && config.table.header[0].every(obj => obj.value)) { 362 | output = rows.map(row => config.table.header[0] 363 | .map(obj => row[obj.value])) 364 | } // eslint-disable-line brace-style 365 | // no property names given, default to object property order 366 | else { 367 | output = rows.map(obj => Object.values(obj)) 368 | } 369 | break 370 | 371 | case ("a-horizontal"): 372 | output = rows 373 | break 374 | 375 | default: 376 | } 377 | 378 | return output 379 | } 380 | 381 | // @TODO For rotating horizontal data into a vertical table 382 | // assumes all rows are same length 383 | // module.exports.verticalizeMatrix = (config, inputArray) => { 384 | // 385 | // // grow to # arrays equal to number of columns in input array 386 | // let outputArray = [] 387 | // let headers = config.table.columns 388 | // 389 | // // create a row for each heading, and prepend the row 390 | // // with the heading name 391 | // headers.forEach(name => outputArray.push([name])) 392 | // 393 | // inputArray.forEach(row => { 394 | // row.forEach((element, index) => outputArray[index].push(element)) 395 | // }) 396 | // 397 | // return outputArray 398 | // } 399 | -------------------------------------------------------------------------------- /src/style.js: -------------------------------------------------------------------------------- 1 | const chalk = require("chalk") 2 | const kleur = require("kleur") 3 | 4 | // user kleur if we are in the browser 5 | const colorLib = (process && process.stdout) ? chalk : kleur 6 | 7 | const stripAnsi = require("strip-ansi") 8 | 9 | module.exports.style = (str, ...colors) => { 10 | const out = colors.reduce(function (input, color) { 11 | return colorLib[color](input) 12 | }, str) 13 | return out 14 | } 15 | 16 | module.exports.styleEachChar = (str, ...colors) => { 17 | // strip existing ansi chars so we dont loop them 18 | // @ TODO create a really clever workaround so that you can accrete styles 19 | const chars = [...stripAnsi(str)] 20 | 21 | // style each character 22 | const out = chars.reduce((prev, current) => { 23 | const coded = colors.reduce((input, color) => { 24 | return colorLib[color](input) 25 | }, current) 26 | return prev + coded 27 | }, "") 28 | 29 | return out 30 | } 31 | 32 | module.exports.resetStyle = function (str) { 33 | this.configure({ reset: true }) 34 | return stripAnsi(str) 35 | } 36 | 37 | module.exports.colorizeCell = (str, cellOptions, rowType) => { 38 | let color = false // false will keep terminal default 39 | 40 | switch (true) { 41 | case (rowType === "body"): 42 | color = cellOptions.color || color 43 | break 44 | 45 | case (rowType === "header"): 46 | color = cellOptions.headerColor || color 47 | break 48 | 49 | default: 50 | color = cellOptions.footerColor || color 51 | } 52 | 53 | if (color) { 54 | str = exports.style(str, color) 55 | } 56 | 57 | return str 58 | } 59 | -------------------------------------------------------------------------------- /test/example-utils.js: -------------------------------------------------------------------------------- 1 | const chalk = require("chalk"), 2 | stripAnsi = require("strip-ansi"), 3 | wcwidth = require("wcwidth"), 4 | util = require("util") 5 | 6 | const messageLog = { 7 | info: [], 8 | warnings: [], 9 | errors: [] 10 | } 11 | 12 | const library = { 13 | /** 14 | * one-liner example initialization, allows arguments/env for console width, env for ruler display 15 | * COLUMNS or first argument - console width 16 | * SHOW_MEASURE - display console width ruler 17 | * @param {number} [columns] set explicit viewport width (COLUMNS env var) 18 | * @param {boolean} [showMeasure] show ruler (SHOW_MEASURE env var) 19 | * @param {string} [truncate] to display in ruler (TRUNCATE env var) 20 | */ 21 | quickInit: function (columns, showMeasure, truncate) { 22 | let w = process.argv[2] && parseInt(process.argv[2]) || columns || process.env.COLUMNS 23 | 24 | if (w) { 25 | library.setViewportWidth(typeof w === "string" ? parseInt(w) : w) 26 | } 27 | 28 | if (showMeasure || process.env.SHOW_MEASURE) { 29 | library.showRuler(truncate || process.env.TRUNCATE) 30 | } 31 | }, 32 | 33 | /** 34 | * convenience function for test cases 35 | * @param {string} name 36 | * @param {number} columns 37 | * @param {object} [config] - tty-table config options 38 | * @ 39 | */ 40 | init: function (name, columns, config) { 41 | library.setViewportWidth(columns) 42 | library.showRuler(config && config.truncate) 43 | }, 44 | 45 | /** 46 | * explicitly override/set viewport width 47 | * @param {number} columns 48 | */ 49 | setViewportWidth: function (columns) { 50 | process.stdout.columns = columns 51 | }, 52 | 53 | /** 54 | * get current viewport width 55 | * @return {number} 56 | */ 57 | getViewportWidth: function () { 58 | return process.stdout.columns 59 | }, 60 | 61 | /** 62 | * display ruler with config info 63 | * @param {string|boolean} [truncate] truncate value for display 64 | */ 65 | showRuler: function (truncate) { 66 | if (process.stdout && typeof process.stdout.columns === "number") { 67 | let columns = process.stdout.columns, 68 | trunc = truncate === "" || truncate ? `/${truncate === "" || truncate === true ? "\"\"" : truncate} ` : " ", 69 | config = `${columns > 20 ? "width" : "w"}=${columns}${trunc}` 70 | console.log(`${`╠═ ${config}${new Array(columns).fill("═").join("")}`.substr(0, columns - 1)}╣`) 71 | } 72 | }, 73 | 74 | /** 75 | * parse an argument into a list of test values 76 | * @param {string} arg - section char or comma delimited list of values 77 | * @param {any} [defaultValue] - string to be parsed instead or any other value to be returned 78 | * @return {Array|any} 79 | */ 80 | parseList: function (arg, defaultValue) { 81 | // to include comma or section, quote with section like §comma,is,part,of,string§ 82 | arg = typeof arg === "undefined" ? defaultValue : arg 83 | if (typeof arg !== "string") { 84 | return typeof arg === "undefined" ? [] : arg 85 | } 86 | if (/^§.*§$/.test(arg)) { 87 | return arg.substr(1, arg.length - 2) 88 | } 89 | return arg.includes("§") ? arg.split("§") : arg.split(",") 90 | }, 91 | 92 | /** 93 | * parse an argument into a list of numeric test values 94 | * @param {string} arg - comma delimited list of values or ranges, e.g. 10,20,30-40,50-90:5 95 | * @param {any} [defaultValue] - string to be parsed instead or any other value to be returned 96 | * @return {Array|any} 97 | */ 98 | parseNumericList: function (arg, defaultValue) { 99 | let list = library.parseList(arg, defaultValue) 100 | return list.reduce((list, each) => { 101 | let range = /^(\d+)-(\d+)(?::(-?\d+))?$/.exec(each) 102 | if (range) { 103 | let start = parseInt(range[1]), 104 | end = parseInt(range[2]), 105 | step = range[3] ? parseInt(range[3]) : (start > end ? -1 : 1) 106 | for (let value = start; step > 0 ? value <= end : value >= end; value += step) { 107 | list.push(value) 108 | } 109 | } else { 110 | list.push(each) 111 | } 112 | return list 113 | }, []) 114 | }, 115 | 116 | /** 117 | * return the width of string in console columns correctly treating ANSI sequences, emoji and double wide chars 118 | * @param {string} str - a string 119 | * @return {number} 120 | */ 121 | getStringWidth: function (str) { 122 | return wcwidth(stripAnsi(str)) 123 | }, 124 | 125 | /** 126 | * return the width of the widest line in console columns correctly treating ANSI sequences, emoji and double wide chars 127 | * @param {Array|string} text - a string wth zero or more embedded linefeeds or array of strings 128 | * @return {number} 129 | */ 130 | getMaxLineWidth: function (text) { 131 | let lines = typeof text === "string" ? text.split("\n") : text, 132 | widest = Math.max(...lines.map(library.getStringWidth)) 133 | return widest 134 | }, 135 | 136 | /** 137 | * set or remove an property in a POJSO 138 | * @param {object} optionObject - object to be mutated 139 | * @param {string} optionName - property name 140 | * @param {any|undefined} [optionValue] - property value or undefined to delete option 141 | */ 142 | setOption: function (optionObject, optionName, optionValue) { 143 | if (optionValue === undefined) { 144 | delete optionObject[optionName] 145 | } else { 146 | optionObject[optionName] = optionValue 147 | } 148 | }, 149 | 150 | /** 151 | * log info message with printf-like arguments 152 | * @param {...any} args - printf style arguments, format string and values 153 | */ 154 | info: function (...args) { 155 | let str = util.format.apply(util, args) 156 | messageLog.info.push(str) 157 | console.log(chalk.cyan(str)) 158 | }, 159 | 160 | /** 161 | * log warning message with printf-like arguments 162 | * @param {...any} args - printf style arguments, format string and values 163 | */ 164 | warning: function (...args) { 165 | let str = util.format.apply(util, args) 166 | messageLog.warnings.push(str) 167 | console.log(chalk.magenta(str)) 168 | }, 169 | 170 | /** 171 | * log error message with printf-like arguments 172 | * @param {...any} args - printf style arguments, format string and values 173 | */ 174 | error: function (...args) { 175 | let str = util.format.apply(util, args) 176 | messageLog.errors.push(str) 177 | console.log(chalk.red(str)) 178 | }, 179 | 180 | /** 181 | * dump message log to console 182 | * @param {boolean} skipInfo - true to suppress dumping info level messages 183 | * @param {boolean} skipWarnings - true to suppress dumping warning level messages 184 | * @param {boolean} skipErrors - true to suppress dumping error level messages 185 | * @param {boolean} reset - true to clear message log 186 | */ 187 | dumpMessages: function (skipInfo, skipWarnings, skipErrors, reset) { 188 | if (!skipInfo) { 189 | for (let info of messageLog.info) { 190 | console.log(chalk.cyan(info)) 191 | } 192 | } 193 | if (!skipWarnings) { 194 | for (let warning of messageLog.warnings) { 195 | console.log(chalk.magenta(warning)) 196 | } 197 | } 198 | if (!skipErrors) { 199 | for (let error of messageLog.errors) { 200 | console.log(chalk.red(error)) 201 | } 202 | } 203 | if (reset) { 204 | for (let name of Object.keys(messageLog)) { 205 | messageLog[name].splice(0, messageLog[name].length) 206 | } 207 | } 208 | } 209 | } 210 | 211 | module.exports = library 212 | -------------------------------------------------------------------------------- /test/mocha.conf.js: -------------------------------------------------------------------------------- 1 | /* globals describe, it */ 2 | const chai = require("chai"), 3 | // eslint-disable-next-line no-unused-vars 4 | expect = chai.expect, 5 | // eslint-disable-next-line no-unused-vars 6 | assert = chai.assert, 7 | // eslint-disable-next-line no-unused-vars 8 | should = chai.should(), 9 | fs = require("fs"), 10 | path = require("path"), 11 | glob = require("glob"), 12 | grunt = require("grunt"), 13 | exec = require("child_process").exec, 14 | pkg = require("../package.json"), 15 | savedTestDir = path.join(__dirname, "/saved_test_outputs") 16 | 17 | chai.should() 18 | 19 | // Test all example scripts against their saved output 20 | let allScripts = glob.sync(path.join(__dirname, "../examples/*.js")), 21 | excludeScripts = glob.sync(path.join(__dirname, "../examples/example-*.js")), 22 | exampleScripts = allScripts.filter(p => !excludeScripts.includes(p)) 23 | 24 | exampleScripts.forEach(function(element) { 25 | 26 | let fileName = path.basename(element).replace(/\..*/, ""), 27 | savedTestPath = path.join(savedTestDir, `${fileName}-output.txt`) 28 | 29 | describe(element, function() { 30 | it(`Should match ${savedTestPath}`, function(deferred) { 31 | exec(`COLUMNS=${pkg.defaultTestColumns} node ${element} --color=always`, 32 | function (error, stdout /* , stderr */) { 33 | if (error !== null) { 34 | grunt.log.error(`Exec error: ${error}`) 35 | } 36 | var subname = fileName.replace(/\..*/, ""), 37 | filepath = path.join(savedTestDir, `${subname}-output.txt`), 38 | expected1 = fs.readFileSync(filepath, "utf-8") 39 | 40 | // example result should match saved output 41 | stdout.should.equal(expected1) 42 | deferred() 43 | } 44 | ) 45 | }) 46 | }) 47 | }) 48 | -------------------------------------------------------------------------------- /test/saved_test_outputs/auto-resize-single-column-widths-output.txt: -------------------------------------------------------------------------------- 1 | ╠═╣ 2 | ┌────────────────┐ 3 | │ this is a test │ 4 | └────────────────┘ 5 | ╠═ w╣ 6 | ┌─┬─┐ 7 | │t│t│ 8 | │h│h│ 9 | │i│i│ 10 | │s│s│ 11 | │i│i│ 12 | │s│s│ 13 | │a│a│ 14 | │t│t│ 15 | │e│e│ 16 | │s│s│ 17 | │t│t│ 18 | └─┴─┘ 19 | ╠═ w=7╣ 20 | ┌─┬─┬─┐ 21 | │t│t│t│ 22 | │h│h│h│ 23 | │i│i│i│ 24 | │s│s│s│ 25 | │i│i│i│ 26 | │s│s│s│ 27 | │a│a│a│ 28 | │t│t│t│ 29 | │e│e│e│ 30 | │s│s│s│ 31 | │t│t│t│ 32 | └─┴─┴─┘ 33 | ╠═ w=9 ═╣ 34 | ┌─┬─┬─┬─┐ 35 | │t│t│t│t│ 36 | │h│h│h│h│ 37 | │i│i│i│i│ 38 | │s│s│s│s│ 39 | │i│i│i│i│ 40 | │s│s│s│s│ 41 | │a│a│a│a│ 42 | │t│t│t│t│ 43 | │e│e│e│e│ 44 | │s│s│s│s│ 45 | │t│t│t│t│ 46 | └─┴─┴─┴─┘ 47 | ╠═ w=11 ══╣ 48 | ┌─┬─┬─┬─┬─┐ 49 | │t│t│t│t│t│ 50 | │h│h│h│h│h│ 51 | │i│i│i│i│i│ 52 | │s│s│s│s│s│ 53 | │i│i│i│i│i│ 54 | │s│s│s│s│s│ 55 | │a│a│a│a│a│ 56 | │t│t│t│t│t│ 57 | │e│e│e│e│e│ 58 | │s│s│s│s│s│ 59 | │t│t│t│t│t│ 60 | └─┴─┴─┴─┴─┘ 61 | ╠═ w=13 ════╣ 62 | ┌─┬─┬─┬─┬─┬─┐ 63 | │t│t│t│t│t│t│ 64 | │h│h│h│h│h│h│ 65 | │i│i│i│i│i│i│ 66 | │s│s│s│s│s│s│ 67 | │i│i│i│i│i│i│ 68 | │s│s│s│s│s│s│ 69 | │a│a│a│a│a│a│ 70 | │t│t│t│t│t│t│ 71 | │e│e│e│e│e│e│ 72 | │s│s│s│s│s│s│ 73 | │t│t│t│t│t│t│ 74 | └─┴─┴─┴─┴─┴─┘ 75 | ╠═ w=15 ══════╣ 76 | ┌─┬─┬─┬─┬─┬─┬─┐ 77 | │t│t│t│t│t│t│t│ 78 | │h│h│h│h│h│h│h│ 79 | │i│i│i│i│i│i│i│ 80 | │s│s│s│s│s│s│s│ 81 | │i│i│i│i│i│i│i│ 82 | │s│s│s│s│s│s│s│ 83 | │a│a│a│a│a│a│a│ 84 | │t│t│t│t│t│t│t│ 85 | │e│e│e│e│e│e│e│ 86 | │s│s│s│s│s│s│s│ 87 | │t│t│t│t│t│t│t│ 88 | └─┴─┴─┴─┴─┴─┴─┘ 89 | ╠═ w=17 ════════╣ 90 | ┌─┬─┬─┬─┬─┬─┬─┬─┐ 91 | │t│t│t│t│t│t│t│t│ 92 | │h│h│h│h│h│h│h│h│ 93 | │i│i│i│i│i│i│i│i│ 94 | │s│s│s│s│s│s│s│s│ 95 | │i│i│i│i│i│i│i│i│ 96 | │s│s│s│s│s│s│s│s│ 97 | │a│a│a│a│a│a│a│a│ 98 | │t│t│t│t│t│t│t│t│ 99 | │e│e│e│e│e│e│e│e│ 100 | │s│s│s│s│s│s│s│s│ 101 | │t│t│t│t│t│t│t│t│ 102 | └─┴─┴─┴─┴─┴─┴─┴─┘ 103 | ╠═ w=19 ══════════╣ 104 | ┌─┬─┬─┬─┬─┬─┬─┬─┬─┐ 105 | │t│t│t│t│t│t│t│t│t│ 106 | │h│h│h│h│h│h│h│h│h│ 107 | │i│i│i│i│i│i│i│i│i│ 108 | │s│s│s│s│s│s│s│s│s│ 109 | │i│i│i│i│i│i│i│i│i│ 110 | │s│s│s│s│s│s│s│s│s│ 111 | │a│a│a│a│a│a│a│a│a│ 112 | │t│t│t│t│t│t│t│t│t│ 113 | │e│e│e│e│e│e│e│e│e│ 114 | │s│s│s│s│s│s│s│s│s│ 115 | │t│t│t│t│t│t│t│t│t│ 116 | └─┴─┴─┴─┴─┴─┴─┴─┴─┘ 117 | ╠═ width=21 ════════╣ 118 | ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 119 | │t│t│t│t│t│t│t│t│t│t│ 120 | │h│h│h│h│h│h│h│h│h│h│ 121 | │i│i│i│i│i│i│i│i│i│i│ 122 | │s│s│s│s│s│s│s│s│s│s│ 123 | │i│i│i│i│i│i│i│i│i│i│ 124 | │s│s│s│s│s│s│s│s│s│s│ 125 | │a│a│a│a│a│a│a│a│a│a│ 126 | │t│t│t│t│t│t│t│t│t│t│ 127 | │e│e│e│e│e│e│e│e│e│e│ 128 | │s│s│s│s│s│s│s│s│s│s│ 129 | │t│t│t│t│t│t│t│t│t│t│ 130 | └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ 131 | ╠═ width=23 ══════════╣ 132 | ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 133 | │t│t│t│t│t│t│t│t│t│t│t│ 134 | │h│h│h│h│h│h│h│h│h│h│h│ 135 | │i│i│i│i│i│i│i│i│i│i│i│ 136 | │s│s│s│s│s│s│s│s│s│s│s│ 137 | │i│i│i│i│i│i│i│i│i│i│i│ 138 | │s│s│s│s│s│s│s│s│s│s│s│ 139 | │a│a│a│a│a│a│a│a│a│a│a│ 140 | │t│t│t│t│t│t│t│t│t│t│t│ 141 | │e│e│e│e│e│e│e│e│e│e│e│ 142 | │s│s│s│s│s│s│s│s│s│s│s│ 143 | │t│t│t│t│t│t│t│t│t│t│t│ 144 | └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ 145 | ╠═ width=25 ════════════╣ 146 | ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 147 | │t│t│t│t│t│t│t│t│t│t│t│t│ 148 | │h│h│h│h│h│h│h│h│h│h│h│h│ 149 | │i│i│i│i│i│i│i│i│i│i│i│i│ 150 | │s│s│s│s│s│s│s│s│s│s│s│s│ 151 | │i│i│i│i│i│i│i│i│i│i│i│i│ 152 | │s│s│s│s│s│s│s│s│s│s│s│s│ 153 | │a│a│a│a│a│a│a│a│a│a│a│a│ 154 | │t│t│t│t│t│t│t│t│t│t│t│t│ 155 | │e│e│e│e│e│e│e│e│e│e│e│e│ 156 | │s│s│s│s│s│s│s│s│s│s│s│s│ 157 | │t│t│t│t│t│t│t│t│t│t│t│t│ 158 | └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ 159 | ╠═ width=27 ══════════════╣ 160 | ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 161 | │t│t│t│t│t│t│t│t│t│t│t│t│t│ 162 | │h│h│h│h│h│h│h│h│h│h│h│h│h│ 163 | │i│i│i│i│i│i│i│i│i│i│i│i│i│ 164 | │s│s│s│s│s│s│s│s│s│s│s│s│s│ 165 | │i│i│i│i│i│i│i│i│i│i│i│i│i│ 166 | │s│s│s│s│s│s│s│s│s│s│s│s│s│ 167 | │a│a│a│a│a│a│a│a│a│a│a│a│a│ 168 | │t│t│t│t│t│t│t│t│t│t│t│t│t│ 169 | │e│e│e│e│e│e│e│e│e│e│e│e│e│ 170 | │s│s│s│s│s│s│s│s│s│s│s│s│s│ 171 | │t│t│t│t│t│t│t│t│t│t│t│t│t│ 172 | └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ 173 | ╠═ width=29 ════════════════╣ 174 | ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 175 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 176 | │h│h│h│h│h│h│h│h│h│h│h│h│h│h│ 177 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 178 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 179 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 180 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 181 | │a│a│a│a│a│a│a│a│a│a│a│a│a│a│ 182 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 183 | │e│e│e│e│e│e│e│e│e│e│e│e│e│e│ 184 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 185 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 186 | └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ 187 | ╠═ width=31 ══════════════════╣ 188 | ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 189 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 190 | │h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│ 191 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 192 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 193 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 194 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 195 | │a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│ 196 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 197 | │e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│ 198 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 199 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 200 | └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ 201 | ╠═ width=33 ════════════════════╣ 202 | ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 203 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 204 | │h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│ 205 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 206 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 207 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 208 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 209 | │a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│ 210 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 211 | │e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│ 212 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 213 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 214 | └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ 215 | ╠═ width=35 ══════════════════════╣ 216 | ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 217 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 218 | │h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│ 219 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 220 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 221 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 222 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 223 | │a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│ 224 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 225 | │e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│ 226 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 227 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 228 | └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ 229 | ╠═ width=37 ════════════════════════╣ 230 | ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 231 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 232 | │h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│ 233 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 234 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 235 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 236 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 237 | │a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│ 238 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 239 | │e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│ 240 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 241 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 242 | └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ 243 | ╠═ width=39 ══════════════════════════╣ 244 | ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 245 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 246 | │h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│ 247 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 248 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 249 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 250 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 251 | │a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│ 252 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 253 | │e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│ 254 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 255 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 256 | └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ 257 | ╠═ width=41 ════════════════════════════╣ 258 | ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 259 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 260 | │h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│ 261 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 262 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 263 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 264 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 265 | │a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│ 266 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 267 | │e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│ 268 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 269 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 270 | └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ 271 | ╠═ width=43 ══════════════════════════════╣ 272 | ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 273 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 274 | │h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│ 275 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 276 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 277 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 278 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 279 | │a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│ 280 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 281 | │e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│ 282 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 283 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 284 | └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ 285 | ╠═ width=45 ════════════════════════════════╣ 286 | ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 287 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 288 | │h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│ 289 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 290 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 291 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 292 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 293 | │a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│ 294 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 295 | │e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│ 296 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 297 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 298 | └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ 299 | ╠═ width=47 ══════════════════════════════════╣ 300 | ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 301 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 302 | │h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│ 303 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 304 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 305 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 306 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 307 | │a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│ 308 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 309 | │e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│ 310 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 311 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 312 | └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ 313 | ╠═ width=49 ════════════════════════════════════╣ 314 | ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 315 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 316 | │h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│ 317 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 318 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 319 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 320 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 321 | │a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│ 322 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 323 | │e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│ 324 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 325 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 326 | └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ 327 | ╠═ width=51 ══════════════════════════════════════╣ 328 | ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬─┐ 329 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 330 | │h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│h│ 331 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 332 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 333 | │i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│i│ 334 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 335 | │a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│a│ 336 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 337 | │e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│e│ 338 | │s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│s│ 339 | │t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│t│ 340 | └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴─┘ 341 | -------------------------------------------------------------------------------- /test/saved_test_outputs/auto-with-percentage-output.txt: -------------------------------------------------------------------------------- 1 | ╠═ width=100 ══════════════════════════════════════════════════════════════════════════════════════╣ 2 | 3 | ┌──────────────────────────┬─────────────────────────────────┐ 4 | │ Fixed header │ Auto header │ 5 | ├──────────────────────────┼─────────────────────────────────┤ 6 | │ aaa bbb ccc │ aaa bbb ccc ddd eee fff ggg hhh │ 7 | └──────────────────────────┴─────────────────────────────────┘ 8 | -------------------------------------------------------------------------------- /test/saved_test_outputs/auttomatic-cli-table-tests-output.txt: -------------------------------------------------------------------------------- 1 | 2 | ┌──────┬─────────────────────┬─────────────────────────┬─────────────────┐ 3 | │ Rel │ Change │ By │ When │ 4 | ├──────┼─────────────────────┼─────────────────────────┼─────────────────┤ 5 | │ v0.1 │ Testing something │ rauchg@gmail.com │ 7 minutes ago │ 6 | │ │ cool │ │ │ 7 | ├──────┼─────────────────────┼─────────────────────────┼─────────────────┤ 8 | │ v0.1 │ Testing something │ rauchg@gmail.com │ 8 minutes ago │ 9 | │ │ cool │ │ │ 10 | └──────┴─────────────────────┴─────────────────────────┴─────────────────┘ 11 | 12 | ┌──────┬─────────────────────┬─────────────────────────┬─────────────────┐ 13 | │ Rel │ Change │ By │ When │ 14 | ├──────┼─────────────────────┼─────────────────────────┼─────────────────┤ 15 | │ v0.1 │ Testing something │ rauchg@gmail.com │ 7 minutes ago │ 16 | │ │ cool │ │ │ 17 | │ v0.1 │ Testing something │ rauchg@gmail.com │ 8 minutes ago │ 18 | │ │ cool │ │ │ 19 | ├──────┼─────────────────────┼─────────────────────────┼─────────────────┤ 20 | │ v0.1 │ Testing something │ rauchg@gmail.com │ 8 minutes ago │ 21 | │ │ cool │ │ │ 22 | └──────┴─────────────────────┴─────────────────────────┴─────────────────┘ 23 | 24 | ┌──────┬────────────────────────┬──────────────────┬───────────────┐ 25 | │ v0.1 │ Testing something cool │ rauchg@gmail.com │ 7 minutes ago │ 26 | └──────┴────────────────────────┴──────────────────┴───────────────┘ 27 | 28 | ┌─────────────────────────┬────────────────────────────────────┐ 29 | │ Some Key │ Some Value │ 30 | ├─────────────────────────┼────────────────────────────────────┤ 31 | │ Another much longer key │ And its corresponding longer value │ 32 | └─────────────────────────┴────────────────────────────────────┘ 33 | 34 | ┌───────────┬───────────┬───────────┐ 35 | │ │ Header #1 │ Header #2 │ 36 | ├───────────┼───────────┼───────────┤ 37 | │ Header #3 │ Value 1 │ Value 2 │ 38 | ├───────────┼───────────┼───────────┤ 39 | │ Header #4 │ Value 3 │ Value 4 │ 40 | └───────────┴───────────┴───────────┘ 41 | 42 | ┌──────┬─────────────────────┬─────────────────────────┬─────────────────┐ 43 | │ Rel │ Change │ By │ When │ 44 | ├──────┼─────────────────────┼─────────────────────────┼─────────────────┤ 45 | │ v0.1 │ Testing something │ rauchg@gmail.com │ 7 minutes ago │ 46 | │ │ cool │ │ │ 47 | │ v0.1 │ Testing something │ rauchg@gmail.com │ 8 minutes ago │ 48 | │ │ cool │ │ │ 49 | ├──────┼─────────────────────┼─────────────────────────┼─────────────────┤ 50 | │ v0.1 │ Testing something │ rauchg@gmail.com │ 8 minutes ago │ 51 | │ │ cool │ │ │ 52 | └──────┴─────────────────────┴─────────────────────────┴─────────────────┘ 53 | -------------------------------------------------------------------------------- /test/saved_test_outputs/cli-test-output.txt: -------------------------------------------------------------------------------- 1 | [?25l 2 | 3 | ┌────────┬────────────┐ 4 | │ SYMBOL │ LAST (USD) │ 5 | ├────────┼────────────┤ 6 | │ AAPL │ 92.50 │ 7 | ├────────┼────────────┤ 8 | │ IBM │ 120.15 │ 9 | ├────────┼────────────┤ 10 | │ WMT │ 65.23 │ 11 | ├────────┼────────────┤ 12 | │ DIS │ 98.42 │ 13 | ├────────┼────────────┤ 14 | │ GE │ 30.85 │ 15 | └────────┴────────────┘ 16 | [?25h 17 | 18 | [?25l 19 | 20 | ┌────────┬────────────┐ 21 | │ SYMBOL │ LAST (USD) │ 22 | ├────────┼────────────┤ 23 | │ AAPL │ 92.5 │ 24 | ├────────┼────────────┤ 25 | │ IBM │ 120.15 │ 26 | ├────────┼────────────┤ 27 | │ WMT │ 65.23 │ 28 | ├────────┼────────────┤ 29 | │ DIS │ 98.42 │ 30 | ├────────┼────────────┤ 31 | │ GE │ 30.85 │ 32 | └────────┴────────────┘ 33 | [?25h 34 | 35 | [?25l 36 | 37 | ┌─┬─┬─┬─┬─┬─┬─┬─┬─┬─┬────────┐ 38 | │F│F│F│F│F│F│F│F│F│F│ F10 │ 39 | │0│1│2│3│4│5│6│7│8│9│ │ 40 | ├─┼─┼─┼─┼─┼─┼─┼─┼─┼─┼────────┤ 41 | │F│F│F│F│F│F│F│F│F│F│ F10121 │ 42 | │0│1│2│3│4│5│6│7│8│9│ 212121 │ 43 | │ │ │ │ │ │ │ │ │ │ │ 212121 │ 44 | │ │ │ │ │ │ │ │ │ │ │ 212212 │ 45 | │ │ │ │ │ │ │ │ │ │ │ 121212 │ 46 | │ │ │ │ │ │ │ │ │ │ │ 121212 │ 47 | │ │ │ │ │ │ │ │ │ │ │ 121212 │ 48 | │ │ │ │ │ │ │ │ │ │ │ 121212 │ 49 | │ │ │ │ │ │ │ │ │ │ │ 1 │ 50 | └─┴─┴─┴─┴─┴─┴─┴─┴─┴─┴────────┘ 51 | [?25h 52 | 53 | -------------------------------------------------------------------------------- /test/saved_test_outputs/constructor-types-output.txt: -------------------------------------------------------------------------------- 1 | 2 | ┌──────┐ 3 | │ item │ 4 | ├──────┤ 5 | │ 1 │ 6 | ├──────┤ 7 | │ 100. │ 8 | │ 00% │ 9 | └──────┘ 10 | ┌──────┐ 11 | │ item │ 12 | ├──────┤ 13 | │ 1 │ 14 | ├──────┤ 15 | │ 100. │ 16 | │ 00% │ 17 | └──────┘ 18 | ┌──────┐ 19 | │ item │ 20 | ├──────┤ 21 | │ 1 │ 22 | ├──────┤ 23 | │ 100. │ 24 | │ 00% │ 25 | └──────┘ 26 | ┌──────┐ 27 | │ item │ 28 | ├──────┤ 29 | │ 1 │ 30 | ├──────┤ 31 | │ 100. │ 32 | │ 00% │ 33 | └──────┘ 34 | ┌──────┐ 35 | │ item │ 36 | ├──────┤ 37 | │ 1 │ 38 | └──────┘ 39 | ┌──────┐ 40 | │ item │ 41 | ├──────┤ 42 | │ 1 │ 43 | └──────┘ 44 | ┌──────┐ 45 | │ item │ 46 | ├──────┤ 47 | │ 1 │ 48 | └──────┘ 49 | ┌──────┐ 50 | │ item │ 51 | ├──────┤ 52 | │ 1 │ 53 | └──────┘ 54 | ┌───┐ 55 | │ 1 │ 56 | └───┘ 57 | ┌───┐ 58 | │ 1 │ 59 | └───┘ 60 | ┌───┐ 61 | │ 1 │ 62 | └───┘ 63 | ┌───┐ 64 | │ 1 │ 65 | └───┘ 66 | -------------------------------------------------------------------------------- /test/saved_test_outputs/empty-rows-output.txt: -------------------------------------------------------------------------------- 1 | 2 | ┌──────┬──────┬──────┐ 3 | │ col1 │ col2 │ col3 │ 4 | ├──────┼──────┼──────┤ 5 | └──────┴──────┴──────┘ 6 | -------------------------------------------------------------------------------- /test/saved_test_outputs/hide-header-output.txt: -------------------------------------------------------------------------------- 1 | 2 | ┌────────────────────┬──────────┬──────────────────────────────┐ 3 | │ column 1 │ │ │ 4 | ├────────────────────┼──────────┼──────────────────────────────┤ 5 | │ e │ pluribus │ unum │ 6 | └────────────────────┴──────────┴──────────────────────────────┘ 7 | 8 | ┌────────────────────┬──────────┬──────────────────────────────┐ 9 | │ column 1 │ │ │ 10 | ├────────────────────┼──────────┼──────────────────────────────┤ 11 | │ e │ pluribus │ unum │ 12 | └────────────────────┴──────────┴──────────────────────────────┘ 13 | 14 | ┌────────────────────┬──────────┬──────────────────────────────┐ 15 | │ e │ pluribus │ unum │ 16 | └────────────────────┴──────────┴──────────────────────────────┘ 17 | 18 | ┌────────────────────┬──────────┬──────────────────────────────┐ 19 | │ e │ pluribus │ unum │ 20 | └────────────────────┴──────────┴──────────────────────────────┘ 21 | 22 | ┌────────────────────┬──────────┬──────────────────────────────┐ 23 | │ │ │ │ 24 | ├────────────────────┼──────────┼──────────────────────────────┤ 25 | │ e │ pluribus │ unum │ 26 | └────────────────────┴──────────┴──────────────────────────────┘ 27 | 28 | ┌────────────────────┬──────────┬──────────────────────────────┐ 29 | │ e │ pluribus │ unum │ 30 | └────────────────────┴──────────┴──────────────────────────────┘ 31 | -------------------------------------------------------------------------------- /test/saved_test_outputs/multiple-tables-output.txt: -------------------------------------------------------------------------------- 1 | 2 | ┌──────┬──────┬──────┐ 3 | │ col1 │ col2 │ col3 │ 4 | ├──────┼──────┼──────┤ 5 | │ 1 │ 2 │ 3 │ 6 | ├──────┼──────┼──────┤ 7 | │ 3 │ 34 │ 99 │ 8 | └──────┴──────┴──────┘ 9 | 10 | ┌───────┬───────┐ 11 | │ col1 │ col2 │ 12 | ├───────┼───────┤ 13 | │ 10001 │ 20002 │ 14 | └───────┴───────┘ 15 | -------------------------------------------------------------------------------- /test/saved_test_outputs/no-border-no-header-output.txt: -------------------------------------------------------------------------------- 1 | 2 |  xxxyyyzzz  aaaaa  bbbbbb  11111111  3 |  zzzxxxyyy  bbbbbb  cccccc  2222222  4 | 5 | -------------------------------------------------------------------------------- /test/saved_test_outputs/null-undefined-values-output.txt: -------------------------------------------------------------------------------- 1 | 2 | ┌─────────────────────┬───────┬─────────┐ 3 | │ item │ price │ organic │ 4 | ├─────────────────────┼───────┼─────────┤ 5 | │ │ ? │ ? │ 6 | ├─────────────────────┼───────┼─────────┤ 7 | │ special sauce │ 0.1 │ yes │ 8 | ├─────────────────────┼───────┼─────────┤ 9 | │ │ 1.5 │ no │ 10 | ├─────────────────────┼───────┼─────────┤ 11 | │ macaroni and cheese │ 3.75 │ ? │ 12 | ├─────────────────────┼───────┼─────────┤ 13 | │ 0 │ 0 │ 0 │ 14 | ├─────────────────────┼───────┼─────────┤ 15 | │ chocolate cake │ 4.65 │ no │ 16 | ├─────────────────────┼───────┼─────────┤ 17 | │ TOTAL │ 10.00 │ 16.67% │ 18 | └─────────────────────┴───────┴─────────┘ 19 | 20 | ┌─────────────────────┬───────┬─────────┐ 21 | │ item │ price │ organic │ 22 | ├─────────────────────┼───────┼─────────┤ 23 | │ │ ? │ ? │ 24 | ├─────────────────────┼───────┼─────────┤ 25 | │ special sauce │ 0.1 │ yes │ 26 | ├─────────────────────┼───────┼─────────┤ 27 | │ │ 1.5 │ no │ 28 | ├─────────────────────┼───────┼─────────┤ 29 | │ macaroni and cheese │ 3.75 │ ? │ 30 | ├─────────────────────┼───────┼─────────┤ 31 | │ 0 │ 0 │ 0 │ 32 | ├─────────────────────┼───────┼─────────┤ 33 | │ chocolate cake │ 4.65 │ no │ 34 | ├─────────────────────┼───────┼─────────┤ 35 | │ TOTAL │ 10.00 │ 16.67% │ 36 | └─────────────────────┴───────┴─────────┘ 37 | -------------------------------------------------------------------------------- /test/saved_test_outputs/placeholder-for-wide-chars-output.txt: -------------------------------------------------------------------------------- 1 | ╠═ width=25 ════════════╣ 2 | ┌─┬─────┬──────────────┐ 3 | │a│ aaa │ aaa bbb ccc │ 4 | │a│ bbb │ ddd eee fff │ 5 | │a│ ccc │ ggg hhh iii │ 6 | │b│ ddd │ jjj kkk lll │ 7 | │b│ eee │ mmm nnn ooo │ 8 | │b│ fff │ ppp qqq rrr │ 9 | │c│ ggg │ sss ttt │ 10 | │c│ hhh │ │ 11 | │c│ │ │ 12 | ├─┼─────┼──────────────┤ 13 | │�│特制 │ 2玉米饼, │ 14 | │�│的酱 │ 大米和豆类, │ 15 | │�│ 汁 │ 大米和豆类, │ 16 | │ │ │ 大米和豆类, │ 17 | │ │ │ 奶酪 │ 18 | └─┴─────┴──────────────┘ 19 | -------------------------------------------------------------------------------- /test/saved_test_outputs/styles-and-formatting-output.txt: -------------------------------------------------------------------------------- 1 | 2 | ┌────────────┬────────────────────────┬─────────┬─────────┬───────┐ 3 | │ item │ description │ peru │ usa │ vegan │ 4 | ├────────────┼────────────────────────┼─────────┼─────────┼───────┤ 5 | │ tallarin │ Peruvian version of │ $2.50 │ $15.50 │ no │ 6 | │ verde │ spaghetti with pesto. │ │ │ │ 7 | │ │ Includes a thin piece │ │ │ │ 8 | │ │ of fried chicken │ │ │ │ 9 | │ │ breast │ │ │ │ 10 | ├────────────┼────────────────────────┼─────────┼─────────┼───────┤ 11 | │ aji de │ Heavy aji cream sauce, │ $1.80 │ $14.50 │ no │ 12 | │ gallina │ rice, and chicken with │ │ │ │ 13 | │ │ a halved hard-boiled │ │ │ │ 14 | │ │ egg │ │ │ │ 15 | ├────────────┼────────────────────────┼─────────┼─────────┼───────┤ 16 | │ TOTAL │ │ $4.30 │ $30.00 │ 0.00% │ 17 | └────────────┴────────────────────────┴─────────┴─────────┴───────┘ 18 | 19 | ┌────────────┬────────────────────────┬─────────┬─────────┬───────┐ 20 | │ item │ description │ peru │ usa │ vegan │ 21 | ├────────────┼────────────────────────┼─────────┼─────────┼───────┤ 22 | │ tallarin │ Peruvian version of │ $2.50 │ $15.50 │ no │ 23 | │ verde │ spaghetti with pesto. │ │ │ │ 24 | │ │ Includes a thin piece │ │ │ │ 25 | │ │ of fried chicken │ │ │ │ 26 | │ │ breast │ │ │ │ 27 | ├────────────┼────────────────────────┼─────────┼─────────┼───────┤ 28 | │ aji de │ Heavy aji cream sauce, │ $1.80 │ $14.50 │ no │ 29 | │ gallina │ rice, and chicken with │ │ │ │ 30 | │ │ a halved hard-boiled │ │ │ │ 31 | │ │ egg │ │ │ │ 32 | ├────────────┼────────────────────────┼─────────┼─────────┼───────┤ 33 | │ TOTAL │ │ $4.30 │ $30.00 │ 0.00% │ 34 | └────────────┴────────────────────────┴─────────┴─────────┴───────┘ 35 | 36 | 37 |  price  item  38 |  1.99  banana 39 |  2.99  grapes  40 | 41 | 42 | ┌─────────────────────┬──────────────────────────┐ 43 | │ price │ item │ 44 | ├─────────────────────┼──────────────────────────┤ 45 | │ │ │ 46 | │ │ │ 47 | │ 1.99 │ banana │ 48 | │ │ │ 49 | │ │ │ 50 | ├─────────────────────┼──────────────────────────┤ 51 | │ │ │ 52 | │ │ │ 53 | │ 2.99 │ grapes │ 54 | │ │ │ 55 | │ │ │ 56 | └─────────────────────┴──────────────────────────┘ 57 | 58 | ┌─────────────────────┬──────────────────────────┐ 59 | │ price │ item │ 60 | ├─────────────────────┼──────────────────────────┤ 61 | │ │ │ 62 | │ │ │ 63 | │ 1.99 │ banana │ 64 | │ │ │ 65 | │ │ │ 66 | ├─────────────────────┼──────────────────────────┤ 67 | │ │ │ 68 | │ │ │ 69 | │ 2.99 │ grapes │ 70 | │ │ │ 71 | │ │ │ 72 | └─────────────────────┴──────────────────────────┘ 73 | 74 | ┌───────────────────┬────────────────────────────┐ 75 | │ │ │ 76 | │ │ │ 77 | │ 1.99 │ banana │ 78 | │ │ │ 79 | │ │ │ 80 | ├───────────────────┼────────────────────────────┤ 81 | │ │ │ 82 | │ │ │ 83 | │ 2.99 │ grapes │ 84 | │ │ │ 85 | │ │ │ 86 | └───────────────────┴────────────────────────────┘ 87 | -------------------------------------------------------------------------------- /test/saved_test_outputs/template-literal-cell-value-output.txt: -------------------------------------------------------------------------------- 1 | 2 | ┌─────────────────────────────────────────┐ 3 | │ The use of the cli-table is deprecated. │ 4 | │ Migrate over -> cli-table. Run: │ 5 | │ npm un cli-table -g │ 6 | │ npm i tty-table -g │ 7 | └─────────────────────────────────────────┘ 8 | -------------------------------------------------------------------------------- /test/saved_test_outputs/text-wrapping-output.txt: -------------------------------------------------------------------------------- 1 | 2 | Embedded ansi column width 3 | ╠═ width=50 ═════════════════════════════════════╣ 4 | 5 | ┌──────────────┬────────────────────┬─────────┐ 6 | │ some red │ some bold and dim │ plain │ 7 | │  words │ words │ test │ 8 | ├──────────────┼────────────────────┼─────────┤ 9 | │ some plain │ some bold and dim │ plain │ 10 | │ words │ words │ test │ 11 | └──────────────┴────────────────────┴─────────┘ 12 | 13 | 14 | Embedded ansi breaking 15 | ╠═ w=20 ═══════════╣ 16 | 17 | ┌────┬──────────┐ 18 | │ pl │ a few │ 19 | │ ai │  red │ 20 | │ n │ words in │ 21 | │ te │ here │ 22 | │ xt │ │ 23 | └────┴──────────┘ 24 | 25 | 26 | Handle line breaks 27 | ╠═ w=20 ═══════════╣ 28 | 29 | ┌────────┬───────┐ 30 | │ plain │ a few │ 31 | │ text │  red │ 32 | │ │ words │ 33 | │ │ in │ 34 | │ │ here │ 35 | └────────┴───────┘ 36 | 37 | 38 | Embedded emojis 39 | ╠═ width=25 ════════════╣ 40 | ┌─┬─────┬────────────┐ 41 | │a│ aaa │aaa bbb ccc │ 42 | │a│ bbb │ddd eee fff │ 43 | │a│ ccc │ggg hhh iii │ 44 | │b│😀😃 │jjj kkk lll │ 45 | │b│😄😁 │mmm nnn ooo │ 46 | │b│ eee │ppp qqq rrr │ 47 | │c│ fff │ sss ttt │ 48 | │c│ ggg │ │ 49 | │c│ hhh │ │ 50 | └─┴─────┴────────────┘ 51 | 52 | -------------------------------------------------------------------------------- /test/saved_test_outputs/truncated-lines-output.txt: -------------------------------------------------------------------------------- 1 | 2 | ┌──────────┬──────────┬──────────┐ 3 | │ item ... │ price │ 100% ... │ 4 | ├──────────┼──────────┼──────────┤ 5 | │ choco... │ 4.65 │ no │ 6 | └──────────┴──────────┴──────────┘ 7 | 8 | ┌──────────┬──────────┬──────────┐ 9 | │ item ... │ price │ 100% ... │ 10 | ├──────────┼──────────┼──────────┤ 11 | │ pound... │ 12345... │ no │ 12 | └──────────┴──────────┴──────────┘ 13 | 14 | ┌──────────┬──────────┬──────────┐ 15 | │ ite... │ price │ 100... │ 16 | ├──────────┼──────────┼──────────┤ 17 | │ pou... │ 123... │ no │ 18 | └──────────┴──────────┴──────────┘ 19 | 20 | ┌──────────┬──────────┬──────────┐ 21 | │ item │ price │ 100% │ 22 | │ name │ │ organic │ 23 | ├──────────┼──────────┼──────────┤ 24 | │ chocolat │ 4.65 │ no │ 25 | │ e cake │ │ │ 26 | └──────────┴──────────┴──────────┘ 27 | 28 | ┌──────────┬──────────┬──────────┐ 29 | │ item nam │ price │ 100% org │ 30 | ├──────────┼──────────┼──────────┤ 31 | │ chocolat │ 5.65 │ no │ 32 | └──────────┴──────────┴──────────┘ 33 | 34 | ┌─────┬────┬─────┐ 35 | │特...│0.1 │ ? │ 36 | ├─────┼────┼─────┤ 37 | │2... │9.8 │ │ 38 | ├─────┼────┼─────┤ 39 | │苹...│ 1 │ yes │ 40 | └─────┴────┴─────┘ 41 | 42 | ┌──────────┐ 43 | │ item ... │ 44 | ├──────────┤ 45 | │ choco... │ 46 | ├──────────┤ 47 | │ jewish │ 48 | │ coffee │ 49 | │ cake │ 50 | ├──────────┤ 51 | │ birth... │ 52 | ├──────────┤ 53 | │ - │ 54 | └──────────┘ 55 | -------------------------------------------------------------------------------- /test/saved_test_outputs/wide-characters-output.txt: -------------------------------------------------------------------------------- 1 | 2 | ┌────────────────────────────┬──────┬──────┐ 3 | │ 项目 │ 价格 │ 有机 │ 4 | ├────────────────────────────┼──────┼──────┤ 5 | │ 汉堡包 │ 2.5 │ ? │ 6 | ├────────────────────────────┼──────┼──────┤ 7 | │ 特制的酱汁 │ 0.1 │ ? │ 8 | ├────────────────────────────┼──────┼──────┤ 9 | │ 2玉米饼, 大米和豆类, 奶酪 │ 9.8 │ │ 10 | ├────────────────────────────┼──────┼──────┤ 11 | │ 苹果片 │ 1 │ yes │ 12 | ├────────────────────────────┼──────┼──────┤ 13 | │ ? │ 1.5 │ no │ 14 | ├────────────────────────────┼──────┼──────┤ 15 | │ 意大利粉, 火腿, 意大利干酪 │ 3.75 │ no │ 16 | └────────────────────────────┴──────┴──────┘ 17 | 18 | +--------+--------+--------+ 19 | | 项目 | 价格 | 有机 | 20 | +--------+--------+--------+ 21 | | abc | ? | ? | 22 | +--------+--------+--------+ 23 | | abć | ? | ? | 24 | +--------+--------+--------+ 25 | | ab한 | ? | ? | 26 | +--------+--------+--------+ 27 | --------------------------------------------------------------------------------