From 6e14ae50e48bb57e9c90d4e026e702d4cc10db4a Mon Sep 17 00:00:00 2001 From: Niek Palm Date: Tue, 2 Mar 2021 22:02:36 +0100 Subject: [PATCH] feat: support CR and CRLF line breaks (#13) --- .editorconfig | 7 ++ .gitignore | 3 +- README.md | 14 +-- .../{index.test.ts => action-docs.test.ts} | 16 +++- __tests__/fixtures/all_fields_action.yml.crlf | 28 ++++++ .../fixtures/all_fields_readme.input.crlf | 7 ++ ...opy 2.md => all_fields_readme.output.crlf} | 74 +++++++------- __tests__/linebreak.test.ts | 14 +++ package-lock.json | 96 +++++++++++-------- package.json | 4 +- src/action-docs.ts | 68 +++++++------ src/cli.ts | 10 ++ src/linebreak.ts | 24 +++++ 13 files changed, 256 insertions(+), 109 deletions(-) create mode 100644 .editorconfig rename __tests__/{index.test.ts => action-docs.test.ts} (80%) create mode 100644 __tests__/fixtures/all_fields_action.yml.crlf create mode 100644 __tests__/fixtures/all_fields_readme.input.crlf rename __tests__/fixtures/{all_fields_readme copy 2.md => all_fields_readme.output.crlf} (80%) create mode 100644 __tests__/linebreak.test.ts create mode 100644 src/linebreak.ts diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 00000000..5e4f9c26 --- /dev/null +++ b/.editorconfig @@ -0,0 +1,7 @@ +[*] +end_of_line = lf +insert_final_newline = true + +[*.crlf] +end_of_line = crlf +tab_width = 20 diff --git a/.gitignore b/.gitignore index 32526050..dc438c2b 100644 --- a/.gitignore +++ b/.gitignore @@ -94,4 +94,5 @@ typings/ .DS_Store Thumbs.db -.npmrc \ No newline at end of file +.npmrc +lib diff --git a/README.md b/README.md index 768f63c8..f45bebfe 100644 --- a/README.md +++ b/README.md @@ -41,13 +41,15 @@ The following options are available via the CLI ``` Options: - --help Show help [boolean] - --version Show version number [boolean] - -t, --toc-level TOC level used for markdown + --help Show help [boolean] + --version Show version number [boolean] + -t, --toc-level TOC level used for markdown [number] [required] [default: 2] - -a, --action GitHub action file [string] [default: "action.yml"] - --no-banner Print no banner - -u, --update-readme Update readme file. [string] + -a, --action GitHub action file [string] [default: "action.yml"] + --no-banner Print no banner + -u, --update-readme Update readme file. [string] + --line-breaks, --lb Used line breaks in the generated docs. + [string] [choices: "CR", "LF", "CRLF"] [default: "LF"] ``` diff --git a/__tests__/index.test.ts b/__tests__/action-docs.test.ts similarity index 80% rename from __tests__/index.test.ts rename to __tests__/action-docs.test.ts index 1a3d65aa..21a8678a 100644 --- a/__tests__/index.test.ts +++ b/__tests__/action-docs.test.ts @@ -1,5 +1,6 @@ -import { generateActionMarkdownDocs } from "../src"; +import { generateActionMarkdownDocs, Options } from "../src"; import { readFileSync, writeFileSync } from "fs"; +import { option } from "yargs"; test("With defaults.", async () => { const markdown = await generateActionMarkdownDocs(); @@ -48,10 +49,20 @@ test("Update filled readme (all fields)", async () => { ); }); +test("Update readme (all fields) CRLF", async () => { + await testReadme( + "__tests__/fixtures/all_fields_action.yml.crlf", + "__tests__/fixtures/all_fields_readme.input.crlf", + "__tests__/fixtures/all_fields_readme.output.crlf", + { lineBreaks: "CRLF" } + ); +}); + async function testReadme( actionFile: string, originalReadme: string, - fixtureReadme: string + fixtureReadme: string, + overwriteOptions?: Options ) { const expected = readFileSync(fixtureReadme, "utf-8"); const original = readFileSync(originalReadme, "utf-8"); @@ -60,6 +71,7 @@ async function testReadme( actionFile: actionFile, updateReadme: true, readmeFile: originalReadme, + ...overwriteOptions, }); const updated = readFileSync(originalReadme, "utf-8"); diff --git a/__tests__/fixtures/all_fields_action.yml.crlf b/__tests__/fixtures/all_fields_action.yml.crlf new file mode 100644 index 00000000..c3bf8afe --- /dev/null +++ b/__tests__/fixtures/all_fields_action.yml.crlf @@ -0,0 +1,28 @@ +name: 'An Action' +description: 'Default test' +author: 'Niek Palm' +inputs: + inputA: + description: 'A description A' + required: false + inputB: + description: 'A description B' + required: true + inputC: + description: 'A description C' + required: true + default: C + inputD: + description: 'A description D' + required: false + default: D + +outputs: + outputA: + description: 'A description A' + outputB: + description: 'A description B' + +runs: + using: 'node12' + main: 'dist/index.js' \ No newline at end of file diff --git a/__tests__/fixtures/all_fields_readme.input.crlf b/__tests__/fixtures/all_fields_readme.input.crlf new file mode 100644 index 00000000..e8866386 --- /dev/null +++ b/__tests__/fixtures/all_fields_readme.input.crlf @@ -0,0 +1,7 @@ + + + + + + + diff --git a/__tests__/fixtures/all_fields_readme copy 2.md b/__tests__/fixtures/all_fields_readme.output.crlf similarity index 80% rename from __tests__/fixtures/all_fields_readme copy 2.md rename to __tests__/fixtures/all_fields_readme.output.crlf index 95bb3bbe..31d7fb2b 100644 --- a/__tests__/fixtures/all_fields_readme copy 2.md +++ b/__tests__/fixtures/all_fields_readme.output.crlf @@ -1,33 +1,41 @@ - -## Description - -Default test - - - - - -## Inputs - -| parameter | description | required | default | -| - | - | - | - | -| inputA | A description A | `false` | | -| inputB | A description B | `true` | | -| inputC | A description C | `true` | C | -| inputD | A description D | `false` | D | - - - - - - -## Outputs - -| parameter | description | -| - | - | -| outputA | A description A | -| outputB | A description B | - - - - + +## Description + +Default test + + + + + +## Inputs + +| parameter | description | required | default | +| - | - | - | - | +| inputA | A description A | `false` | | +| inputB | A description B | `true` | | +| inputC | A description C | `true` | C | +| inputD | A description D | `false` | D | + + + + + + +## Outputs + +| parameter | description | +| - | - | +| outputA | A description A | +| outputB | A description B | + + + + + + +## Runs + +This action is an `node12` action. + + + diff --git a/__tests__/linebreak.test.ts b/__tests__/linebreak.test.ts new file mode 100644 index 00000000..9e8dbdcd --- /dev/null +++ b/__tests__/linebreak.test.ts @@ -0,0 +1,14 @@ +import * as lb from "../src/linebreak"; + +test("Line break types", () => { + expect(lb.getLineBreakType("CR")).toEqual("CR"); + expect(lb.getLineBreakType("CRLF")).toEqual("CRLF"); + expect(lb.getLineBreakType("LF")).toEqual("LF"); + expect(lb.getLineBreakType("unknown")).toEqual("LF"); +}); + +test("Line break types", () => { + expect(lb.getLineBreak("CR")).toEqual("\r"); + expect(lb.getLineBreak("CRLF")).toEqual("\r\n"); + expect(lb.getLineBreak("LF")).toEqual("\n"); +}); diff --git a/package-lock.json b/package-lock.json index 20dc8775..c8e3e8ef 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1391,58 +1391,60 @@ "@typescript-eslint/typescript-estree": "4.15.1", "eslint-scope": "^5.0.0", "eslint-utils": "^2.0.0" + }, + "dependencies": { + "@typescript-eslint/typescript-estree": { + "version": "4.15.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.15.1.tgz", + "integrity": "sha512-z8MN3CicTEumrWAEB2e2CcoZa3KP9+SMYLIA2aM49XW3cWIaiVSOAGq30ffR5XHxRirqE90fgLw3e6WmNx5uNw==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.15.1", + "@typescript-eslint/visitor-keys": "4.15.1", + "debug": "^4.1.1", + "globby": "^11.0.1", + "is-glob": "^4.0.1", + "semver": "^7.3.2", + "tsutils": "^3.17.1" + } + } } }, "@typescript-eslint/parser": { - "version": "4.15.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.15.2.tgz", - "integrity": "sha512-SHeF8xbsC6z2FKXsaTb1tBCf0QZsjJ94H6Bo51Y1aVEZ4XAefaw5ZAilMoDPlGghe+qtq7XdTiDlGfVTOmvA+Q==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.16.1.tgz", + "integrity": "sha512-/c0LEZcDL5y8RyI1zLcmZMvJrsR6SM1uetskFkoh3dvqDKVXPsXI+wFB/CbVw7WkEyyTKobC1mUNp/5y6gRvXg==", "dev": true, "requires": { - "@typescript-eslint/scope-manager": "4.15.2", - "@typescript-eslint/types": "4.15.2", - "@typescript-eslint/typescript-estree": "4.15.2", + "@typescript-eslint/scope-manager": "4.16.1", + "@typescript-eslint/types": "4.16.1", + "@typescript-eslint/typescript-estree": "4.16.1", "debug": "^4.1.1" }, "dependencies": { "@typescript-eslint/scope-manager": { - "version": "4.15.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.15.2.tgz", - "integrity": "sha512-Zm0tf/MSKuX6aeJmuXexgdVyxT9/oJJhaCkijv0DvJVT3ui4zY6XYd6iwIo/8GEZGy43cd7w1rFMiCLHbRzAPQ==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.16.1.tgz", + "integrity": "sha512-6IlZv9JaurqV0jkEg923cV49aAn8V6+1H1DRfhRcvZUrptQ+UtSKHb5kwTayzOYTJJ/RsYZdcvhOEKiBLyc0Cw==", "dev": true, "requires": { - "@typescript-eslint/types": "4.15.2", - "@typescript-eslint/visitor-keys": "4.15.2" + "@typescript-eslint/types": "4.16.1", + "@typescript-eslint/visitor-keys": "4.16.1" } }, "@typescript-eslint/types": { - "version": "4.15.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.15.2.tgz", - "integrity": "sha512-r7lW7HFkAarfUylJ2tKndyO9njwSyoy6cpfDKWPX6/ctZA+QyaYscAHXVAfJqtnY6aaTwDYrOhp+ginlbc7HfQ==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.16.1.tgz", + "integrity": "sha512-nnKqBwMgRlhzmJQF8tnFDZWfunXmJyuXj55xc8Kbfup4PbkzdoDXZvzN8//EiKR27J6vUSU8j4t37yUuYPiLqA==", "dev": true }, - "@typescript-eslint/typescript-estree": { - "version": "4.15.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.15.2.tgz", - "integrity": "sha512-cGR8C2g5SPtHTQvAymEODeqx90pJHadWsgTtx6GbnTWKqsg7yp6Eaya9nFzUd4KrKhxdYTTFBiYeTPQaz/l8bw==", - "dev": true, - "requires": { - "@typescript-eslint/types": "4.15.2", - "@typescript-eslint/visitor-keys": "4.15.2", - "debug": "^4.1.1", - "globby": "^11.0.1", - "is-glob": "^4.0.1", - "semver": "^7.3.2", - "tsutils": "^3.17.1" - } - }, "@typescript-eslint/visitor-keys": { - "version": "4.15.2", - "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.15.2.tgz", - "integrity": "sha512-TME1VgSb7wTwgENN5KVj4Nqg25hP8DisXxNBojM4Nn31rYaNDIocNm5cmjOFfh42n7NVERxWrDFoETO/76ePyg==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.16.1.tgz", + "integrity": "sha512-s/aIP1XcMkEqCNcPQtl60ogUYjSM8FU2mq1O7y5cFf3Xcob1z1iXWNB6cC43Op+NGRTFgGolri6s8z/efA9i1w==", "dev": true, "requires": { - "@typescript-eslint/types": "4.15.2", + "@typescript-eslint/types": "4.16.1", "eslint-visitor-keys": "^2.0.0" } } @@ -1465,18 +1467,36 @@ "dev": true }, "@typescript-eslint/typescript-estree": { - "version": "4.15.1", - "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.15.1.tgz", - "integrity": "sha512-z8MN3CicTEumrWAEB2e2CcoZa3KP9+SMYLIA2aM49XW3cWIaiVSOAGq30ffR5XHxRirqE90fgLw3e6WmNx5uNw==", + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.16.1.tgz", + "integrity": "sha512-m8I/DKHa8YbeHt31T+UGd/l8Kwr0XCTCZL3H4HMvvLCT7HU9V7yYdinTOv1gf/zfqNeDcCgaFH2BMsS8x6NvJg==", "dev": true, "requires": { - "@typescript-eslint/types": "4.15.1", - "@typescript-eslint/visitor-keys": "4.15.1", + "@typescript-eslint/types": "4.16.1", + "@typescript-eslint/visitor-keys": "4.16.1", "debug": "^4.1.1", "globby": "^11.0.1", "is-glob": "^4.0.1", "semver": "^7.3.2", "tsutils": "^3.17.1" + }, + "dependencies": { + "@typescript-eslint/types": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.16.1.tgz", + "integrity": "sha512-nnKqBwMgRlhzmJQF8tnFDZWfunXmJyuXj55xc8Kbfup4PbkzdoDXZvzN8//EiKR27J6vUSU8j4t37yUuYPiLqA==", + "dev": true + }, + "@typescript-eslint/visitor-keys": { + "version": "4.16.1", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.16.1.tgz", + "integrity": "sha512-s/aIP1XcMkEqCNcPQtl60ogUYjSM8FU2mq1O7y5cFf3Xcob1z1iXWNB6cC43Op+NGRTFgGolri6s8z/efA9i1w==", + "dev": true, + "requires": { + "@typescript-eslint/types": "4.16.1", + "eslint-visitor-keys": "^2.0.0" + } + } } }, "@typescript-eslint/visitor-keys": { diff --git a/package.json b/package.json index bd27afbf..7c6afd3c 100644 --- a/package.json +++ b/package.json @@ -54,7 +54,7 @@ "@types/jest": "^26.0.15", "@types/js-yaml": "^4.0.0", "@types/node": "^14.14.30", - "@typescript-eslint/parser": "^4.8.1", + "@typescript-eslint/parser": "^4.16.1", "conventional-changelog-conventionalcommits": "^4.5.0", "eslint": "^7.17.0", "eslint-plugin-github": "^4.1.1", @@ -74,4 +74,4 @@ "directories": { "dist": "lib" } -} \ No newline at end of file +} diff --git a/src/action-docs.ts b/src/action-docs.ts index 96829123..52accb5b 100644 --- a/src/action-docs.ts +++ b/src/action-docs.ts @@ -1,12 +1,14 @@ import { load } from "js-yaml"; import { readFileSync } from "fs"; import replaceInFile from "replace-in-file"; +import { LineBreakType, getLineBreak } from "./linebreak"; export interface Options { tocLevel?: number; actionFile?: string; updateReadme?: boolean; readmeFile?: string; + lineBreaks?: LineBreakType; } interface ActionMarkdown { @@ -21,13 +23,21 @@ interface DefaultOptions { actionFile: string; updateReadme: boolean; readmeFile: string; + lineBreaks: LineBreakType; } +// enum LineBreak { +// CR = "\r", +// LF = "\n", +// CRLF = "\r'n", +// } + export const defaultOptions: DefaultOptions = { tocLevel: 2, actionFile: "action.yml", updateReadme: false, readmeFile: "README.md", + lineBreaks: "LF", }; interface ActionInput { required?: boolean; @@ -35,7 +45,7 @@ interface ActionInput { default?: string; } -function createMdTable(data: string[][]): string { +function createMdTable(options: DefaultOptions, data: string[][]): string { let result = ""; for (const line of data) { @@ -43,7 +53,7 @@ function createMdTable(data: string[][]): string { for (const c of line) { result = `${result} ${c} |`; } - result = `${result}\n`; + result = `${result}${getLineBreak(options.lineBreaks)}`; } return result; @@ -66,39 +76,48 @@ export async function generateActionMarkdownDocs( }; const docs = generateActionDocs(options); - if (options.updateReadme) { - await updateReadme(options.readmeFile, docs.description, "description"); - await updateReadme(options.readmeFile, docs.inputs, "inputs"); - await updateReadme(options.readmeFile, docs.outputs, "outputs"); - await updateReadme(options.readmeFile, docs.runs, "runs"); + await updateReadme(options, docs.description, "description"); + await updateReadme(options, docs.inputs, "inputs"); + await updateReadme(options, docs.outputs, "outputs"); + await updateReadme(options, docs.runs, "runs"); } return `${docs.description + docs.inputs + docs.outputs + docs.runs}`; } async function updateReadme( - file: string, + options: DefaultOptions, text: string, section: string ): Promise { const to = new RegExp( - `(?:(?:\n.*)+)?` + `(?:(?:\r\n|\r|\n.*)+)?` ); await replaceInFile.replaceInFile({ - files: file, + files: options.readmeFile, from: to, - to: `\n${text}\n`, + to: `${getLineBreak( + options.lineBreaks + )}${text}${getLineBreak( + options.lineBreaks + )}`, }); } function createMarkdownSection( - tocLevel: number, + options: DefaultOptions, data: string, header: string ): string { - return data !== "" ? `${getToc(tocLevel)} ${header}\n\n${data}\n\n` : ""; + return data !== "" + ? `${getToc(options.tocLevel)} ${header}${getLineBreak( + options.lineBreaks + )}${getLineBreak(options.lineBreaks)}${data}${getLineBreak( + options.lineBreaks + )}${getLineBreak(options.lineBreaks)}` + : ""; } function generateActionDocs(options: DefaultOptions): ActionMarkdown { @@ -142,26 +161,21 @@ function generateActionDocs(options: DefaultOptions): ActionMarkdown { } } - const inputMdTable = createMdTable(inputHeaders.concat(inputRows)); - const outputMdTable = createMdTable(outputHeaders.concat(outputRows)); + const inputMdTable = createMdTable(options, inputHeaders.concat(inputRows)); + const outputMdTable = createMdTable( + options, + outputHeaders.concat(outputRows) + ); const descriptionMd = createMarkdownSection( - options.tocLevel, + options, yml.description, "Description" ); - const inputMd = createMarkdownSection( - options.tocLevel, - inputMdTable, - "Inputs" - ); - const outputMd = createMarkdownSection( - options.tocLevel, - outputMdTable, - "Outputs" - ); + const inputMd = createMarkdownSection(options, inputMdTable, "Inputs"); + const outputMd = createMarkdownSection(options, outputMdTable, "Outputs"); const runMd = createMarkdownSection( - options.tocLevel, + options, `This action is an \`${yml.runs.using}\` action.`, "Runs" ); diff --git a/src/cli.ts b/src/cli.ts index fcd8b60d..5be22de0 100644 --- a/src/cli.ts +++ b/src/cli.ts @@ -4,6 +4,7 @@ import yargs from "yargs"; import { generateActionMarkdownDocs, defaultOptions } from "."; import chalk from "chalk"; import figlet from "figlet"; +import { getLineBreakType } from "./linebreak"; const args = yargs.options({ "toc-level": { @@ -30,6 +31,14 @@ const args = yargs.options({ type: "string", alias: "u", }, + "line-breaks": { + description: "Used line breaks in the generated docs.", + default: "LF", + choices: ["CR", "LF", "CRLF"], + demandOption: false, + type: "string", + alias: "lb", + }, }).argv; args["banner"] === undefined && @@ -46,6 +55,7 @@ generateActionMarkdownDocs({ args["update-readme"] === undefined || args["update-readme"] === "" ? defaultOptions.readmeFile : args["update-readme"], + lineBreaks: getLineBreakType(args["line-breaks"]), }) .then((r) => { console.info(r); diff --git a/src/linebreak.ts b/src/linebreak.ts new file mode 100644 index 00000000..e8608f1e --- /dev/null +++ b/src/linebreak.ts @@ -0,0 +1,24 @@ +export type LineBreakType = "CR" | "LF" | "CRLF"; + +export function getLineBreak(lineBreakType: LineBreakType): string { + switch (lineBreakType) { + case "CR": + return "\r"; + case "LF": + return "\n"; + case "CRLF": + return "\r\n"; + } +} + +export function getLineBreakType(value: string): LineBreakType { + if (isLineBreakType(value)) { + return value; + } else { + return "LF" as LineBreakType; + } +} + +function isLineBreakType(value: string): value is LineBreakType { + return value === "CR" || value === "LF" || value === "CRLF"; +}