├── .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 | [](http://badge.fury.io/js/tty-table) [](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 | 
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 | 
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 | 
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("[32m myText[39m") // "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 | 
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 | 
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: "[32m[37m[41m banana[49m[32m[39m",
150 | important: true,
151 | enabled: true
152 | },
153 | {
154 | price: 2.99,
155 | item: "[32m[37m[41m grapes[49m[32m[39m",
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: "[32m[37m[41m ?[49m[32m[39m",
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 | │[33m Fixed header [39m│[33m Auto header [39m│
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 | │[33m Rel [39m│[33m Change [39m│[33m By [39m│[33m When [39m│
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 | │[33m Rel [39m│[33m Change [39m│[33m By [39m│[33m When [39m│
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 | │[32m Some Key [39m│ Some Value │
30 | ├─────────────────────────┼────────────────────────────────────┤
31 | │[32m Another much longer key [39m│ And its corresponding longer value │
32 | └─────────────────────────┴────────────────────────────────────┘
33 |
34 | ┌───────────┬───────────┬───────────┐
35 | │ │[33m Header #1 [39m│[33m Header #2 [39m│
36 | ├───────────┼───────────┼───────────┤
37 | │[33m Header #3 [39m│ Value 1 │ Value 2 │
38 | ├───────────┼───────────┼───────────┤
39 | │[33m Header #4 [39m│ Value 3 │ Value 4 │
40 | └───────────┴───────────┴───────────┘
41 |
42 | ┌──────┬─────────────────────┬─────────────────────────┬─────────────────┐
43 | │[33m Rel [39m│[33m Change [39m│[33m By [39m│[33m When [39m│
44 | ├──────┼─────────────────────┼─────────────────────────┼─────────────────┤
45 | │[32m v0.1 [39m│[32m Testing something [39m│[32m rauchg@gmail.com [39m│[32m 7 minutes ago [39m│
46 | │ │[32m cool [39m│ │ │
47 | │[32m v0.1 [39m│[32m Testing something [39m│[32m rauchg@gmail.com [39m│[32m 8 minutes ago [39m│
48 | │ │[32m cool [39m│ │ │
49 | ├──────┼─────────────────────┼─────────────────────────┼─────────────────┤
50 | │[32m v0.1 [39m│[32m Testing something [39m│[32m rauchg@gmail.com [39m│[32m 8 minutes ago [39m│
51 | │ │[32m cool [39m│ │ │
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 | [32m┌[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┐[39m
3 | [32m│[39m[33m item [39m[32m│[39m
4 | [32m├[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┤[39m
5 | [32m│[39m 1 [32m│[39m
6 | [32m├[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┤[39m
7 | [32m│[39m 100. [32m│[39m
8 | [32m│[39m 00% [32m│[39m
9 | [32m└[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┘[39m
10 | [32m┌[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┐[39m
11 | [32m│[39m[33m item [39m[32m│[39m
12 | [32m├[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┤[39m
13 | [32m│[39m 1 [32m│[39m
14 | [32m├[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┤[39m
15 | [32m│[39m 100. [32m│[39m
16 | [32m│[39m 00% [32m│[39m
17 | [32m└[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┘[39m
18 | ┌──────┐
19 | │[33m item [39m│
20 | ├──────┤
21 | │ 1 │
22 | ├──────┤
23 | │ 100. │
24 | │ 00% │
25 | └──────┘
26 | ┌──────┐
27 | │[33m item [39m│
28 | ├──────┤
29 | │ 1 │
30 | ├──────┤
31 | │ 100. │
32 | │ 00% │
33 | └──────┘
34 | [32m┌[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┐[39m
35 | [32m│[39m[33m item [39m[32m│[39m
36 | [32m├[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┤[39m
37 | [32m│[39m 1 [32m│[39m
38 | [32m└[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┘[39m
39 | [32m┌[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┐[39m
40 | [32m│[39m[33m item [39m[32m│[39m
41 | [32m├[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┤[39m
42 | [32m│[39m 1 [32m│[39m
43 | [32m└[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┘[39m
44 | ┌──────┐
45 | │[33m item [39m│
46 | ├──────┤
47 | │ 1 │
48 | └──────┘
49 | ┌──────┐
50 | │[33m item [39m│
51 | ├──────┤
52 | │ 1 │
53 | └──────┘
54 | [32m┌[39m[32m─[39m[32m─[39m[32m─[39m[32m┐[39m
55 | [32m│[39m 1 [32m│[39m
56 | [32m└[39m[32m─[39m[32m─[39m[32m─[39m[32m┘[39m
57 | [32m┌[39m[32m─[39m[32m─[39m[32m─[39m[32m┐[39m
58 | [32m│[39m 1 [32m│[39m
59 | [32m└[39m[32m─[39m[32m─[39m[32m─[39m[32m┘[39m
60 | ┌───┐
61 | │ 1 │
62 | └───┘
63 | ┌───┐
64 | │ 1 │
65 | └───┘
66 |
--------------------------------------------------------------------------------
/test/saved_test_outputs/empty-rows-output.txt:
--------------------------------------------------------------------------------
1 |
2 | ┌──────┬──────┬──────┐
3 | │[33m col1 [39m│[33m col2 [39m│[33m col3 [39m│
4 | ├──────┼──────┼──────┤
5 | └──────┴──────┴──────┘
6 |
--------------------------------------------------------------------------------
/test/saved_test_outputs/hide-header-output.txt:
--------------------------------------------------------------------------------
1 |
2 | ┌────────────────────┬──────────┬──────────────────────────────┐
3 | │[33m column 1 [39m│ │ │
4 | ├────────────────────┼──────────┼──────────────────────────────┤
5 | │ e │ pluribus │ unum │
6 | └────────────────────┴──────────┴──────────────────────────────┘
7 |
8 | ┌────────────────────┬──────────┬──────────────────────────────┐
9 | │[33m column 1 [39m│ │ │
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 | │[33m col1 [39m│[33m col2 [39m│[33m col3 [39m│
4 | ├──────┼──────┼──────┤
5 | │ 1 │ 2 │ 3 │
6 | ├──────┼──────┼──────┤
7 | │ 3 │ 34 │ 99 │
8 | └──────┴──────┴──────┘
9 |
10 | ┌───────┬───────┐
11 | │[33m col1 [39m│[33m col2 [39m│
12 | ├───────┼───────┤
13 | │ 10001 │ 20002 │
14 | └───────┴───────┘
15 |
--------------------------------------------------------------------------------
/test/saved_test_outputs/no-border-no-header-output.txt:
--------------------------------------------------------------------------------
1 |
2 | [37m xxxyyyzzz [39m[37m aaaaa [39m[37m bbbbbb [39m[37m 11111111 [39m
3 | [37m zzzxxxyyy [39m[37m bbbbbb [39m[37m cccccc [39m[37m 2222222 [39m
4 |
5 |
--------------------------------------------------------------------------------
/test/saved_test_outputs/null-undefined-values-output.txt:
--------------------------------------------------------------------------------
1 |
2 | ┌─────────────────────┬───────┬─────────┐
3 | │[33m item [39m│[33m price [39m│[33m organic [39m│
4 | ├─────────────────────┼───────┼─────────┤
5 | │ │[32m[32m[37m[41m ? [49m[32m[39m[32m[39m│[32m[32m[37m[41m ? [49m[32m[39m[32m[39m│
6 | ├─────────────────────┼───────┼─────────┤
7 | │[32m[36m special sauce [39m[32m[39m│[32m 0.1 [39m│[32m yes [39m│
8 | ├─────────────────────┼───────┼─────────┤
9 | │ │[32m 1.5 [39m│[32m no [39m│
10 | ├─────────────────────┼───────┼─────────┤
11 | │[32m[36m macaroni and cheese [39m[32m[39m│[32m 3.75 [39m│[32m[32m[37m[41m ? [49m[32m[39m[32m[39m│
12 | ├─────────────────────┼───────┼─────────┤
13 | │[32m[36m 0 [39m[32m[39m│[32m 0 [39m│[32m 0 [39m│
14 | ├─────────────────────┼───────┼─────────┤
15 | │[32m[36m chocolate cake [39m[32m[39m│[32m 4.65 [39m│[32m no [39m│
16 | ├─────────────────────┼───────┼─────────┤
17 | │[33m TOTAL [39m│[33m 10.00 [39m│[33m 16.67% [39m│
18 | └─────────────────────┴───────┴─────────┘
19 |
20 | ┌─────────────────────┬───────┬─────────┐
21 | │[33m item [39m│[33m price [39m│[33m organic [39m│
22 | ├─────────────────────┼───────┼─────────┤
23 | │ │[32m[32m[37m[41m ? [49m[32m[39m[32m[39m│[32m[32m[37m[41m ? [49m[32m[39m[32m[39m│
24 | ├─────────────────────┼───────┼─────────┤
25 | │[32m[36m special sauce [39m[32m[39m│[32m 0.1 [39m│[32m yes [39m│
26 | ├─────────────────────┼───────┼─────────┤
27 | │ │[32m 1.5 [39m│[32m no [39m│
28 | ├─────────────────────┼───────┼─────────┤
29 | │[32m[36m macaroni and cheese [39m[32m[39m│[32m 3.75 [39m│[32m[32m[37m[41m ? [49m[32m[39m[32m[39m│
30 | ├─────────────────────┼───────┼─────────┤
31 | │[32m[36m 0 [39m[32m[39m│[32m 0 [39m│[32m 0 [39m│
32 | ├─────────────────────┼───────┼─────────┤
33 | │[32m[36m chocolate cake [39m[32m[39m│[32m 4.65 [39m│[32m no [39m│
34 | ├─────────────────────┼───────┼─────────┤
35 | │[33m TOTAL [39m│[33m 10.00 [39m│[33m 16.67% [39m│
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 | [32m┌[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┬[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┬[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┬[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┬[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┐[39m
3 | [32m│[39m[36m item [39m[32m│[39m[35m description [39m[32m│[39m[32m peru [39m[32m│[39m[32m usa [39m[32m│[39m[32m vegan [39m[32m│[39m
4 | [32m├[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┤[39m
5 | [32m│[39m[37m tallarin [39m[32m│[39m[33m Peruvian version of [39m[32m│[39m[31m[1m[31m $2.50 [39m[31m[22m[39m[32m│[39m[31m[1m[32m $15.50 [39m[31m[22m[39m[32m│[39m[37m[37m[41m no [49m[39m[37m[39m[32m│[39m
6 | [32m│[39m[37m verde [39m[32m│[39m[33m spaghetti with pesto. [39m[32m│[39m [32m│[39m [32m│[39m [32m│[39m
7 | [32m│[39m [32m│[39m[33m Includes a thin piece [39m[32m│[39m [32m│[39m [32m│[39m [32m│[39m
8 | [32m│[39m [32m│[39m[33m of fried chicken [39m[32m│[39m [32m│[39m [32m│[39m [32m│[39m
9 | [32m│[39m [32m│[39m[33m breast [39m[32m│[39m [32m│[39m [32m│[39m [32m│[39m
10 | [32m├[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┤[39m
11 | [32m│[39m[37m aji de [39m[32m│[39m[33m Heavy aji cream sauce, [39m[32m│[39m[31m[1m[31m $1.80 [39m[31m[22m[39m[32m│[39m[31m[1m[32m $14.50 [39m[31m[22m[39m[32m│[39m[37m[37m[41m no [49m[39m[37m[39m[32m│[39m
12 | [32m│[39m[37m gallina [39m[32m│[39m[33m rice, and chicken with [39m[32m│[39m [32m│[39m [32m│[39m [32m│[39m
13 | [32m│[39m [32m│[39m[33m a halved hard-boiled [39m[32m│[39m [32m│[39m [32m│[39m [32m│[39m
14 | [32m│[39m [32m│[39m[33m egg [39m[32m│[39m [32m│[39m [32m│[39m [32m│[39m
15 | [32m├[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┤[39m
16 | [32m│[39m TOTAL [32m│[39m [32m│[39m[3m $4.30 [23m[32m│[39m[3m $30.00 [23m[32m│[39m 0.00% [32m│[39m
17 | [32m└[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┴[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┴[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┴[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┴[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┘[39m
18 |
19 | [32m┌[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┬[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┬[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┬[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┬[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┐[39m
20 | [32m│[39m[36m item [39m[32m│[39m[35m description [39m[32m│[39m[32m peru [39m[32m│[39m[32m usa [39m[32m│[39m[32m vegan [39m[32m│[39m
21 | [32m├[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┤[39m
22 | [32m│[39m[37m tallarin [39m[32m│[39m[33m Peruvian version of [39m[32m│[39m[31m[1m[31m $2.50 [39m[31m[22m[39m[32m│[39m[31m[1m[32m $15.50 [39m[31m[22m[39m[32m│[39m[37m[37m[41m no [49m[39m[37m[39m[32m│[39m
23 | [32m│[39m[37m verde [39m[32m│[39m[33m spaghetti with pesto. [39m[32m│[39m [32m│[39m [32m│[39m [32m│[39m
24 | [32m│[39m [32m│[39m[33m Includes a thin piece [39m[32m│[39m [32m│[39m [32m│[39m [32m│[39m
25 | [32m│[39m [32m│[39m[33m of fried chicken [39m[32m│[39m [32m│[39m [32m│[39m [32m│[39m
26 | [32m│[39m [32m│[39m[33m breast [39m[32m│[39m [32m│[39m [32m│[39m [32m│[39m
27 | [32m├[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┤[39m
28 | [32m│[39m[37m aji de [39m[32m│[39m[33m Heavy aji cream sauce, [39m[32m│[39m[31m[1m[31m $1.80 [39m[31m[22m[39m[32m│[39m[31m[1m[32m $14.50 [39m[31m[22m[39m[32m│[39m[37m[37m[41m no [49m[39m[37m[39m[32m│[39m
29 | [32m│[39m[37m gallina [39m[32m│[39m[33m rice, and chicken with [39m[32m│[39m [32m│[39m [32m│[39m [32m│[39m
30 | [32m│[39m [32m│[39m[33m a halved hard-boiled [39m[32m│[39m [32m│[39m [32m│[39m [32m│[39m
31 | [32m│[39m [32m│[39m[33m egg [39m[32m│[39m [32m│[39m [32m│[39m [32m│[39m
32 | [32m├[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┼[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┤[39m
33 | [32m│[39m TOTAL [32m│[39m [32m│[39m[3m $4.30 [23m[32m│[39m[3m $30.00 [23m[32m│[39m 0.00% [32m│[39m
34 | [32m└[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┴[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┴[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┴[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┴[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m─[39m[32m┘[39m
35 |
36 |
37 | [33m price [39m[33m item [39m
38 | [31m 1.99 [39m banana
39 | [90m 2.99 [39m[4m[32m[37m[41m grapes [49m[32m[39m[24m
40 |
41 |
42 | ┌─────────────────────┬──────────────────────────┐
43 | │[33m price [39m│[33m item [39m│
44 | ├─────────────────────┼──────────────────────────┤
45 | │ │ │
46 | │ │ │
47 | │[31m 1.99 [39m│ banana │
48 | │ │ │
49 | │ │ │
50 | ├─────────────────────┼──────────────────────────┤
51 | │ │ │
52 | │ │ │
53 | │[90m 2.99 [39m│[4m[32m[37m[41m grapes [49m[32m[39m[24m│
54 | │ │ │
55 | │ │ │
56 | └─────────────────────┴──────────────────────────┘
57 |
58 | ┌─────────────────────┬──────────────────────────┐
59 | │[33m price [39m│[33m item [39m│
60 | ├─────────────────────┼──────────────────────────┤
61 | │ │ │
62 | │ │ │
63 | │[31m 1.99 [39m│ banana │
64 | │ │ │
65 | │ │ │
66 | ├─────────────────────┼──────────────────────────┤
67 | │ │ │
68 | │ │ │
69 | │[90m 2.99 [39m│[4m[32m[37m[41m grapes [49m[32m[39m[24m│
70 | │ │ │
71 | │ │ │
72 | └─────────────────────┴──────────────────────────┘
73 |
74 | ┌───────────────────┬────────────────────────────┐
75 | │ │ │
76 | │ │ │
77 | │[31m 1.99 [39m│ banana │
78 | │ │ │
79 | │ │ │
80 | ├───────────────────┼────────────────────────────┤
81 | │ │ │
82 | │ │ │
83 | │[90m 2.99 [39m│[4m[32m[37m[41m grapes [49m[32m[39m[24m│
84 | │ │ │
85 | │ │ │
86 | └───────────────────┴────────────────────────────┘
87 |
--------------------------------------------------------------------------------
/test/saved_test_outputs/template-literal-cell-value-output.txt:
--------------------------------------------------------------------------------
1 |
2 | [33m┌[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m┐[39m
3 | [33m│[39m[37m The use of the cli-table is deprecated.[39m [39m[33m│[39m
4 | [33m│[39m[37m [37mMigrate over -> cli-table. Run:[39m [39m[33m│[39m
5 | [33m│[39m[37m [37mnpm un cli-table -g[39m [39m[33m│[39m
6 | [33m│[39m[37m [37mnpm i tty-table -g [39m[33m│[39m
7 | [33m└[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m─[39m[33m┘[39m
8 |
--------------------------------------------------------------------------------
/test/saved_test_outputs/text-wrapping-output.txt:
--------------------------------------------------------------------------------
1 |
2 | [36mE[39m[36mm[39m[36mb[39m[36me[39m[36md[39m[36md[39m[36me[39m[36md[39m[36m [39m[36ma[39m[36mn[39m[36ms[39m[36mi[39m[36m [39m[36mc[39m[36mo[39m[36ml[39m[36mu[39m[36mm[39m[36mn[39m[36m [39m[36mw[39m[36mi[39m[36md[39m[36mt[39m[36mh[39m
3 | ╠═ width=50 ═════════════════════════════════════╣
4 |
5 | ┌──────────────┬────────────────────┬─────────┐
6 | │[31m s[39m[31mo[39m[31mm[39m[31me[39m[31m [39m[31mr[39m[31me[39m[31md[39m [39m│ some [1mb[22m[1mo[22m[1ml[22m[1md[22m and [2md[22m[2mi[22m[2mm[22m │ plain │
7 | │[31m [31m [39m[31mw[39m[31mo[39m[31mr[39m[31md[39m[31ms [39m│ words │ test │
8 | ├──────────────┼────────────────────┼─────────┤
9 | │ some plain │ some [1mb[22m[1mo[22m[1ml[22m[1md[22m and [2md[22m[2mi[22m[2mm[22m │ plain │
10 | │ words │ words │ test │
11 | └──────────────┴────────────────────┴─────────┘
12 |
13 |
14 | [36mE[39m[36mm[39m[36mb[39m[36me[39m[36md[39m[36md[39m[36me[39m[36md[39m[36m [39m[36ma[39m[36mn[39m[36ms[39m[36mi[39m[36m [39m[36mb[39m[36mr[39m[36me[39m[36ma[39m[36mk[39m[36mi[39m[36mn[39m[36mg[39m
15 | ╠═ w=20 ═══════════╣
16 |
17 | ┌────┬──────────┐
18 | │ pl │ a [31mf[39m[31me[39m[31mw[39m │
19 | │ ai │ [31m [39m[31mr[39m[31me[39m[31md[39m │
20 | │ n │[31m [39m[31mw[39m[31mo[39m[31mr[39m[31md[39m[31ms[39m[31m [39m[31mi[39m[31mn[39m │
21 | │ te │ here │
22 | │ xt │ │
23 | └────┴──────────┘
24 |
25 |
26 | [36mH[39m[36ma[39m[36mn[39m[36md[39m[36ml[39m[36me[39m[36m [39m[36ml[39m[36mi[39m[36mn[39m[36me[39m[36m [39m[36mb[39m[36mr[39m[36me[39m[36ma[39m[36mk[39m[36ms[39m
27 | ╠═ w=20 ═══════════╣
28 |
29 | ┌────────┬───────┐
30 | │ plain │ a [31mf[39m[31me[39m[31mw[39m │
31 | │ text │ [31m [39m[31mr[39m[31me[39m[31md[39m[31m[39m │
32 | │ │[31m[39m[31m [39m[31mw[39m[31mo[39m[31mr[39m[31md[39m[31ms[39m[31m [39m[31m[39m│
33 | │ │ [31m[39m[31mi[39m[31mn[39m │
34 | │ │ here │
35 | └────────┴───────┘
36 |
37 |
38 | [36mE[39m[36mm[39m[36mb[39m[36me[39m[36md[39m[36md[39m[36me[39m[36md[39m[36m [39m[36me[39m[36mm[39m[36mo[39m[36mj[39m[36mi[39m[36ms[39m
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 | │[33m item ... [39m│[33m price [39m│[33m 100% ... [39m│
4 | ├──────────┼──────────┼──────────┤
5 | │[32m[36m choco... [39m[32m[39m│[32m 4.65 [39m│[32m no [39m│
6 | └──────────┴──────────┴──────────┘
7 |
8 | ┌──────────┬──────────┬──────────┐
9 | │[33m item ... [39m│[33m price [39m│[33m 100% ... [39m│
10 | ├──────────┼──────────┼──────────┤
11 | │[32m[36m pound... [39m[32m[39m│[32m 12345... [39m│[32m no [39m│
12 | └──────────┴──────────┴──────────┘
13 |
14 | ┌──────────┬──────────┬──────────┐
15 | │[33m ite... [39m│[33m price [39m│[33m 100... [39m│
16 | ├──────────┼──────────┼──────────┤
17 | │[32m[36m pou... [39m[32m[39m│[32m 123... [39m│[32m no [39m│
18 | └──────────┴──────────┴──────────┘
19 |
20 | ┌──────────┬──────────┬──────────┐
21 | │[33m item [39m│[33m price [39m│[33m 100% [39m│
22 | │[33m name [39m│ │[33m organic [39m│
23 | ├──────────┼──────────┼──────────┤
24 | │[32m[36m chocolat [39m[32m[39m│[32m 4.65 [39m│[32m no [39m│
25 | │[32m[36m e cake [39m[32m[39m│ │ │
26 | └──────────┴──────────┴──────────┘
27 |
28 | ┌──────────┬──────────┬──────────┐
29 | │[33m item nam [39m│[33m price [39m│[33m 100% org [39m│
30 | ├──────────┼──────────┼──────────┤
31 | │[32m[36m chocolat [39m[32m[39m│[32m 5.65 [39m│[32m no [39m│
32 | └──────────┴──────────┴──────────┘
33 |
34 | ┌─────┬────┬─────┐
35 | │特...│0.1 │[32m[37m[41m ? [49m[32m[39m│
36 | ├─────┼────┼─────┤
37 | │2... │9.8 │ │
38 | ├─────┼────┼─────┤
39 | │苹...│ 1 │ yes │
40 | └─────┴────┴─────┘
41 |
42 | ┌──────────┐
43 | │[33m item ... [39m│
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 | │[33m 项目 [39m│[33m 价格 [39m│[33m 有机 [39m│
4 | ├────────────────────────────┼──────┼──────┤
5 | │[37m 汉堡包 [39m│[37m 2.5 [39m│[37m[32m[37m[41m ? [49m[32m[39m[37m[39m│
6 | ├────────────────────────────┼──────┼──────┤
7 | │[37m 特制的酱汁 [39m│[37m 0.1 [39m│[37m[32m[37m[41m ? [49m[32m[39m[37m[39m│
8 | ├────────────────────────────┼──────┼──────┤
9 | │[37m 2玉米饼, 大米和豆类, 奶酪 [39m│[37m 9.8 [39m│ │
10 | ├────────────────────────────┼──────┼──────┤
11 | │[37m 苹果片 [39m│[37m 1 [39m│[37m yes [39m│
12 | ├────────────────────────────┼──────┼──────┤
13 | │[37m[32m[37m[41m ? [49m[32m[39m[37m[39m│[37m 1.5 [39m│[37m no [39m│
14 | ├────────────────────────────┼──────┼──────┤
15 | │[37m 意大利粉, 火腿, 意大利干酪 [39m│[37m 3.75 [39m│[37m no [39m│
16 | └────────────────────────────┴──────┴──────┘
17 |
18 | +--------+--------+--------+
19 | |[33m 项目 [39m|[33m 价格 [39m|[33m 有机 [39m|
20 | +--------+--------+--------+
21 | |[37m abc [39m|[37m[32m[37m[41m ? [49m[32m[39m[37m[39m|[37m[32m[37m[41m ? [49m[32m[39m[37m[39m|
22 | +--------+--------+--------+
23 | |[37m abć [39m|[37m[32m[37m[41m ? [49m[32m[39m[37m[39m|[37m[32m[37m[41m ? [49m[32m[39m[37m[39m|
24 | +--------+--------+--------+
25 | |[37m ab한 [39m|[37m[32m[37m[41m ? [49m[32m[39m[37m[39m|[37m[32m[37m[41m ? [49m[32m[39m[37m[39m|
26 | +--------+--------+--------+
27 |
--------------------------------------------------------------------------------