diff --git a/.github/workflows/readme-automation.yml b/.github/workflows/readme-automation.yml new file mode 100644 index 00000000..fa375529 --- /dev/null +++ b/.github/workflows/readme-automation.yml @@ -0,0 +1,44 @@ +# SPDX-FileCopyrightText: Copyright (c) 2022-2026 Objectionary.com +# SPDX-License-Identifier: MIT +--- +# yamllint disable rule:line-length +name: readme-automation + +'on': + workflow_dispatch: + push: + paths: + - "src/eoc.js" +concurrency: + group: readmeautomation-${{ github.ref }} + cancel-in-progress: true +jobs: + build: + runs-on: ubuntu-24.04 + + steps: + - name: Check out repo + uses: actions/checkout@v6 + - name: Use Node + uses: actions/setup-node@v6 + with: + node-version: "18.x" + check-latest: false + - name: Clean install + run: npm ci + - name: Install global + run: npm install -g + - name: Run readme_automation script + run: |- + node scripts/readme_automation.js + - name: Create Pull Request + uses: peter-evans/create-pull-request@v8 + with: + commit-message: "chore(readme): insert sections into README from CLI help" + branch: chore/readme-automation + title: "chore(readme): insert sections into README from CLI help" + body: | + This PR was automatically generated by GitHub Actions. + + - Insert sections into README from CLI help output + - Keeps documentation in sync with the CLI diff --git a/README.md b/README.md index 751862b9..fc14ea86 100644 --- a/README.md +++ b/README.md @@ -115,6 +115,7 @@ That's it. You can also do many other things with `eoc` commands (the flow is explained in [this blog post][blog]): + * `register` finds necessary `.eo` files and registers them in a JSON catalog * `assemble` parses `.eo` files into `.xmir`, optimizes them, and pulls foreign EO objects @@ -151,6 +152,7 @@ There are also commands that help manipulate with XMIR and EO sources * ~~`dejump` removes `goto` objects~~ * ~~`infer` suggests object names where it's possible to infer them~~ * ~~`flatten` moves inner objects to upper level~~ + This command line toolkit simply integrates other tools available in the [@objectionary](https://github.com/objectionary) GitHub organization. diff --git a/scripts/readme_automation.js b/scripts/readme_automation.js new file mode 100644 index 00000000..1970b1b9 --- /dev/null +++ b/scripts/readme_automation.js @@ -0,0 +1,50 @@ +#!/usr/bin/env node +/* + * SPDX-FileCopyrightText: Copyright (c) 2022-2026 Objectionary.com + * SPDX-License-Identifier: MIT + */ + +const assert = require('assert'); +const { commandsDescription } = require("../src/eoc"); + +function escapeRegex(str) { + return str.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} + +function updateSection(sectionName, newContent, readMeContent) { + const start = ``; + const end = ``; + const regex = new RegExp( + `(${escapeRegex(start)})([\\s\\S]*?)(${escapeRegex(end)})`, + "g" + ); + return readMeContent.replace(regex, `$1\n${newContent}$3`); +} + +function bulletListTemplate(rows) { + return `${rows.map(([cmd, desc]) => `* \`${cmd}\` ${desc}`).join("\n") }\n`; +} + +function modifyReadme(commands,fs) { + assert.ok(commands.length > 0,'Commands should have rows') + const commandsMarkdown = bulletListTemplate(commands); + assert.ok(commandsMarkdown.length > 0,"commandsMarkdown result should have text") + let readMeContent = fs.readFileSync('README.md', "utf8"); + assert.ok(readMeContent.length > 0,"readMeContent should have text") + readMeContent = updateSection('commands', commandsMarkdown, readMeContent); + assert.ok(readMeContent.length > 0,"readMeContent should sill have text after modification") + fs.writeFileSync('README.md',readMeContent); +} + +function syncCommandsToReadme(fs) +{ + modifyReadme(commandsDescription(),fs); +} + +module.exports = { + bulletListTemplate, updateSection, modifyReadme, syncCommandsToReadme +}; + +if (require.main === module) { + syncCommandsToReadme(require("fs")); +}; diff --git a/src/eoc.js b/src/eoc.js index 352be112..1a15cbbd 100755 --- a/src/eoc.js +++ b/src/eoc.js @@ -394,12 +394,19 @@ program.command('fmt') }); }); -try { - program.parse(process.argv); -} catch (e) { - console.error(e.message); - console.debug(e.stack); - process.exit(1); +if (require.main === module) { + try { + program.parse(process.argv); + } catch (e) { + console.error(e.message); + console.debug(e.stack); + process.exit(1); + } +} + +module.exports.commandsDescription = function commandsDescription() { + return program.commands + .map(c => [c.name(),c.description()]); } /** diff --git a/test/test_eoc.js b/test/test_eoc.js index ef403f9f..cbc449c1 100644 --- a/test/test_eoc.js +++ b/test/test_eoc.js @@ -20,6 +20,14 @@ describe('eoc', () => { assert(stdout.includes(version.when)); done(); }); + it('can get commands description from eoc as a module', (done) => { + const commandsDescriptionList = require('../src/eoc').commandsDescription(); + assert(commandsDescriptionList.length > 0,"commandsDescriptionList should have more then one element"); + assert(commandsDescriptionList[0].length == 2,"commandsDescriptionList element should have 2 values"); + assert(commandsDescriptionList[0][0].length > 0,"First value of commandsDescriptionList element should have non-zero length"); + assert(commandsDescriptionList[0][1].length > 0,"Second value of commandsDescriptionList element should have non-zero length"); + done(); + }); }); describe('eoc', () => { diff --git a/test/test_readme_automation.js b/test/test_readme_automation.js new file mode 100644 index 00000000..dcac222d --- /dev/null +++ b/test/test_readme_automation.js @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: Copyright (c) 2022-2026 Objectionary.com + * SPDX-License-Identifier: MIT + */ + +const assert = require("assert"); +const path = require("path"); +const { updateSection, bulletListTemplate, modifyReadme, syncCommandsToReadme} = require(path.join(__dirname, "../scripts/readme_automation.js")); + +const readme = `before\n\nold content\n\n\nkeep\n\nafter` +const commandsList = [['one','a'],['two','b'],['three','c']]; +const keywords = ["keep","after","before","",""]; +const fsMockProto = {readMeContent:null, readFileSync(path,options){ return readme; }, writeFileSync(path,data){ this.readMeContent = data; }}; + + +describe("readme_automation scripts", () => { + it("updateSection replaces only the requested section", () => { + const readmeUpdated = updateSection("commands", "new content", readme); + assert.ok(!readmeUpdated.includes("old content"),"Update README should no have 'old content'"); + assert.ok(keywords.every(sub => readmeUpdated.includes(sub)),"Update README should have all keywords"); + assert.ok(readmeUpdated.includes("new content"),"Update README should have `new content`"); + }); + it("bulletListTemplate renders a list", async () => { + const bulletListMarkdown = bulletListTemplate(commandsList); + assert.strictEqual(bulletListMarkdown, "* `one` a\n* `two` b\n* `three` c\n"); + }); + it("call modifyReadme with fs mock", async () => { + const fsMock = {...fsMockProto}; + modifyReadme(commandsList,fsMock); + assert.notEqual(fsMock.readMeContent,null,"README file should have some content"); + assert.ok(!fsMock.readMeContent.includes("old content"),"README file should no have 'old content'"); + assert.ok(keywords.every(sub => fsMock.readMeContent.includes(sub)),"README file should have all keywords"); + assert.ok(["* `one` a","* `two` b","* `three` c"].every(sub => fsMock.readMeContent.includes(sub))); + }); + it("test the whole workflow with fs mock", async () => { + const fsMock = {...fsMockProto}; + syncCommandsToReadme(fsMock); + assert.notEqual(fsMock.readMeContent,null,"README file should have some content"); + }); +});