diff options
| author | Bobby <[email protected]> | 2022-01-26 13:57:40 -0500 |
|---|---|---|
| committer | Bobby <[email protected]> | 2022-01-26 13:57:40 -0500 |
| commit | 901901f0cc4795305d368f588a2d18fe2a8e28b2 (patch) | |
| tree | dbaf30463ef987e9d6727b3afe783e3d53021b50 | |
| parent | 235a6523cc3239193bb908ae91da7426929d79ab (diff) | |
| download | izuku.js-901901f0cc4795305d368f588a2d18fe2a8e28b2.tar.xz izuku.js-901901f0cc4795305d368f588a2d18fe2a8e28b2.zip | |
feat: inbuilt table functionality
18 files changed, 1434 insertions, 86 deletions
diff --git a/package-lock.json b/package-lock.json index a81df1d..b600c03 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "license": "MIT", "dependencies": { "buffer": "^6.0.3", + "simple-wcswidth": "^1.0.1", "table": "^6.8.0" }, "devDependencies": { @@ -118,6 +119,18 @@ "node": ">=0.8.0" } }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/@commitlint/cli": { "version": "16.0.2", "resolved": "https://registry.npmjs.org/@commitlint/cli/-/cli-16.0.2.tgz", @@ -210,6 +223,43 @@ "node": ">=v12" } }, + "node_modules/@commitlint/format/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@commitlint/format/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@commitlint/format/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@commitlint/is-ignored": { "version": "16.0.0", "resolved": "https://registry.npmjs.org/@commitlint/is-ignored/-/is-ignored-16.0.0.tgz", @@ -274,6 +324,31 @@ "node": ">=v12" } }, + "node_modules/@commitlint/load/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@commitlint/load/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/@commitlint/load/node_modules/resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", @@ -283,6 +358,18 @@ "node": ">=8" } }, + "node_modules/@commitlint/load/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@commitlint/message": { "version": "16.0.0", "resolved": "https://registry.npmjs.org/@commitlint/message/-/message-16.0.0.tgz", @@ -396,6 +483,43 @@ "node": ">=v12" } }, + "node_modules/@commitlint/types/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/@commitlint/types/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/@commitlint/types/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/@cspotcode/source-map-consumer": { "version": "0.8.0", "resolved": "https://registry.npmjs.org/@cspotcode/source-map-consumer/-/source-map-consumer-0.8.0.tgz", @@ -1172,6 +1296,43 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/boxen/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/boxen/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/boxen/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/brace-expansion": { "version": "1.1.11", "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", @@ -1329,43 +1490,6 @@ "node": ">=4" } }, - "node_modules/chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "dependencies": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "engines": { - "node": ">=10" - }, - "funding": { - "url": "https://github.com/chalk/chalk?sponsor=1" - } - }, - "node_modules/chalk/node_modules/has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true, - "engines": { - "node": ">=8" - } - }, - "node_modules/chalk/node_modules/supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "dependencies": { - "has-flag": "^4.0.0" - }, - "engines": { - "node": ">=8" - } - }, "node_modules/check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -1932,6 +2056,22 @@ "node": "^12.22.0 || ^14.17.0 || >=16.0.0" } }, + "node_modules/eslint/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, "node_modules/eslint/node_modules/debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", @@ -1983,6 +2123,15 @@ "node": ">=10.13.0" } }, + "node_modules/eslint/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/eslint/node_modules/ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -2001,6 +2150,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/espree": { "version": "9.3.0", "resolved": "https://registry.npmjs.org/espree/-/espree-9.3.0.tgz", @@ -2997,6 +3158,43 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/log-symbols/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/log-symbols/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/lowercase-keys": { "version": "1.0.1", "resolved": "https://registry.npmjs.org/lowercase-keys/-/lowercase-keys-1.0.1.tgz", @@ -3416,6 +3614,18 @@ "url": "https://opencollective.com/nodemon" } }, + "node_modules/nodemon/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, "node_modules/nopt": { "version": "1.0.10", "resolved": "https://registry.npmjs.org/nopt/-/nopt-1.0.10.tgz", @@ -4255,6 +4465,11 @@ "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", "dev": true }, + "node_modules/simple-wcswidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.0.1.tgz", + "integrity": "sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg==" + }, "node_modules/slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -4384,18 +4599,6 @@ "node": ">=0.10.0" } }, - "node_modules/supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "dependencies": { - "has-flag": "^3.0.0" - }, - "engines": { - "node": ">=4" - } - }, "node_modules/supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -4687,6 +4890,31 @@ "url": "https://github.com/yeoman/update-notifier?sponsor=1" } }, + "node_modules/update-notifier/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/update-notifier/node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/update-notifier/node_modules/semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -4702,6 +4930,18 @@ "node": ">=10" } }, + "node_modules/update-notifier/node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/uri-js": { "version": "4.4.1", "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", @@ -5015,6 +5255,15 @@ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } } } }, @@ -5086,6 +5335,33 @@ "requires": { "@commitlint/types": "^16.0.0", "chalk": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "@commitlint/is-ignored": { @@ -5139,11 +5415,36 @@ "typescript": "^4.4.3" }, "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "resolve-from": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, @@ -5233,6 +5534,33 @@ "dev": true, "requires": { "chalk": "^4.0.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "@cspotcode/source-map-consumer": { @@ -5771,6 +6099,33 @@ "type-fest": "^0.20.2", "widest-line": "^3.1.0", "wrap-ansi": "^7.0.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "brace-expansion": { @@ -5884,33 +6239,6 @@ "type-detect": "^4.0.5" } }, - "chalk": { - "version": "4.1.2", - "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", - "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", - "dev": true, - "requires": { - "ansi-styles": "^4.1.0", - "supports-color": "^7.1.0" - }, - "dependencies": { - "has-flag": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", - "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", - "dev": true - }, - "supports-color": { - "version": "7.2.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", - "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", - "dev": true, - "requires": { - "has-flag": "^4.0.0" - } - } - } - }, "check-error": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz", @@ -6287,6 +6615,16 @@ "v8-compile-cache": "^2.0.3" }, "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, "debug": { "version": "4.3.3", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.3.tgz", @@ -6321,6 +6659,12 @@ "is-glob": "^4.0.3" } }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "ms": { "version": "2.1.2", "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", @@ -6332,6 +6676,15 @@ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, @@ -7115,6 +7468,33 @@ "requires": { "chalk": "^4.1.0", "is-unicode-supported": "^0.1.0" + }, + "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } + } } }, "lowercase-keys": { @@ -7417,6 +7797,17 @@ "touch": "^3.1.0", "undefsafe": "^2.0.5", "update-notifier": "^5.1.0" + }, + "dependencies": { + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } } }, "nopt": { @@ -8015,6 +8406,11 @@ "integrity": "sha512-sDl4qMFpijcGw22U5w63KmD3cZJfBuFlVNbVMKje2keoKML7X2UzWbc4XrmEbDwg0NXJc3yv4/ox7b+JWb57kQ==", "dev": true }, + "simple-wcswidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-wcswidth/-/simple-wcswidth-1.0.1.tgz", + "integrity": "sha512-xMO/8eNREtaROt7tJvWJqHBDTMFN4eiQ5I4JRMuilwfnFcV5W9u7RUkueNkdw0jPqGMX36iCywelS5yilTuOxg==" + }, "slash": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", @@ -8120,15 +8516,6 @@ "integrity": "sha1-PFMZQukIwml8DsNEhYwobHygpgo=", "dev": true }, - "supports-color": { - "version": "5.5.0", - "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", - "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", - "dev": true, - "requires": { - "has-flag": "^3.0.0" - } - }, "supports-preserve-symlinks-flag": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", @@ -8336,6 +8723,22 @@ "xdg-basedir": "^4.0.0" }, "dependencies": { + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, "semver": { "version": "7.3.5", "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", @@ -8344,6 +8747,15 @@ "requires": { "lru-cache": "^6.0.0" } + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "requires": { + "has-flag": "^4.0.0" + } } } }, diff --git a/package.json b/package.json index 6411e93..499b166 100644 --- a/package.json +++ b/package.json @@ -61,6 +61,7 @@ }, "dependencies": { "buffer": "^6.0.3", + "simple-wcswidth": "^1.0.1", "table": "^6.8.0" } } diff --git a/src/helpers/tableBuilder/index.ts b/src/helpers/tableBuilder/index.ts new file mode 100644 index 0000000..bf29228 --- /dev/null +++ b/src/helpers/tableBuilder/index.ts @@ -0,0 +1,9 @@ +import Table from './src/console-table-printer'; +import { + printSimpleTable as printTable, + renderSimpleTable as renderTable +} from './src/internalTable/internal-table-printer'; + +import { COLOR, ALIGNMENT } from './src/models/external-table'; + +export { Table, printTable, renderTable, COLOR, ALIGNMENT }; diff --git a/src/helpers/tableBuilder/src/console-table-printer.ts b/src/helpers/tableBuilder/src/console-table-printer.ts new file mode 100644 index 0000000..cc43b74 --- /dev/null +++ b/src/helpers/tableBuilder/src/console-table-printer.ts @@ -0,0 +1,47 @@ +import TableInternal from './internalTable/internal-table'; +import { Dictionary } from './models/common'; +import { ComplexOptions } from './models/external-table'; +import { + convertRawRowOptionsToStandard, + RowOptionsRaw +} from './utils/table-helpers'; + +export default class Table { + table: TableInternal; + + constructor(options?: ComplexOptions | string[]) { + this.table = new TableInternal(options); + } + + addColumn(column: string) { + this.table.addColumn(column); + return this; + } + + addColumns(columns: string[]) { + this.table.addColumns(columns); + return this; + } + + addRow(text: Dictionary, rowOptions?: RowOptionsRaw) { + this.table.addRow(text, convertRawRowOptionsToStandard(rowOptions)); + return this; + } + + addRows(toBeInsertedRows: any, rowOptions?: RowOptionsRaw) { + this.table.addRows( + toBeInsertedRows, + convertRawRowOptionsToStandard(rowOptions) + ); + return this; + } + + printTable() { + const tableRendered = this.table.renderTable(); + console.log(tableRendered); + } + + render() { + return this.table.renderTable(); + } +} diff --git a/src/helpers/tableBuilder/src/internalTable/input-converter.ts b/src/helpers/tableBuilder/src/internalTable/input-converter.ts new file mode 100644 index 0000000..c3800e6 --- /dev/null +++ b/src/helpers/tableBuilder/src/internalTable/input-converter.ts @@ -0,0 +1,25 @@ +import { COLOR } from '../models/common'; +import { ColumnOptionsRaw } from '../models/external-table'; +import { Column } from '../models/internal-table'; +import { DEFAULT_ROW_ALIGNMENT } from '../utils/table-constants'; + +export const objIfExists = (key: string, val: any) => { + if (!val) { + return {}; + } + + return { + [key]: val, + }; +}; + +export const rawColumnToInternalColumn = ( + column: ColumnOptionsRaw +): Column => ({ + name: column.name, + title: column.title || column.name, + ...objIfExists('color', column.color as COLOR), + ...objIfExists('maxLen', column.maxLen), + ...objIfExists('minLen', column.minLen), + alignment: column.alignment || DEFAULT_ROW_ALIGNMENT, +}); diff --git a/src/helpers/tableBuilder/src/internalTable/internal-table-printer.ts b/src/helpers/tableBuilder/src/internalTable/internal-table-printer.ts new file mode 100644 index 0000000..ffdeb59 --- /dev/null +++ b/src/helpers/tableBuilder/src/internalTable/internal-table-printer.ts @@ -0,0 +1,220 @@ +import { Row } from '../models/common'; +import { Column, TableStyleDetails } from '../models/internal-table'; +import ColoredConsoleLine from '../utils/colored-console-line'; +import { textWithPadding } from '../utils/string-utils'; +import { + DEFAULT_COLUMN_LEN, + DEFAULT_HEADER_ALIGNMENT, + DEFAULT_HEADER_FONT_COLOR, + DEFAULT_ROW_ALIGNMENT, + DEFAULT_ROW_FONT_COLOR, +} from '../utils/table-constants'; +import { + cellText, + createHeaderAsRow, + createRow, + getWidthLimitedColumnsArray, + renderTableHorizontalBorders, +} from '../utils/table-helpers'; +import TableInternal from './internal-table'; +import { preProcessColumns, preProcessRows } from './table-pre-processors'; + +// ║ Index ║ ║ ║ +const renderOneLine = ( + tableStyle: TableStyleDetails, + columns: Column[], + currentLineIndex: number, + widthLimitedColumnsArray: { [key: string]: string[] }, + isHeader: boolean | undefined, + row: Row +): string => { + const line = new ColoredConsoleLine(); + line.addCharsWithColor(DEFAULT_ROW_FONT_COLOR, tableStyle.vertical); + columns.forEach((column) => { + const thisLineHasText = + currentLineIndex < widthLimitedColumnsArray[column.name].length; + + const textForThisLine: string = thisLineHasText + ? cellText(widthLimitedColumnsArray[column.name][currentLineIndex]) + : ''; + + line.addCharsWithColor(DEFAULT_ROW_FONT_COLOR, ' '); + line.addCharsWithColor( + (isHeader && DEFAULT_HEADER_FONT_COLOR) || column.color || row.color, + textWithPadding( + textForThisLine, + column.alignment || DEFAULT_ROW_ALIGNMENT, + column.length || DEFAULT_COLUMN_LEN + ) + ); + line.addCharsWithColor(DEFAULT_ROW_FONT_COLOR, ` ${tableStyle.vertical}`); + }); + return line.renderConsole(); +}; + +// ║ Bold ║ text ║ value ║ +// ║ Index ║ ║ ║ +const renderWidthLimitedLines = ( + tableStyle: TableStyleDetails, + columns: Column[], + row: Row, + isHeader?: boolean +): string[] => { + // { col1: ['How', 'Is', 'Going'], col2: ['I am', 'Tom'], } + const widthLimitedColumnsArray = getWidthLimitedColumnsArray(columns, row); + + const totalLines = Object.values(widthLimitedColumnsArray).reduce( + (a, b) => Math.max(a, b.length), + 0 + ); + + const ret = []; + for ( + let currentLineIndex = 0; + currentLineIndex < totalLines; + currentLineIndex += 1 + ) { + const singleLine = renderOneLine( + tableStyle, + columns, + currentLineIndex, + widthLimitedColumnsArray, + isHeader, + row + ); + + ret.push(singleLine); + } + + return ret; +}; + +// ║ 1 ║ I would like some red wine please ║ 10.212 ║ +const renderRow = (table: TableInternal, row: Row): string[] => { + let ret: string[] = []; + ret = ret.concat( + renderWidthLimitedLines(table.tableStyle, table.columns, row) + ); + return ret; +}; + +/* + The analysis Result + ╔═══════╦═══════════════════════════════════════╦════════╗ +*/ +const renderTableTitle = (table: TableInternal): string[] => { + const ret: string[] = []; + + if (table.title === undefined) { + return ret; + } + + const getTableWidth = () => { + const reducer = (accumulator: number, currentValue: number) => + // ║ cell ║, 2 spaces + cellTextSize + one border on the left + accumulator + currentValue + 2 + 1; + return table.columns + .map((m) => m.length || DEFAULT_COLUMN_LEN) + .reduce(reducer, 1); + }; + + const titleWithPadding = textWithPadding( + table.title as string, + DEFAULT_HEADER_ALIGNMENT, + getTableWidth() + ); + const styledText = new ColoredConsoleLine(); + styledText.addCharsWithColor(DEFAULT_HEADER_FONT_COLOR, titleWithPadding); + // The analysis Result + ret.push(styledText.renderConsole()); + return ret; +}; + +/* + ╔═══════╦═══════════════════════════════════════╦════════╗ + ║ index ║ text ║ value ║ + ╟═══════╬═══════════════════════════════════════╬════════╢ +*/ +const renderTableHeaders = (table: TableInternal): string[] => { + let ret: string[] = []; + + // ╔═══════╦═══════════════════════════════════════╦════════╗ + ret.push( + renderTableHorizontalBorders( + table.tableStyle.headerTop, + table.columns.map((m: Column) => m.length || DEFAULT_COLUMN_LEN) + ) + ); + + // ║ index ║ text ║ value ║ + const row = createHeaderAsRow(createRow, table.columns); + ret = ret.concat( + renderWidthLimitedLines(table.tableStyle, table.columns, row, true) + ); + + // ╟═══════╬═══════════════════════════════════════╬════════╢ + ret.push( + renderTableHorizontalBorders( + table.tableStyle.headerBottom, + table.columns.map((m) => m.length || DEFAULT_COLUMN_LEN) + ) + ); + + return ret; +}; + +const renderTableEnding = (table: TableInternal): string[] => { + const ret: string[] = []; + // ╚═══════╩═══════════════════════════════════════╩════════╝ + ret.push( + renderTableHorizontalBorders( + table.tableStyle.tableBottom, + table.columns.map((m) => m.length || DEFAULT_COLUMN_LEN) + ) + ); + return ret; +}; + +const renderRowSeparator = (table: TableInternal, row: Row): string[] => { + const ret: string[] = []; + const lastRowIndex = table.rows.length - 1; + const currentRowIndex = table.rows.indexOf(row); + + if (currentRowIndex !== lastRowIndex && row.separator) { + // ╟═══════╬═══════════════════════════════════════╬════════╢ + ret.push( + renderTableHorizontalBorders( + table.tableStyle.rowSeparator, + table.columns.map((m) => m.length || DEFAULT_COLUMN_LEN) + ) + ); + } + return ret; +}; + +export const renderTable = (table: TableInternal): string => { + preProcessColumns(table); // enable / disable cols, find maxLn of each col/ computed Columns + preProcessRows(table); // sort and filter + + const ret: string[] = []; + renderTableTitle(table).forEach((row) => ret.push(row)); + + renderTableHeaders(table).forEach((row) => ret.push(row)); + + table.rows.forEach((row) => { + renderRow(table, row).forEach((row_) => ret.push(row_)); + renderRowSeparator(table, row).forEach((row_) => ret.push(row_)); + }); + renderTableEnding(table).forEach((row) => ret.push(row)); + return ret.join('\n'); +}; + +export const renderSimpleTable = (rows: any[]) => { + const table = new TableInternal(); + table.addRows(rows); + return renderTable(table); +}; + +export const printSimpleTable = (rows: any[]) => { + console.log(renderSimpleTable(rows)); +}; diff --git a/src/helpers/tableBuilder/src/internalTable/internal-table.ts b/src/helpers/tableBuilder/src/internalTable/internal-table.ts new file mode 100644 index 0000000..a7b443f --- /dev/null +++ b/src/helpers/tableBuilder/src/internalTable/internal-table.ts @@ -0,0 +1,141 @@ +import { Dictionary, Row } from '../models/common'; +import { + ComplexOptions, + ComputedColumn, + RowFilterFunction, + RowSortFunction, +} from '../models/external-table'; +import { Column, TableStyleDetails } from '../models/internal-table'; +import { + DEFAULT_TABLE_STYLE, + DEFAULT_ROW_ALIGNMENT, + DEFAULT_ROW_FONT_COLOR, + DEFAULT_ROW_SEPARATOR, +} from '../utils/table-constants'; +import { + createColumFromComputedColumn, + createColumFromOnlyName, + createRow, + RowOptions, +} from '../utils/table-helpers'; +import { rawColumnToInternalColumn } from './input-converter'; +import { renderTable } from './internal-table-printer'; + +const DEFAULT_ROW_SORT_FUNC = () => 0; + +const DEFAULT_ROW_FILTER_FUNC = () => true; + +class TableInternal { + title?: string; + + tableStyle: TableStyleDetails; + + columns: Column[]; + + rows: Row[]; + + filterFunction: RowFilterFunction; + + sortFunction: RowSortFunction; + + enabledColumns: string[]; + + disabledColumns: string[]; + + computedColumns: any[]; + + rowSeparator: boolean; + + initSimple(columns: string[]) { + this.columns = columns.map((column) => ({ + name: column, + title: column, + alignment: DEFAULT_ROW_ALIGNMENT, + })); + } + + initDetailed(options: ComplexOptions) { + this.title = options?.title || this.title; + this.tableStyle = options?.style || this.tableStyle; + this.sortFunction = options?.sort || this.sortFunction; + this.filterFunction = options?.filter || this.filterFunction; + this.enabledColumns = options?.enabledColumns || this.enabledColumns; + this.disabledColumns = options?.disabledColumns || this.disabledColumns; + this.computedColumns = options?.computedColumns || this.computedColumns; + this.columns = + options?.columns?.map(rawColumnToInternalColumn) || this.columns; + this.rowSeparator = options?.rowSeparator || this.rowSeparator; + + if (options.rows !== undefined) { + this.addRows(options.rows); + } + } + + constructor(options?: ComplexOptions | string[]) { + // default construction + this.rows = []; + this.columns = []; + this.title = undefined; + this.tableStyle = DEFAULT_TABLE_STYLE; + this.filterFunction = DEFAULT_ROW_FILTER_FUNC; + this.sortFunction = DEFAULT_ROW_SORT_FUNC; + this.enabledColumns = []; + this.disabledColumns = []; + this.computedColumns = []; + this.rowSeparator = DEFAULT_ROW_SEPARATOR; + + if (options instanceof Array) { + this.initSimple(options); + } else if (typeof options === 'object') { + this.initDetailed(options); + } + } + + createColumnFromRow(text: Dictionary) { + const colNames = this.columns.map((col) => col.name); + Object.keys(text).forEach((key) => { + if (!colNames.includes(key)) { + this.columns.push(createColumFromOnlyName(key)); + } + }); + } + + addColumn(textOrObj: string | ComputedColumn) { + if (typeof textOrObj === 'string') { + this.columns.push(createColumFromOnlyName(textOrObj)); + } else { + this.columns.push(createColumFromComputedColumn(textOrObj)); + } + } + + addColumns(toBeInsertedColumns: string[]) { + toBeInsertedColumns.forEach((toBeInsertedColumn) => { + this.addColumn(toBeInsertedColumn); + }); + } + + addRow(text: Dictionary, options?: RowOptions) { + this.createColumnFromRow(text); + this.rows.push( + createRow( + options?.color || DEFAULT_ROW_FONT_COLOR, + text, + options?.separator !== undefined + ? options?.separator + : this.rowSeparator + ) + ); + } + + addRows(toBeInsertedRows: Dictionary[], options?: RowOptions) { + toBeInsertedRows.forEach((toBeInsertedRow) => { + this.addRow(toBeInsertedRow, options); + }); + } + + renderTable() { + return renderTable(this); + } +} + +export default TableInternal; diff --git a/src/helpers/tableBuilder/src/internalTable/table-pre-processors.ts b/src/helpers/tableBuilder/src/internalTable/table-pre-processors.ts new file mode 100644 index 0000000..1b8ef87 --- /dev/null +++ b/src/helpers/tableBuilder/src/internalTable/table-pre-processors.ts @@ -0,0 +1,53 @@ +/* eslint-disable no-param-reassign */ +import { Row } from '../models/common'; +import { ComputedColumn } from '../models/external-table'; +import { Column } from '../models/internal-table'; +import { findLenOfColumn } from '../utils/table-helpers'; +import TableInternal from './internal-table'; + +const createComputedColumnsIfNecessary = (table: TableInternal) => { + if (table.computedColumns.length) { + table.computedColumns.forEach((computedColumn: ComputedColumn) => { + table.addColumn(computedColumn); + table.rows.forEach((row: Row) => { + row.text[computedColumn.name] = computedColumn.function(row.text); + }); + }); + } +}; + +const disableColumnsIfNecessary = (table: TableInternal) => { + if (table.enabledColumns.length) { + table.columns = table.columns.filter((col: Column) => + table.enabledColumns.includes(col.name) + ); + } +}; + +const enableColumnsIfNecessary = (table: TableInternal) => { + if (table.disabledColumns.length) { + table.columns = table.columns.filter( + (col: Column) => !table.disabledColumns.includes(col.name) + ); + } +}; + +const findColumnWidth = (table: TableInternal) => { + table.columns.forEach((column) => { + column.length = findLenOfColumn(column, table.rows); + }); +}; + +export const preProcessColumns = (table: TableInternal) => { + createComputedColumnsIfNecessary(table); + enableColumnsIfNecessary(table); + disableColumnsIfNecessary(table); + findColumnWidth(table); +}; + +export const preProcessRows = (table: TableInternal) => { + const newRows = table.rows + .filter((r) => table.filterFunction(r.text)) + .sort((r1, r2) => table.sortFunction(r1.text, r2.text)); + table.rows = newRows; +}; diff --git a/src/helpers/tableBuilder/src/models/common.ts b/src/helpers/tableBuilder/src/models/common.ts new file mode 100644 index 0000000..6d2b9e6 --- /dev/null +++ b/src/helpers/tableBuilder/src/models/common.ts @@ -0,0 +1,13 @@ +import { ALIGNMENTS, COLORS } from '../utils/table-constants'; + +export type ALIGNMENT = typeof ALIGNMENTS[number]; + +export type COLOR = typeof COLORS[number]; +export interface Dictionary { + [key: string]: any; +} +export interface Row { + color: COLOR; + separator: boolean; + text: Dictionary; +} diff --git a/src/helpers/tableBuilder/src/models/external-table.ts b/src/helpers/tableBuilder/src/models/external-table.ts new file mode 100644 index 0000000..4c2df30 --- /dev/null +++ b/src/helpers/tableBuilder/src/models/external-table.ts @@ -0,0 +1,34 @@ +import { ALIGNMENT, COLOR, Dictionary } from './common'; +import { TableStyleDetails } from './internal-table'; + +export { ALIGNMENT, COLOR }; + +export interface ColumnOptionsRaw { + name: string; // unique id + title?: string; // the value that will be printed, if not present this will be 'name' + alignment?: ALIGNMENT; + color?: COLOR; + maxLen?: number; + minLen?: number; +} + +export interface ComputedColumn extends ColumnOptionsRaw { + function: (arg0: any) => any; +} + +export type RowSortFunction = (row1: any, row2: any) => number; + +export type RowFilterFunction = (row: any) => Boolean; + +export interface ComplexOptions { + style?: TableStyleDetails; + title?: string; + columns?: ColumnOptionsRaw[]; + rows?: Dictionary[]; + sort?: RowSortFunction; + filter?: RowFilterFunction; + enabledColumns?: string[]; + disabledColumns?: string[]; + computedColumns?: ComputedColumn[]; + rowSeparator?: boolean; +} diff --git a/src/helpers/tableBuilder/src/models/internal-table.ts b/src/helpers/tableBuilder/src/models/internal-table.ts new file mode 100644 index 0000000..50d048a --- /dev/null +++ b/src/helpers/tableBuilder/src/models/internal-table.ts @@ -0,0 +1,31 @@ +import { ALIGNMENT, COLOR } from './common'; + +/* +All the fields of Internal Table has to be mandatory +These fields are generated based on user input +and during generated is some input is missing it is filled by default value. +*/ + +export interface Column { + name: string; + title: string; + alignment?: ALIGNMENT; + color?: COLOR; + length?: number; + minLen?: number; + maxLen?: number; +} + +type TableLineDetailsKeys = 'left' | 'right' | 'mid' | 'other'; + +export type TableLineDetails = { + [key in TableLineDetailsKeys]: string; +}; + +export type TableStyleDetails = { + headerTop: TableLineDetails; + headerBottom: TableLineDetails; + tableBottom: TableLineDetails; + vertical: string; + rowSeparator?: TableLineDetails; +}; diff --git a/src/helpers/tableBuilder/src/utils/colored-console-line.ts b/src/helpers/tableBuilder/src/utils/colored-console-line.ts new file mode 100644 index 0000000..7de589d --- /dev/null +++ b/src/helpers/tableBuilder/src/utils/colored-console-line.ts @@ -0,0 +1,35 @@ +import { COLOR } from '../models/common'; + +const COLOR_MAP: { + [key in COLOR]?: string; +} = { + red: '\x1b[31m', + green: '\x1b[32m', + yellow: '\x1b[33m', + blue: '\x1b[34m', + magenta: '\x1b[35m', + cyan: '\x1b[36m', + white: '\x1b[37m', + crimson: '\x1b[38m', + white_bold: '\x1b[01m', + reset: '\x1b[0m' +}; + +export const colorString = (color: COLOR, text: string) => + `${color && COLOR_MAP[color]}${text}${COLOR_MAP.reset}`; + +export default class ColoredConsoleLine { + text: string; + + constructor() { + this.text = ''; + } + + addCharsWithColor(color: COLOR, text: string) { + this.text += colorString(color, text); + } + + renderConsole(): string { + return this.text; + } +} diff --git a/src/helpers/tableBuilder/src/utils/console-utils.ts b/src/helpers/tableBuilder/src/utils/console-utils.ts new file mode 100644 index 0000000..93620c8 --- /dev/null +++ b/src/helpers/tableBuilder/src/utils/console-utils.ts @@ -0,0 +1,9 @@ +import { wcswidth } from 'simple-wcswidth'; + +/* eslint-disable no-control-regex */ +const colorRegex = /\x1b\[\d{1,3}m/g; // \x1b[30m \x1b[305m + +const stripAnsi = (str: string): string => str.replace(colorRegex, ''); +const findWidthInConsole = (str: string): number => wcswidth(stripAnsi(str)); + +export default findWidthInConsole; diff --git a/src/helpers/tableBuilder/src/utils/string-utils.ts b/src/helpers/tableBuilder/src/utils/string-utils.ts new file mode 100644 index 0000000..5155c73 --- /dev/null +++ b/src/helpers/tableBuilder/src/utils/string-utils.ts @@ -0,0 +1,57 @@ +import { ALIGNMENT } from '../models/common'; +import findWidthInConsole from './console-utils'; + +// ("How are you?",center, 20) => " How are you? " +// ("How are you?",right, 20) => " How are you?" +export const textWithPadding = ( + text: string, + alignment: ALIGNMENT, + columnLen: number +): string => { + const curTextSize = findWidthInConsole(text); + // alignments for center padding case + const leftPadding = Math.floor((columnLen - curTextSize) / 2); + const rightPadding = columnLen - leftPadding - curTextSize; + + // console.log(text, columnLen, curTextSize); + switch (alignment) { + case 'left': + return text.concat(' '.repeat(columnLen - curTextSize)); + case 'center': + return ' ' + .repeat(leftPadding) + .concat(text) + .concat(' '.repeat(rightPadding)); + case 'right': + default: + return ' '.repeat(columnLen - curTextSize).concat(text); + } +}; + +// ("How are you?",10) => ["How are ", "you?"] +export const limitWidth = (inpStr: string, width: number): string[] => { + const ret: string[] = []; + + const spaceSeparatedStrings = inpStr.split(' '); + + let now: string[] = []; + let cnt = 0; + spaceSeparatedStrings.forEach((strWithoutSpace) => { + const consoleWidth = findWidthInConsole(strWithoutSpace); + if (cnt + consoleWidth <= width) { + cnt += consoleWidth + 1; // 1 for the space + now.push(strWithoutSpace); + } else { + ret.push(now.join(' ')); + now = [strWithoutSpace]; + cnt = consoleWidth + 1; + } + }); + ret.push(now.join(' ')); + + return ret; +}; + +// ("How are you?",10) => ["How are ", "you?"] +export const biggestWordInSentence = (inpStr: string): number => + inpStr.split(' ').reduce((a, b) => Math.max(a, findWidthInConsole(b)), 0); diff --git a/src/helpers/tableBuilder/src/utils/table-constants.ts b/src/helpers/tableBuilder/src/utils/table-constants.ts new file mode 100644 index 0000000..90d7bf9 --- /dev/null +++ b/src/helpers/tableBuilder/src/utils/table-constants.ts @@ -0,0 +1,62 @@ +import { ALIGNMENT, COLOR } from '../models/common'; +import { TableStyleDetails } from '../models/internal-table'; + +export const DEFAULT_COLUMN_LEN = 20; + +export const DEFAULT_ROW_SEPARATOR = false; + +export const DEFAULT_TABLE_STYLE: TableStyleDetails = { + /* + Default Style + ┌────────────┬─────┬──────┐ + │ foo │ bar │ baz │ + │ frobnicate │ bar │ quuz │ + └────────────┴─────┴──────┘ + */ + headerTop: { + left: '┌', + mid: '┬', + right: '┐', + other: '─', + }, + headerBottom: { + left: '├', + mid: '┼', + right: '┤', + other: '─', + }, + tableBottom: { + left: '└', + mid: '┴', + right: '┘', + other: '─', + }, + vertical: '│', + rowSeparator: { + left: '├', + mid: '┼', + right: '┤', + other: '─', + }, +}; + +export const ALIGNMENTS = ['right', 'left', 'center']; + +export const COLORS = [ + 'red', + 'green', + 'yellow', + 'white', + 'blue', + 'magenta', + 'cyan', + 'crimson', + 'white_bold', + 'reset', +]; + +export const DEFAULT_ROW_FONT_COLOR: COLOR = 'white'; +export const DEFAULT_HEADER_FONT_COLOR: COLOR = 'white_bold'; + +export const DEFAULT_ROW_ALIGNMENT: ALIGNMENT = 'right'; +export const DEFAULT_HEADER_ALIGNMENT: ALIGNMENT = 'center'; diff --git a/src/helpers/tableBuilder/src/utils/table-helpers.ts b/src/helpers/tableBuilder/src/utils/table-helpers.ts new file mode 100644 index 0000000..51478a3 --- /dev/null +++ b/src/helpers/tableBuilder/src/utils/table-helpers.ts @@ -0,0 +1,154 @@ +import { objIfExists } from '../internalTable/input-converter'; +import { COLOR, Dictionary, Row } from '../models/common'; +import { ComputedColumn } from '../models/external-table'; +import { Column } from '../models/internal-table'; +import findWidthInConsole from './console-utils'; +import { biggestWordInSentence, limitWidth } from './string-utils'; +import { + DEFAULT_COLUMN_LEN, + DEFAULT_ROW_ALIGNMENT, + DEFAULT_ROW_SEPARATOR, + DEFAULT_HEADER_FONT_COLOR, +} from './table-constants'; + +const max = (a: number, b: number) => Math.max(a, b); + +// takes any input that is given by user and converts to string +export const cellText = (text: string | number): string => + text === undefined || text === null ? '' : `${text}`; + +export interface RowOptionsRaw { + color?: string; + separator?: boolean; +} + +export interface RowOptions { + color: COLOR; + separator: boolean; +} + +export const convertRawRowOptionsToStandard = ( + options?: RowOptionsRaw +): RowOptions | undefined => { + if (options) { + return { + color: options.color as COLOR, + separator: options.separator || DEFAULT_ROW_SEPARATOR, + }; + } + return undefined; +}; + +export const createTableHorizontalBorders = ( + { + left, + mid, + right, + other, + }: { left: string; mid: string; right: string; other: string }, + column_lengths: number[] +) => { + // ╚ + let ret = left; + + // ╚═══════╩═══════════════════════════════════════╩════════╩ + column_lengths.forEach((len) => { + ret += other.repeat(len + 2); + ret += mid; + }); + + // ╚═══════╩═══════════════════════════════════════╩════════ + ret = ret.slice(0, -mid.length); + + // ╚═══════╩═══════════════════════════════════════╩════════╝ + ret += right; + return ret; +}; + +export const createColumFromOnlyName = (name: string): Column => ({ + name, + title: name, +}); +export const createColumFromComputedColumn = ( + column: ComputedColumn +): Column => ({ + name: column.name, + title: column.title || column.name, + ...objIfExists('color', column.color as COLOR), + ...objIfExists('maxLen', column.maxLen), + ...objIfExists('minLen', column.minLen), + alignment: column.alignment || DEFAULT_ROW_ALIGNMENT, +}); + +export const createRow = ( + color: COLOR, + text: Dictionary, + separator: boolean +): Row => ({ + color, + separator, + text, +}); + +export const findLenOfColumn = (column: Column, rows: Row[]): number => { + const columnId = column.name; + const columnTitle = column.title; + let length = max(0, column?.minLen || 0); + + if (column.maxLen) { + // if customer input is mentioned a max width, lets see if all other can fit here + // if others cant fit find the max word length so that at least the table can be printed + length = max( + length, + max(column.maxLen, biggestWordInSentence(columnTitle)) + ); + length = rows.reduce( + (acc, row) => + max(acc, biggestWordInSentence(cellText(row.text[columnId]))), + length + ); + return length; + } + + length = max(length, findWidthInConsole(columnTitle)); + + rows.forEach((row) => { + length = max(length, findWidthInConsole(cellText(row.text[columnId]))); + }); + + return length; +}; + +export const renderTableHorizontalBorders = ( + style: any, + column_lengths: number[] +): string => { + const str = createTableHorizontalBorders(style, column_lengths); + return str; +}; + +export const createHeaderAsRow = (createRowFn: any, columns: Column[]): Row => { + const headerColor: COLOR = DEFAULT_HEADER_FONT_COLOR; + const row: Row = createRowFn(headerColor, {}, false); + columns.forEach((column) => { + row.text[column.name] = column.title; + }); + return row; +}; + +// { col1: ['How', 'Is', 'Going'], col2: ['I am', 'Tom'], } +export const getWidthLimitedColumnsArray = ( + columns: Column[], + row: Row +): { [key: string]: string[] } => { + const ret: { [key: string]: string[] } = {}; + + columns.forEach((column) => { + ret[column.name] = limitWidth( + cellText(row.text[column.name]), + column.length || DEFAULT_COLUMN_LEN + ); + }); + + return ret; +}; diff --git a/src/lib/display.ts b/src/lib/display.ts index 4349aa5..2ec19d9 100644 --- a/src/lib/display.ts +++ b/src/lib/display.ts @@ -1,5 +1,6 @@ import { Frame } from '../index'; import { table } from 'table'; +import { Table } from '../helpers/tableBuilder'; /** * getTable returns the data compatible with table.table @@ -35,6 +36,40 @@ function getTable( } /** + * displayTable prints the table to the console + * @param rowdata the rowdata to be sent to the frame + */ +export function displayTable(rowdata: any[][]): void { + // Convert row data into object with keys and values, keys are the column names stored in rowdata[0] + const headerObject: any = []; + rowdata[0].forEach((column) => { + headerObject.push({ + name: column, + alignment: 'left', + paddingLeft: 2, + bold: true, + paddingRight: 2 + }); + }); + const table = new Table({ + columns: headerObject + }); + + const rowdataObject = rowdata.map((row) => { + const rowObject: any = {}; + row.forEach((value, index) => { + rowObject[rowdata[0][index]] = value; + }); + return rowObject; + }); + + // remove the first row from rowdataObject + const rowdataObjectWithoutHeader = rowdataObject.slice(1); + table.addRows(rowdataObjectWithoutHeader); + table.printTable(); +} + +/** * show prints the frame in console.table format * @returns the current frame * @throws Error if the frame is empty @@ -63,7 +98,7 @@ export function show(this: Frame): void { numberOfRows - 1 ]; const combinedRow = [...firstThreeRows, [...middleRow], ...lastThreeRows]; - console.log(table(getTable(combinedRow, this.columns, indexRow))); + displayTable(getTable(combinedRow, this.columns, indexRow)); } } } diff --git a/tests/simple.test.ts b/tests/simple.test.ts new file mode 100644 index 0000000..fa2167f --- /dev/null +++ b/tests/simple.test.ts @@ -0,0 +1,10 @@ +import { Frame } from '../src/index'; +import { data, header } from './support/people'; + +const frame = new Frame(data, header); + +describe('Print a frame', () => { + it('should print a frame', () => { + frame.show(); + }); +}); |
