firest commit

This commit is contained in:
wwweww
2026-02-21 22:48:40 +08:00
commit 55e8053e07
1034 changed files with 99049 additions and 0 deletions
+21
View File
@@ -0,0 +1,21 @@
The MIT License (MIT)
Copyright (c) 2015-21 Lloyd Brookes <75pound@gmail.com>
Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
in the Software without restriction, including without limitation the rights
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
copies of the Software, and to permit persons to whom the Software is
furnished to do so, subject to the following conditions:
The above copyright notice and this permission notice shall be included in all
copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
SOFTWARE.
+84
View File
@@ -0,0 +1,84 @@
[![view on npm](https://badgen.net/npm/v/table-layout)](https://www.npmjs.org/package/table-layout)
[![npm module downloads](https://badgen.net/npm/dt/table-layout)](https://www.npmjs.org/package/table-layout)
[![Gihub repo dependents](https://badgen.net/github/dependents-repo/75lb/table-layout)](https://github.com/75lb/table-layout/network/dependents?dependent_type=REPOSITORY)
[![Gihub package dependents](https://badgen.net/github/dependents-pkg/75lb/table-layout)](https://github.com/75lb/table-layout/network/dependents?dependent_type=PACKAGE)
[![Build Status](https://travis-ci.org/75lb/table-layout.svg?branch=master)](https://travis-ci.org/75lb/table-layout)
[![Coverage Status](https://coveralls.io/repos/github/75lb/table-layout/badge.svg)](https://coveralls.io/github/75lb/table-layout)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](https://github.com/feross/standard)
# table-layout
Generates plain-text tables from JSON recordset input (array of objects). Useful for presenting text in column layout or data in table layout in text-based user interfaces. Also [available as a command-line tool](https://github.com/75lb/table-layout-cli).
## Synopsis
Where input looks like this:
```json
[
{
"number": 15134,
"title": "Coveralls has no source available ",
"login": "ndelangen",
"comments": 0
},
{
"number": 15133,
"title": "Fixing --preserve-symlinks. Enhancing node to exploit.",
"login": "phestermcs",
"comments": 0
},
{
"number": 15131,
"title": "Question - Confused about NPM's local installation philosophy",
"login": "the1mills",
"comments": 0
},
{
"number": 15130,
"title": "Question - global npm cache directory if user is root?",
"login": "ORESoftware",
"comments": 0
}
]
```
This code...
```js
const Table = require('table-layout')
const issues = require('./issues.json')
const table = new Table(issues, { maxWidth: 60 })
console.log(table.toString())
```
...produces this output:
```
15134 Coveralls has no source available ndelangen 0
15133 Fixing --preserve-symlinks. phestermcs 0
Enhancing node to exploit.
15131 Question - Confused about NPM's the1mills 0
local installation philosophy
15130 Question - global npm cache ORESoftware 0
directory if user is root?
15127 how to installa gulp fontfacegen aramgreat 0
on Windows 10
15097 Cannot install package from mastertinner 3
tarball out of package.json entry
generated by npm
15067 npm "SELF_SIGNED_CERT_IN_CHAIN" LegendsLyfe 3
error when installing discord.js
with .log
```
## API Reference
{{#module name="table-layout"}}
{{>body~}}
{{>member-index~}}
{{>members~}}
{{/module}}
* * *
&copy; 2015-21 Lloyd Brookes \<75pound@gmail.com\>. Documented by [jsdoc-to-markdown](https://github.com/jsdoc2md/jsdoc-to-markdown).
+154
View File
@@ -0,0 +1,154 @@
[![view on npm](https://badgen.net/npm/v/table-layout)](https://www.npmjs.org/package/table-layout)
[![npm module downloads](https://badgen.net/npm/dt/table-layout)](https://www.npmjs.org/package/table-layout)
[![Gihub repo dependents](https://badgen.net/github/dependents-repo/75lb/table-layout)](https://github.com/75lb/table-layout/network/dependents?dependent_type=REPOSITORY)
[![Gihub package dependents](https://badgen.net/github/dependents-pkg/75lb/table-layout)](https://github.com/75lb/table-layout/network/dependents?dependent_type=PACKAGE)
[![Build Status](https://travis-ci.org/75lb/table-layout.svg?branch=master)](https://travis-ci.org/75lb/table-layout)
[![Coverage Status](https://coveralls.io/repos/github/75lb/table-layout/badge.svg)](https://coveralls.io/github/75lb/table-layout)
[![js-standard-style](https://img.shields.io/badge/code%20style-standard-brightgreen.svg)](https://github.com/feross/standard)
# table-layout
Generates plain-text tables from JSON recordset input (array of objects). Useful for presenting text in column layout or data in table layout in text-based user interfaces. Also [available as a command-line tool](https://github.com/75lb/table-layout-cli).
## Synopsis
Where input looks like this:
```json
[
{
"number": 15134,
"title": "Coveralls has no source available ",
"login": "ndelangen",
"comments": 0
},
{
"number": 15133,
"title": "Fixing --preserve-symlinks. Enhancing node to exploit.",
"login": "phestermcs",
"comments": 0
},
{
"number": 15131,
"title": "Question - Confused about NPM's local installation philosophy",
"login": "the1mills",
"comments": 0
},
{
"number": 15130,
"title": "Question - global npm cache directory if user is root?",
"login": "ORESoftware",
"comments": 0
}
]
```
This code...
```js
const Table = require('table-layout')
const issues = require('./issues.json')
const table = new Table(issues, { maxWidth: 60 })
console.log(table.toString())
```
...produces this output:
```
15134 Coveralls has no source available ndelangen 0
15133 Fixing --preserve-symlinks. phestermcs 0
Enhancing node to exploit.
15131 Question - Confused about NPM's the1mills 0
local installation philosophy
15130 Question - global npm cache ORESoftware 0
directory if user is root?
15127 how to installa gulp fontfacegen aramgreat 0
on Windows 10
15097 Cannot install package from mastertinner 3
tarball out of package.json entry
generated by npm
15067 npm "SELF_SIGNED_CERT_IN_CHAIN" LegendsLyfe 3
error when installing discord.js
with .log
```
## API Reference
* [table-layout](#module_table-layout)
* [Table](#exp_module_table-layout--Table) ⏏
* [new Table(data, [options])](#new_module_table-layout--Table_new)
* [table.renderLines()](#module_table-layout--Table+renderLines) ⇒ <code>Array.&lt;string&gt;</code>
* [table.toString()](#module_table-layout--Table+toString) ⇒ <code>string</code>
* [Table~columnOption](#module_table-layout--Table..columnOption)
<a name="exp_module_table-layout--Table"></a>
### Table ⏏
Recordset data in (array of objects), text table out.
**Kind**: Exported class
<a name="new_module_table-layout--Table_new"></a>
#### new Table(data, [options])
**Params**
- data <code>Array.&lt;object&gt;</code> - input data
- [options] <code>object</code> - optional settings
- [.maxWidth] <code>number</code> - maximum width of layout
- [.noWrap] <code>boolean</code> - disable wrapping on all columns
- [.noTrim] <code>boolean</code> - disable line-trimming
- [.break] <code>boolean</code> - enable word-breaking on all columns
- [.columns] [<code>columnOption</code>](#module_table-layout--Table..columnOption) - array of column-specific options
- [.ignoreEmptyColumns] <code>boolean</code> - if set, empty columns or columns containing only whitespace are not rendered.
- [.padding] <code>object</code> - Padding values to set on each column. Per-column overrides can be set in the `options.columns` array.
- [.left] <code>string</code> - Defaults to a single space.
- [.right] <code>string</code> - Defaults to a single space.
**Example**
```js
> Table = require('table-layout')
> jsonData = [{
col1: 'Some text you wish to read in table layout',
col2: 'And some more text in column two. '
}]
> table = new Table(jsonData, { maxWidth: 30 })
> console.log(table.toString())
Some text you And some more
wish to read text in
in table column two.
layout
```
<a name="module_table-layout--Table+renderLines"></a>
#### table.renderLines() ⇒ <code>Array.&lt;string&gt;</code>
Identical to `.toString()` with the exception that the result will be an array of lines, rather than a single, multi-line string.
**Kind**: instance method of [<code>Table</code>](#exp_module_table-layout--Table)
<a name="module_table-layout--Table+toString"></a>
#### table.toString() ⇒ <code>string</code>
Returns the input data as a text table.
**Kind**: instance method of [<code>Table</code>](#exp_module_table-layout--Table)
<a name="module_table-layout--Table..columnOption"></a>
#### Table~columnOption
**Kind**: inner typedef of [<code>Table</code>](#exp_module_table-layout--Table)
**Properties**
| Name | Type | Description |
| --- | --- | --- |
| name | <code>string</code> | column name, must match a property name in the input |
| [width] | <code>number</code> | A specific column width. Supply either this or a min and/or max width. |
| [minWidth] | <code>number</code> | column min width |
| [maxWidth] | <code>number</code> | column max width |
| [nowrap] | <code>boolean</code> | disable wrapping for this column |
| [break] | <code>boolean</code> | enable word-breaking for this columns |
| [padding] | <code>object</code> | padding options |
| [padding.left] | <code>string</code> | a string to pad the left of each cell (default: `' '`) |
| [padding.right] | <code>string</code> | a string to pad the right of each cell (default: `' '`) |
* * *
&copy; 2015-21 Lloyd Brookes \<75pound@gmail.com\>. Documented by [jsdoc-to-markdown](https://github.com/jsdoc2md/jsdoc-to-markdown).
+197
View File
@@ -0,0 +1,197 @@
const os = require('os')
/**
* @module table-layout
*/
/**
* Recordset data in (array of objects), text table out.
* @alias module:table-layout
* @example
* > Table = require('table-layout')
* > jsonData = [{
* col1: 'Some text you wish to read in table layout',
* col2: 'And some more text in column two. '
* }]
* > table = new Table(jsonData, { maxWidth: 30 })
* > console.log(table.toString())
* Some text you And some more
* wish to read text in
* in table column two.
* layout
*/
class Table {
/**
* @param {object[]} - input data
* @param [options] {object} - optional settings
* @param [options.maxWidth] {number} - maximum width of layout
* @param [options.noWrap] {boolean} - disable wrapping on all columns
* @param [options.noTrim] {boolean} - disable line-trimming
* @param [options.break] {boolean} - enable word-breaking on all columns
* @param [options.columns] {module:table-layout~columnOption} - array of column-specific options
* @param [options.ignoreEmptyColumns] {boolean} - if set, empty columns or columns containing only whitespace are not rendered.
* @param [options.padding] {object} - Padding values to set on each column. Per-column overrides can be set in the `options.columns` array.
* @param [options.padding.left] {string} - Defaults to a single space.
* @param [options.padding.right] {string} - Defaults to a single space.
* @alias module:table-layout
*/
constructor (data, options) {
let ttyWidth = (process && (process.stdout.columns || process.stderr.columns)) || 0
/* Windows quirk workaround */
if (ttyWidth && os.platform() === 'win32') ttyWidth--
let defaults = {
padding: {
left: ' ',
right: ' '
},
maxWidth: ttyWidth || 80,
columns: []
}
const extend = require('deep-extend')
this.options = extend(defaults, options)
this.load(data)
}
load (data) {
const Rows = require('./lib/rows')
const Columns = require('./lib/columns')
let options = this.options
/* remove empty columns */
if (options.ignoreEmptyColumns) {
data = Rows.removeEmptyColumns(data)
}
this.columns = Columns.getColumns(data)
this.rows = new Rows(data, this.columns)
/* load default column properties from options */
this.columns.maxWidth = options.maxWidth
this.columns.list.forEach(column => {
if (options.padding) column.padding = options.padding
if (options.noWrap) column.noWrap = options.noWrap
if (options.break) {
column.break = options.break
column.contentWrappable = true
}
})
/* load column properties from options.columns */
options.columns.forEach(optionColumn => {
let column = this.columns.get(optionColumn.name)
if (column) {
if (optionColumn.padding) {
column.padding.left = optionColumn.padding.left
column.padding.right = optionColumn.padding.right
}
if (optionColumn.width) column.width = optionColumn.width
if (optionColumn.maxWidth) column.maxWidth = optionColumn.maxWidth
if (optionColumn.minWidth) column.minWidth = optionColumn.minWidth
if (optionColumn.noWrap) column.noWrap = optionColumn.noWrap
if (optionColumn.break) {
column.break = optionColumn.break
column.contentWrappable = true
}
}
})
this.columns.autoSize()
return this
}
getWrapped () {
const wrap = require('wordwrapjs')
this.columns.autoSize()
return this.rows.list.map(row => {
let line = []
row.forEach((cell, column) => {
if (column.noWrap) {
line.push(cell.value.split(/\r\n?|\n/))
} else {
line.push(wrap.lines(cell.value, {
width: column.wrappedContentWidth,
break: column.break,
noTrim: this.options.noTrim
}))
}
})
return line
})
}
getLines () {
var wrappedLines = this.getWrapped()
var lines = []
wrappedLines.forEach(wrapped => {
let mostLines = getLongestArray(wrapped)
for (let i = 0; i < mostLines; i++) {
let line = []
wrapped.forEach(cell => {
line.push(cell[i] || '')
})
lines.push(line)
}
})
return lines
}
/**
* Identical to `.toString()` with the exception that the result will be an array of lines, rather than a single, multi-line string.
* @returns {string[]}
*/
renderLines () {
var lines = this.getLines()
return lines.map(line => {
return line.reduce((prev, cell, index) => {
let column = this.columns.list[index]
return prev + padCell(cell, column.padding, column.generatedWidth)
}, '')
})
}
/**
* Returns the input data as a text table.
* @returns {string}
*/
toString () {
return this.renderLines().join(os.EOL) + os.EOL
}
}
/**
* Array of arrays in.. Returns the length of the longest one
* @returns {number}
* @private
*/
function getLongestArray (arrays) {
var lengths = arrays.map(array => array.length)
return Math.max.apply(null, lengths)
}
function padCell (cellValue, padding, width) {
const ansi = require('./lib/ansi')
var ansiLength = cellValue.length - ansi.remove(cellValue).length
cellValue = cellValue || ''
return (padding.left || '') +
cellValue.padEnd(width - padding.length() + ansiLength) + (padding.right || '')
}
/**
* @typedef module:table-layout~columnOption
* @property name {string} - column name, must match a property name in the input
* @property [width] {number} - A specific column width. Supply either this or a min and/or max width.
* @property [minWidth] {number} - column min width
* @property [maxWidth] {number} - column max width
* @property [nowrap] {boolean} - disable wrapping for this column
* @property [break] {boolean} - enable word-breaking for this columns
* @property [padding] {object} - padding options
* @property [padding.left] {string} - a string to pad the left of each cell (default: `' '`)
* @property [padding.right] {string} - a string to pad the right of each cell (default: `' '`)
*/
module.exports = Table
+15
View File
@@ -0,0 +1,15 @@
const ansiEscapeSequence = /\u001b.*?m/g
/**
* @module ansi
*/
exports.remove = remove
exports.has = has
function remove (input) {
return input.replace(ansiEscapeSequence, '')
}
function has (input) {
return ansiEscapeSequence.test(input)
}
+28
View File
@@ -0,0 +1,28 @@
const t = require('typical')
const _value = new WeakMap()
const _column = new WeakMap()
class Cell {
constructor (value, column) {
this.value = value
_column.set(this, column)
}
set value (val) {
_value.set(this, val)
}
get value () {
let cellValue = _value.get(this)
if (typeof cellValue === 'function') cellValue = cellValue.call(_column.get(this))
if (cellValue === undefined) {
cellValue = ''
} else {
cellValue = String(cellValue)
}
return cellValue
}
}
module.exports = Cell
+67
View File
@@ -0,0 +1,67 @@
const t = require('typical')
const Padding = require('./padding')
/**
* @module column
*/
const _padding = new WeakMap()
// setting any column property which is a factor of the width should trigger autoSize()
/**
* Represents a table column
*/
class Column {
constructor (column) {
/**
* @type {string}
*/
if (t.isDefined(column.name)) this.name = column.name
/**
* @type {number}
*/
if (t.isDefined(column.width)) this.width = column.width
if (t.isDefined(column.maxWidth)) this.maxWidth = column.maxWidth
if (t.isDefined(column.minWidth)) this.minWidth = column.minWidth
if (t.isDefined(column.noWrap)) this.noWrap = column.noWrap
if (t.isDefined(column.break)) this.break = column.break
if (t.isDefined(column.contentWrappable)) this.contentWrappable = column.contentWrappable
if (t.isDefined(column.contentWidth)) this.contentWidth = column.contentWidth
if (t.isDefined(column.minContentWidth)) this.minContentWidth = column.minContentWidth
this.padding = column.padding || { left: ' ', right: ' ' }
this.generatedWidth = null
}
set padding (padding) {
_padding.set(this, new Padding(padding))
}
get padding () {
return _padding.get(this)
}
/**
* the width of the content (excluding padding) after being wrapped
*/
get wrappedContentWidth () {
return Math.max(this.generatedWidth - this.padding.length(), 0)
}
isResizable () {
return !this.isFixed()
}
isFixed () {
return t.isDefined(this.width) || this.noWrap || !this.contentWrappable
}
generateWidth () {
this.generatedWidth = this.width || (this.contentWidth + this.padding.length())
}
generateMinWidth () {
this.minWidth = this.minContentWidth + this.padding.length()
}
}
module.exports = Column
+156
View File
@@ -0,0 +1,156 @@
const t = require('typical')
const arrayify = require('array-back')
const Column = require('./column')
const wrap = require('wordwrapjs')
const Cell = require('./cell')
const ansi = require('./ansi')
const _maxWidth = new WeakMap()
/**
* @module columns
*/
class Columns {
constructor (columns) {
this.list = []
arrayify(columns).forEach(this.add.bind(this))
}
/**
* sum of all generatedWidth fields
* @return {number}
*/
totalWidth () {
return this.list.length
? this.list.map(col => col.generatedWidth).reduce((a, b) => a + b)
: 0
}
totalFixedWidth () {
return this.getFixed()
.map(col => col.generatedWidth)
.reduce((a, b) => a + b, 0)
}
get (columnName) {
return this.list.find(column => column.name === columnName)
}
getResizable () {
return this.list.filter(column => column.isResizable())
}
getFixed () {
return this.list.filter(column => column.isFixed())
}
add (column) {
const col = column instanceof Column ? column : new Column(column)
this.list.push(col)
return col
}
set maxWidth (val) {
_maxWidth.set(this, val)
}
/**
* sets `generatedWidth` for each column
* @chainable
*/
autoSize () {
const maxWidth = _maxWidth.get(this)
/* size */
this.list.forEach(column => {
column.generateWidth()
column.generateMinWidth()
})
/* adjust if user set a min or maxWidth */
this.list.forEach(column => {
if (t.isDefined(column.maxWidth) && column.generatedWidth > column.maxWidth) {
column.generatedWidth = column.maxWidth
}
if (t.isDefined(column.minWidth) && column.generatedWidth < column.minWidth) {
column.generatedWidth = column.minWidth
}
})
const width = {
total: this.totalWidth(),
view: maxWidth,
diff: this.totalWidth() - maxWidth,
totalFixed: this.totalFixedWidth(),
totalResizable: Math.max(maxWidth - this.totalFixedWidth(), 0)
}
/* adjust if short of space */
if (width.diff > 0) {
/* share the available space between resizeable columns */
let resizableColumns = this.getResizable()
resizableColumns.forEach(column => {
column.generatedWidth = Math.floor(width.totalResizable / resizableColumns.length)
})
/* at this point, the generatedWidth should never end up bigger than the contentWidth */
const grownColumns = this.list.filter(column => column.generatedWidth > column.contentWidth)
const shrunkenColumns = this.list.filter(column => column.generatedWidth < column.contentWidth)
let salvagedSpace = 0
grownColumns.forEach(column => {
const currentGeneratedWidth = column.generatedWidth
column.generateWidth()
salvagedSpace += currentGeneratedWidth - column.generatedWidth
})
shrunkenColumns.forEach(column => {
column.generatedWidth += Math.floor(salvagedSpace / shrunkenColumns.length)
})
/* if, after autosizing, we still don't fit within maxWidth then give up */
}
return this
}
/**
* Factory method returning all distinct columns from input
* @param {object[]} - input recordset
* @return {module:columns}
*/
static getColumns (rows) {
var columns = new Columns()
arrayify(rows).forEach(row => {
for (let columnName in row) {
let column = columns.get(columnName)
if (!column) {
column = columns.add({ name: columnName, contentWidth: 0, minContentWidth: 0 })
}
let cell = new Cell(row[columnName], column)
let cellValue = cell.value
if (ansi.has(cellValue)) {
cellValue = ansi.remove(cellValue)
}
if (cellValue.length > column.contentWidth) column.contentWidth = cellValue.length
let longestWord = getLongestWord(cellValue)
if (longestWord > column.minContentWidth) {
column.minContentWidth = longestWord
}
if (!column.contentWrappable) column.contentWrappable = wrap.isWrappable(cellValue)
}
})
return columns
}
}
function getLongestWord (line) {
const words = wrap.getChunks(line)
return words.reduce((max, word) => {
return Math.max(word.length, max)
}, 0)
}
module.exports = Columns
+14
View File
@@ -0,0 +1,14 @@
class Padding {
constructor (padding) {
this.left = padding.left
this.right = padding.right
}
length () {
return this.left.length + this.right.length
}
}
/**
@module padding
*/
module.exports = Padding
+52
View File
@@ -0,0 +1,52 @@
const arrayify = require('array-back')
const Cell = require('./cell')
const t = require('typical')
/**
*
*/
class Rows {
constructor (rows, columns) {
this.list = []
this.load(rows, columns)
}
load (rows, columns) {
arrayify(rows).forEach(row => {
this.list.push(new Map(objectToIterable(row, columns)))
})
}
static removeEmptyColumns (data) {
const distinctColumnNames = data.reduce((columnNames, row) => {
Object.keys(row).forEach(key => {
if (columnNames.indexOf(key) === -1) columnNames.push(key)
})
return columnNames
}, [])
const emptyColumns = distinctColumnNames.filter(columnName => {
const hasValue = data.some(row => {
const value = row[columnName]
return (t.isDefined(value) && typeof value !== 'string') || (typeof value === 'string' && /\S+/.test(value))
})
return !hasValue
})
return data.map(row => {
emptyColumns.forEach(emptyCol => delete row[emptyCol])
return row
})
}
}
function objectToIterable (row, columns) {
return columns.list.map(column => {
return [ column, new Cell(row[column.name], column) ]
})
}
/**
* @module rows
*/
module.exports = Rows
+41
View File
@@ -0,0 +1,41 @@
{
"name": "table-layout",
"author": "Lloyd Brookes <75pound@gmail.com>",
"version": "1.0.2",
"description": "Stylable text tables, handling ansi colour. Useful for console output.",
"repository": "https://github.com/75lb/table-layout.git",
"license": "MIT",
"keywords": [
"wrap",
"terminal",
"data",
"columns",
"format",
"json",
"command line"
],
"engines": {
"node": ">=8.0.0"
},
"scripts": {
"test": "test-runner test/*.js",
"docs": "jsdoc2md -t README.hbs index.js -p list --member-index-format list > README.md; echo",
"cover": "nyc npm test && nyc report --reporter=text-lcov | coveralls"
},
"dependencies": {
"array-back": "^4.0.1",
"deep-extend": "~0.6.0",
"typical": "^5.2.0",
"wordwrapjs": "^4.0.0"
},
"devDependencies": {
"coveralls": "^3.1.0",
"jsdoc-to-markdown": "^7.0.0",
"nyc": "^15.1.0",
"test-runner": "^0.6.3"
},
"files": [
"index.js",
"lib/*.js"
]
}