├── .eslintrc.cjs ├── .gitattributes ├── .github └── workflows │ └── test.yml ├── .gitignore ├── .npmrc ├── ava.config.js ├── changelog.md ├── license ├── package.json ├── readme.md ├── src ├── cli.ts └── util.ts ├── tests ├── .eslintrc.cjs ├── fixtures │ ├── input.json │ └── input.ndjson ├── tests.ts └── tsconfig.json └── tsconfig.json /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const [, , error] = ["off", "warn", "error"] 4 | 5 | module.exports = { 6 | extends: ["./node_modules/ts-standardx/.eslintrc.js"], 7 | ignorePatterns: ["dist"], 8 | rules: { 9 | "no-unused-vars": [ 10 | error, 11 | { 12 | argsIgnorePattern: "^_", 13 | caughtErrorsIgnorePattern: "^_", 14 | varsIgnorePattern: "^_" 15 | } 16 | ], 17 | quotes: [error, "double"], 18 | 19 | "prettier/prettier": [ 20 | error, 21 | { 22 | semi: false, 23 | singleQuote: false, 24 | trailingComma: "none", 25 | bracketSameLine: true, 26 | arrowParens: "avoid" 27 | } 28 | ] 29 | }, 30 | overrides: [ 31 | { 32 | files: ["**/*.{ts,tsx}"], 33 | rules: { 34 | "@typescript-eslint/quotes": [error, "double"] 35 | } 36 | } 37 | ] 38 | } 39 | -------------------------------------------------------------------------------- /.gitattributes: -------------------------------------------------------------------------------- 1 | * text=auto eol=lf 2 | -------------------------------------------------------------------------------- /.github/workflows/test.yml: -------------------------------------------------------------------------------- 1 | name: Test 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | test: 6 | name: ${{ matrix.os }} / Node ${{ matrix.node }} 7 | runs-on: ${{ matrix.os }} 8 | if: "!contains(github.event.head_commit.message, 'skip ci')" 9 | strategy: 10 | matrix: 11 | os: [windows-latest, ubuntu-latest, macOS-latest] 12 | node: [16, 14] 13 | steps: 14 | - uses: actions/checkout@v2 15 | - name: Install Node 16 | uses: actions/setup-node@v2 17 | with: 18 | node-version: ${{ matrix.node }} 19 | - name: Unit tests 20 | run: yarn && yarn test 21 | 22 | - name: Installation test (npm) 23 | run: | 24 | npm install -g 25 | tablemark --version 26 | npm uninstall -g tablemark-cli 27 | 28 | - name: Installation test (npx) 29 | run: | 30 | npx -p . tablemark --version 31 | 32 | - name: Add yarn global directory to path 33 | if: runner.os != 'Windows' 34 | run: yarn global bin >> $GITHUB_PATH 35 | 36 | - name: Add yarn global directory to path (Windows) 37 | if: runner.os == 'Windows' 38 | run: yarn global bin >> $env:GITHUB_PATH 39 | 40 | - name: Installation test (yarn) 41 | run: | 42 | yarn global add $PWD 43 | tablemark --version 44 | yarn global remove tablemark-cli 45 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # ide configurations 2 | .c9 3 | .idea 4 | .vscode 5 | 6 | # lock files 7 | npm-shrinkwrap.json 8 | package-lock.json 9 | yarn.lock 10 | 11 | node_modules 12 | 13 | /dist 14 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | package-lock=false 2 | -------------------------------------------------------------------------------- /ava.config.js: -------------------------------------------------------------------------------- 1 | export default { 2 | extensions: { 3 | ts: "module" 4 | }, 5 | nodeArguments: [ 6 | "--loader=esbuild-node-loader", 7 | "--experimental-specifier-resolution=node" 8 | ] 9 | } 10 | -------------------------------------------------------------------------------- /changelog.md: -------------------------------------------------------------------------------- 1 | ## [`3.0.0`](https://github.com/haltcase/tablemark-cli/compare/v2.0.0...v3.0.0) (2022-04-30) 2 | 3 | 4 | ###### FEATURES 5 | 6 | * require node >= 14.18 ([3bd02ca](https://github.com/haltcase/tablemark-cli/commit/3bd02ca)) 7 | * rewrite in TypeScript / ESM ([1c45f86](https://github.com/haltcase/tablemark-cli/commit/1c45f86)) 8 | 9 | 10 | ###### BREAKING CHANGES 11 | 12 | * support for node 8, 10, 12, and < 14.18 has been dropped. 13 | * the `--columns` option long name is now `--column`. 14 | * to pass input as stdin, `-` must be provided as the input file parameter. 15 | 16 | --- 17 | 18 | ## [`2.0.0`](https://github.com/haltcase/tablemark-cli/compare/v1.1.0...v2.0.0) (2019-07-26) 19 | 20 | 21 | ###### FEATURES 22 | 23 | * require node >= 8.10 ([4d12e35](https://github.com/haltcase/tablemark-cli/commit/4d12e35)) 24 | 25 | 26 | ###### BREAKING CHANGES 27 | 28 | * support for node 4, 6, and < 8.10 has been dropped. 29 | 30 | --- 31 | 32 | ## [`1.1.0`](https://github.com/haltcase/tablemark-cli/compare/360eaef...v1.1.0) (2017-09-14) 33 | 34 | 35 | ###### BUG FIXES 36 | 37 | * actually support node 4 ([360eaef](https://github.com/haltcase/tablemark-cli/commit/360eaef)) 38 | 39 | 40 | ###### FEATURES 41 | 42 | * case header row by default, add disable flag ([a1bdf51](https://github.com/haltcase/tablemark-cli/commit/a1bdf51)) 43 | * prettier markdown output ([eb85d82](https://github.com/haltcase/tablemark-cli/commit/eb85d82)) 44 | * support ndjson ([#4](https://github.com/haltcase/tablemark-cli/issues/4)) ([46f412e](https://github.com/haltcase/tablemark-cli/commit/46f412e)), closes [#4](https://github.com/haltcase/tablemark-cli/issues/4) 45 | * support stdin ([#3](https://github.com/haltcase/tablemark-cli/issues/3)) ([f0d41d8](https://github.com/haltcase/tablemark-cli/commit/f0d41d8)), closes [#3](https://github.com/haltcase/tablemark-cli/issues/3) 46 | 47 | 48 | --- 49 | -------------------------------------------------------------------------------- /license: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) Bo Lingen (https://github.com/haltcase) 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in 13 | all copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 21 | THE SOFTWARE. 22 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "tablemark-cli", 3 | "version": "3.0.0", 4 | "description": "Generate markdown tables from JSON data at the command line.", 5 | "author": "Bo Lingen (https://github.com/haltcase)", 6 | "license": "MIT", 7 | "repository": "https://github.com/haltcase/tablemark-cli.git", 8 | "homepage": "https://github.com/haltcase/tablemark-cli", 9 | "bugs": "https://github.com/haltcase/tablemark-cli/issues", 10 | "engines": { 11 | "node": ">=14.18" 12 | }, 13 | "type": "module", 14 | "exports": "./dist/index.js", 15 | "keywords": [ 16 | "cli", 17 | "cli-app", 18 | "markdown", 19 | "tables", 20 | "json", 21 | "convert", 22 | "generate" 23 | ], 24 | "bin": { 25 | "tablemark": "./dist/cli.js" 26 | }, 27 | "files": [ 28 | "dist" 29 | ], 30 | "scripts": { 31 | "changelog": "changelog", 32 | "lint": "ts-standardx && cd tests && ts-standardx . --disable-gitignore", 33 | "build": "tsc", 34 | "pretest": "npm run build", 35 | "test": "ava", 36 | "prepublishOnly": "npm run lint && npm test" 37 | }, 38 | "dependencies": { 39 | "cmd-ts": "^0.10.1", 40 | "get-stdin": "^9.0.0", 41 | "tablemark": "^3.0.0" 42 | }, 43 | "devDependencies": { 44 | "@citycide/changelog": "^2.0.0", 45 | "@types/node": "^14.18.2", 46 | "ava": "^4.2.0", 47 | "esbuild-node-loader": "^0.8.0", 48 | "execa": "^6.0.0", 49 | "ts-standardx": "^0.8.4", 50 | "typescript": "^4.6.3" 51 | } 52 | } 53 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | # tablemark-cli · [![Version](https://flat.badgen.net/npm/v/tablemark-cli)](https://www.npmjs.com/package/tablemark-cli) [![License](https://flat.badgen.net/npm/license/tablemark-cli)](https://www.npmjs.com/package/tablemark-cli) [![Travis CI](https://flat.badgen.net/travis/haltcase/tablemark-cli)](https://travis-ci.org/haltcase/tablemark-cli) [![JavaScript Standard Style](https://flat.badgen.net/badge/code%20style/standard/green)](https://standardjs.com) 2 | 3 | > Generate markdown tables from JSON data at the command line. 4 | 5 | Render JSON input data as a markdown table from the command line, 6 | powered by the [`tablemark`](https://github.com/haltcase/tablemark) module. 7 | 8 | ## features 9 | 10 | This utility supports: 11 | 12 | * JSON file input from a provided path 13 | * data piped from `stdin` 14 | * NDJSON formatted data ([newline delimited JSON](http://ndjson.org/)) 15 | 16 | ## installation 17 | 18 | ```sh 19 | yarn global add tablemark-cli 20 | 21 | # or 22 | 23 | npm install --global tablemark-cli 24 | ``` 25 | 26 | ## usage 27 | 28 | ```sh 29 | tablemark 3.0.0 30 | > Generate markdown tables from JSON data at the command line. 31 | 32 | ARGUMENTS: 33 | - Path to input file containing JSON data (use - for stdin) 34 | 35 | OPTIONS: 36 | --column , -c= - Custom column name, can be used multiple times (default: infer from object keys) 37 | --align , -a= - Custom alignments, can be used multiple times, applied in order to columns (default: left) 38 | --line-ending, -e - End-of-line string (default: \n) [optional] 39 | --wrap-width, -w - Width at which to hard wrap cell content [default: Infinity] 40 | 41 | FLAGS: 42 | --no-case-headers, -N - Disable automatic sentence casing of inferred column names [default: false] 43 | --wrap-with-gutters, -G - Add '|' characters to wrapped rows [default: false] 44 | --help, -h - show help 45 | --version, -v - print the version 46 | ``` 47 | 48 | To apply the `align` and `column` options to multiple columns, supply the flag 49 | multiple times, like this: 50 | 51 | ```sh 52 | tablemark input.json > output.md -a left -a center -a right 53 | ``` 54 | 55 | ... which will align the first three columns left, center, and right respectively. 56 | 57 | ## stdin 58 | 59 | In bash-like shells: 60 | 61 | ```sh 62 | # stdin -> stdout 63 | echo '{ "one": 1 }' | tablemark - 64 | 65 | # redirect input file content into stdin, then to a file 66 | tablemark - < input.json > output.md 67 | ``` 68 | 69 | In PowerShell: 70 | 71 | ```powershell 72 | # stdin -> stdout 73 | '{ "one": 1 }' | tablemark - 74 | 75 | # redirect input file content into stdin, then to a file 76 | cat input.json | tablemark - > output.md 77 | ``` 78 | 79 | ## ndjson 80 | 81 | [NDJSON](http://ndjson.org) is a form of JSON that delimits multiple JSON objects by newlines: 82 | 83 | ```js 84 | {"name":"trilogy","repo":"[haltcase/trilogy](https://github.com/haltcase/trilogy)","desc":"No-hassle SQLite with type-casting schema models and support for native & pure JS backends."} 85 | {"name":"strat","repo":"[haltcase/strat](https://github.com/haltcase/strat)","desc":"Functional-ish JavaScript string formatting, with inspirations from Python."} 86 | {"name":"tablemark-cli","repo":"[haltcase/tablemark-cli](https://github.com/haltcase/tablemark-cli)","desc":"Generate markdown tables from JSON data at the command line."} 87 | ``` 88 | 89 | This input from a file or stdin is supported just as if it were 90 | a JSON compatible array: 91 | 92 | ```sh 93 | tablemark input.ndjson > output.md 94 | ``` 95 | 96 | ## see also 97 | 98 | * [`tablemark`](https://github.com/haltcase/tablemark) – the module used by this utility 99 | 100 | ## contributing 101 | 102 | Search the [issues](https://github.com/haltcase/tablemark-cli) if you come 103 | across any trouble, open a new one if it hasn't been posted, or, if you're 104 | able, open a [pull request](https://help.github.com/articles/about-pull-requests/). 105 | Contributions of any kind are welcome in this project. 106 | 107 | The following people have already contributed their time and effort: 108 | 109 | * Thomas Jensen (**[@tjconcept](https://github.com/tjconcept)**) 110 | 111 | Thank you! 112 | 113 | ## license 114 | 115 | MIT © Bo Lingen / haltcase 116 | -------------------------------------------------------------------------------- /src/cli.ts: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env node 2 | 3 | import { readFileSync } from "fs" 4 | import { dirname, resolve } from "path" 5 | import { fileURLToPath } from "url" 6 | 7 | import { 8 | Alignment, 9 | alignmentOptions, 10 | InputData, 11 | TablemarkOptions 12 | } from "tablemark" 13 | 14 | import getStdin from "get-stdin" 15 | 16 | import { 17 | array, 18 | Type, 19 | binary, 20 | command, 21 | flag, 22 | multioption, 23 | number, 24 | option, 25 | positional, 26 | string, 27 | run 28 | } from "cmd-ts" 29 | 30 | import { convert, read, parse, zip } from "./util.js" 31 | 32 | interface PackageInfo { 33 | description: string 34 | version: string 35 | } 36 | 37 | const getPackageInfo = (): PackageInfo => { 38 | const pkgPath = dirname(fileURLToPath(import.meta.url)) 39 | 40 | try { 41 | const pkg = readFileSync(resolve(pkgPath, "../package.json"), "utf8") 42 | const { description, version } = JSON.parse(pkg) as { 43 | description: string 44 | version: string 45 | } 46 | 47 | return { description, version } 48 | } catch (e) { 49 | return { description: "", version: "" } 50 | } 51 | } 52 | 53 | const alignmentList: Type = { 54 | async from(input) { 55 | return input.map(part => { 56 | if (part === "") { 57 | return "left" 58 | } 59 | 60 | if (!Object.keys(alignmentOptions).includes(part.toLowerCase())) { 61 | throw new Error(`Expected an Alignment, got "${part}"`) 62 | } 63 | 64 | return part as Alignment 65 | }) 66 | } 67 | } 68 | 69 | export const inputContent: Type = { 70 | async from(input) { 71 | const content = input === "-" ? await getStdin() : read(input) 72 | 73 | if (content === "" && process.stdin.isTTY) { 74 | return [] 75 | } 76 | 77 | return parse(content) 78 | } 79 | } 80 | 81 | const { description, version } = getPackageInfo() 82 | 83 | const cmd = command({ 84 | name: "tablemark", 85 | description, 86 | version, 87 | args: { 88 | inputFile: positional({ 89 | displayName: "input-file", 90 | description: "Path to input file containing JSON data (use - for stdin)", 91 | type: inputContent 92 | }), 93 | column: multioption({ 94 | long: "column", 95 | short: "c", 96 | description: 97 | "Custom column name, can be used multiple times (default: infer from object keys)", 98 | type: array(string) 99 | }), 100 | align: multioption({ 101 | long: "align", 102 | short: "a", 103 | description: 104 | "Custom alignments, can be used multiple times, applied in order to columns (default: left)", 105 | type: alignmentList 106 | }), 107 | noCaseHeaders: flag({ 108 | long: "no-case-headers", 109 | short: "N", 110 | description: "Disable automatic sentence casing of inferred column names", 111 | defaultValue: () => false, 112 | defaultValueIsSerializable: true 113 | }), 114 | lineEnding: option({ 115 | long: "line-ending", 116 | short: "e", 117 | description: "End-of-line string (default: \\n)", 118 | type: string, 119 | defaultValue: () => "\n" 120 | }), 121 | wrapWidth: option({ 122 | long: "wrap-width", 123 | short: "w", 124 | description: "Width at which to hard wrap cell content", 125 | type: number, 126 | defaultValue: () => Infinity, 127 | defaultValueIsSerializable: true 128 | }), 129 | wrapWithGutters: flag({ 130 | long: "wrap-with-gutters", 131 | short: "G", 132 | description: "Add '|' characters to wrapped rows", 133 | defaultValue: () => false, 134 | defaultValueIsSerializable: true 135 | }) 136 | }, 137 | handler: args => { 138 | const options: TablemarkOptions = Object.assign({}, args, { 139 | caseHeaders: !args.noCaseHeaders, 140 | columns: [] 141 | }) 142 | 143 | for (const [name, align] of zip(args.column, args.align)) { 144 | options.columns!.push({ name, align }) 145 | } 146 | 147 | // write results to stdout 148 | process.stdout.write(convert(args.inputFile, options) + "\n") 149 | } 150 | }) 151 | 152 | await run(binary(cmd), process.argv) 153 | -------------------------------------------------------------------------------- /src/util.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from "fs" 2 | import tablemark, { InputData, TablemarkOptions } from "tablemark" 3 | 4 | const jsonIsArrayRegex = /^\s*\[/ 5 | const isEmptyRegex = /^\s*$/ 6 | 7 | export const zip = (listA: T[], listB: U[]): Array<[T, U]> => { 8 | const maxLength = Math.max(listA.length, listB.length) 9 | 10 | return Array.from(new Array(maxLength), (_, index) => [ 11 | listA[index], 12 | listB[index] 13 | ]) 14 | } 15 | 16 | export const read = (input: string): string => { 17 | try { 18 | return readFileSync(input, { encoding: "utf8" }) 19 | } catch (e) { 20 | const detail = e instanceof Error ? ` :: ${e.message}` : "" 21 | throw new ReferenceError(`Error reading file at ${input} ${detail}`.trim()) 22 | } 23 | } 24 | 25 | const parseJson = (input: string): InputData => { 26 | try { 27 | return JSON.parse(input) 28 | } catch (e) { 29 | const details = e instanceof Error ? ` :: ${e.message}` : "" 30 | throw new TypeError( 31 | `Could not parse input as JSON${details}, input:\n${input}`.trim() 32 | ) 33 | } 34 | } 35 | 36 | export const parse = (input: string): InputData => { 37 | if (jsonIsArrayRegex.test(input)) { 38 | return parseJson(input) 39 | } 40 | 41 | // handle ndjson (see http://ndjson.org) 42 | return input 43 | .split("\n") 44 | .filter(line => !isEmptyRegex.test(line)) 45 | .flatMap(parseJson) 46 | } 47 | 48 | export const convert = ( 49 | input?: InputData, 50 | options?: TablemarkOptions 51 | ): string => { 52 | options = Object.assign({}, options) 53 | 54 | if (input == null || input.length === 0) { 55 | return "" 56 | } 57 | 58 | return tablemark(input, options) 59 | } 60 | -------------------------------------------------------------------------------- /tests/.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | "use strict" 2 | 3 | const { resolve } = require("path") 4 | 5 | module.exports = { 6 | extends: [resolve(__dirname, "../.eslintrc.cjs")], 7 | overrides: [ 8 | { 9 | files: ["**/*.{ts,tsx}"], 10 | parserOptions: { 11 | project: resolve(__dirname, "tsconfig.json") 12 | } 13 | } 14 | ] 15 | } 16 | -------------------------------------------------------------------------------- /tests/fixtures/input.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "trilogy", 4 | "repo": "[haltcase/trilogy](https://github.com/haltcase/trilogy)", 5 | "desc": "No-hassle SQLite with type-casting schema models and support for native & pure JS backends." 6 | }, 7 | { 8 | "name": "strat", 9 | "repo": "[haltcase/strat](https://github.com/haltcase/strat)", 10 | "desc": "Functional-ish JavaScript string formatting, with inspirations from Python." 11 | }, 12 | { 13 | "name": "tablemark-cli", 14 | "repo": "[haltcase/tablemark-cli](https://github.com/haltcase/tablemark-cli)", 15 | "desc": "Generate markdown tables from JSON data at the command line." 16 | } 17 | ] 18 | -------------------------------------------------------------------------------- /tests/fixtures/input.ndjson: -------------------------------------------------------------------------------- 1 | {"name":"trilogy","repo":"[haltcase/trilogy](https://github.com/haltcase/trilogy)","desc":"No-hassle SQLite with type-casting schema models and support for native & pure JS backends."} 2 | {"name":"strat","repo":"[haltcase/strat](https://github.com/haltcase/strat)","desc":"Functional-ish JavaScript string formatting, with inspirations from Python."} 3 | {"name":"tablemark-cli","repo":"[haltcase/tablemark-cli](https://github.com/haltcase/tablemark-cli)","desc":"Generate markdown tables from JSON data at the command line."} 4 | -------------------------------------------------------------------------------- /tests/tests.ts: -------------------------------------------------------------------------------- 1 | import { readFileSync } from "fs" 2 | import { dirname, resolve } from "path" 3 | import { fileURLToPath } from "url" 4 | 5 | import test from "ava" 6 | import { execaCommandSync } from "execa" 7 | 8 | interface ExecutionResult { 9 | success: boolean 10 | stdout: string 11 | stderr: string 12 | } 13 | 14 | const joinLines = (lines: string[], lineEnding = "\n"): string => 15 | lines.join(lineEnding) + lineEnding 16 | 17 | const expected = joinLines([ 18 | "| Name | Repo | Desc |", 19 | "| :------------ | :------------------------------------------------------------------ | :------------------------------------------------------------------------------------------ |", 20 | "| trilogy | [haltcase/trilogy](https://github.com/haltcase/trilogy) | No-hassle SQLite with type-casting schema models and support for native & pure JS backends. |", 21 | "| strat | [haltcase/strat](https://github.com/haltcase/strat) | Functional-ish JavaScript string formatting, with inspirations from Python. |", 22 | "| tablemark-cli | [haltcase/tablemark-cli](https://github.com/haltcase/tablemark-cli) | Generate markdown tables from JSON data at the command line. |" 23 | ]) 24 | 25 | const inputLongValue = JSON.stringify({ 26 | "lots of ones": "1".repeat(50) 27 | }) 28 | 29 | const inputThreeColumn = JSON.stringify({ 30 | one: "one", 31 | two: "two", 32 | "three dog": "night" 33 | }) 34 | 35 | const testDirectory = dirname(fileURLToPath(import.meta.url)) 36 | const cliPath = resolve(testDirectory, "../dist/cli.js") 37 | const inputPath = resolve(testDirectory, "./fixtures/input.json") 38 | const ndjsonInputPath = resolve(testDirectory, "./fixtures/input.ndjson") 39 | 40 | const jsonContent = readFileSync(inputPath, "utf8") 41 | 42 | const execute = (argString: string, stdin?: string): ExecutionResult => { 43 | const { stdout, stderr, failed } = execaCommandSync( 44 | `node ${cliPath} ${argString}`.trim(), 45 | { 46 | encoding: "utf8", 47 | input: stdin 48 | } 49 | ) 50 | 51 | return { 52 | success: !failed, 53 | stdout, 54 | stderr 55 | } 56 | } 57 | 58 | test("renders JSON from file as a markdown table", async t => { 59 | const { success, stdout } = execute(inputPath) 60 | t.true(success) 61 | t.is(stdout, expected) 62 | }) 63 | 64 | test("renders NDJSON from file as a markdown table", async t => { 65 | const { success, stdout } = execute(ndjsonInputPath) 66 | t.true(success) 67 | t.is(stdout, expected) 68 | }) 69 | 70 | test("renders JSON content from stdin as a markdown table", async t => { 71 | const { success, stdout } = execute("-", jsonContent) 72 | t.true(success) 73 | t.is(stdout, expected) 74 | }) 75 | 76 | test("fails when input file path does not exist", async t => { 77 | t.throws(() => execute("not-a-file.js"), { 78 | message: /no such file or directory/ 79 | }) 80 | }) 81 | 82 | test("fails when input content is invalid", async t => { 83 | t.throws(() => execute("-", "not json"), { 84 | message: /Could not parse input as JSON/ 85 | }) 86 | }) 87 | 88 | test("long values are not wrapped by default", async t => { 89 | const expected = joinLines([ 90 | "| Lots of ones |", 91 | "| :------------------------------------------------- |", 92 | "| 11111111111111111111111111111111111111111111111111 |" 93 | ]) 94 | 95 | const { success, stdout } = execute("-", inputLongValue) 96 | 97 | t.true(success) 98 | t.is(stdout, expected) 99 | }) 100 | 101 | test("long values are wrapped if `--wrap-width` is supplied", async t => { 102 | const expected = joinLines([ 103 | "| Lots of ones |", 104 | "| :------------------------ |", 105 | "| 1111111111111111111111111 |", 106 | " 1111111111111111111111111 " 107 | ]) 108 | 109 | const { success, stdout } = execute("- --wrap-width 25", inputLongValue) 110 | 111 | t.true(success) 112 | t.is(stdout, expected) 113 | }) 114 | 115 | test("gutters are included on wrapped rows when `--wrap-with-gutters` is supplied", async t => { 116 | const expected = joinLines([ 117 | "| Lots of ones |", 118 | "| :------------------------ |", 119 | "| 1111111111111111111111111 |", 120 | "| 1111111111111111111111111 |" 121 | ]) 122 | 123 | const { success, stdout } = execute( 124 | "- --wrap-width 25 --wrap-with-gutters", 125 | inputLongValue 126 | ) 127 | 128 | t.true(success) 129 | t.is(stdout, expected) 130 | }) 131 | 132 | test("line ending can be customized using `--line-ending`", async t => { 133 | const lineEnding = "~@~" 134 | 135 | const expected = joinLines( 136 | [ 137 | "| Lots of ones |", 138 | "| :------------------------------------------------- |", 139 | "| 11111111111111111111111111111111111111111111111111 |" 140 | ], 141 | lineEnding 142 | ) 143 | 144 | const { success, stdout } = execute( 145 | `- --line-ending ${lineEnding}`, 146 | inputLongValue 147 | ) 148 | 149 | t.true(success) 150 | t.is(stdout, expected) 151 | }) 152 | 153 | test("sentence casing can be disabled with `--no-case-headers`", async t => { 154 | const expected = joinLines([ 155 | "| one | two | three dog |", 156 | "| :---- | :---- | :-------- |", 157 | "| one | two | night |" 158 | ]) 159 | 160 | const { success, stdout } = execute("- --no-case-headers", inputThreeColumn) 161 | 162 | t.true(success) 163 | t.is(stdout, expected) 164 | }) 165 | 166 | test("column alignment can be customized using `--align`", async t => { 167 | const expected = joinLines([ 168 | "| One | Two | Three dog |", 169 | "| :---- | :---: | --------: |", 170 | "| one | two | night |" 171 | ]) 172 | 173 | const { success, stdout } = execute( 174 | "- --align left --align center --align right", 175 | inputThreeColumn 176 | ) 177 | 178 | t.true(success) 179 | t.is(stdout, expected) 180 | }) 181 | 182 | test("column names can be customized using `--column`", async t => { 183 | const expected = joinLines([ 184 | "| AAA | BBB | CCCCC |", 185 | "| :---- | :---- | :---- |", 186 | "| one | two | night |" 187 | ]) 188 | 189 | const { success, stdout } = execute( 190 | "- --column AAA --column BBB --column CCCCC", 191 | inputThreeColumn 192 | ) 193 | 194 | t.true(success) 195 | t.is(stdout, expected) 196 | }) 197 | 198 | test("all options work together as expected", async t => { 199 | const lineEnding = "~@~" 200 | 201 | const expected = joinLines( 202 | [ 203 | "| AAA | BBB | three |", 204 | "| | | dog |", 205 | "| :---- | :---: | ----: |", 206 | "| one | two | night |" 207 | ], 208 | lineEnding 209 | ) 210 | 211 | const columns = ["AAA", "BBB"].map(name => `-c ${name}`).join(" ") 212 | 213 | const alignments = ["left", "center", "right"] 214 | .map(align => `-a ${align}`) 215 | .join(" ") 216 | 217 | const { success, stdout } = execute( 218 | `- ${columns} ${alignments} --wrap-width 3 --wrap-with-gutters --line-ending ${lineEnding} --no-case-headers`, 219 | inputThreeColumn 220 | ) 221 | 222 | t.true(success) 223 | t.is(stdout, expected) 224 | }) 225 | -------------------------------------------------------------------------------- /tests/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "../tsconfig.json", 3 | "include": [ 4 | "." 5 | ] 6 | } 7 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "es2022", 4 | "target": "es2020", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "declaration": true, 8 | "outDir": "dist", 9 | // emitted bin script must use LF line endings to be 10 | // executed properly in all scenarios, see: 11 | // https://github.com/haltcase/tablemark-cli/issues/1 12 | "newLine": "lf" 13 | }, 14 | "include": [ 15 | "src" 16 | ] 17 | } 18 | --------------------------------------------------------------------------------