Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 44 additions & 0 deletions .github/workflows/readme-automation.yml
Original file line number Diff line number Diff line change
@@ -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
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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]):

<!-- BEGIN COMMANDS SECTION -->
* `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
Expand Down Expand Up @@ -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~~
<!-- END COMMANDS SECTION -->

This command line toolkit simply integrates other tools available in
the [@objectionary](https://github.com/objectionary) GitHub organization.
Expand Down
50 changes: 50 additions & 0 deletions scripts/readme_automation.js
Original file line number Diff line number Diff line change
@@ -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 = `<!-- BEGIN ${sectionName.toUpperCase()} SECTION -->`;
const end = `<!-- END ${sectionName.toUpperCase()} SECTION -->`;
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"));
};
19 changes: 13 additions & 6 deletions src/eoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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()]);
}

/**
Expand Down
8 changes: 8 additions & 0 deletions test/test_eoc.js
Original file line number Diff line number Diff line change
Expand Up @@ -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', () => {
Expand Down
40 changes: 40 additions & 0 deletions test/test_readme_automation.js
Original file line number Diff line number Diff line change
@@ -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<!-- BEGIN COMMANDS SECTION -->\nold content\n<!-- END COMMANDS SECTION -->\n<!-- BEGIN OPTIONS SECTION -->\nkeep\n<!-- END OPTIONS SECTION -->\nafter`
const commandsList = [['one','a'],['two','b'],['three','c']];
const keywords = ["keep","after","before","<!-- BEGIN COMMANDS SECTION -->","<!-- END COMMANDS SECTION -->"];
const fsMockProto = {readMeContent:null, readFileSync(path,options){ return readme; }, writeFileSync(path,data){ this.readMeContent = data; }};


describe("readme_automation scripts", () => {
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@paskhalov In these tests, you check how parseBlock, updateSection and bulletListTemplate are working. There is no need for that. We would like to have a single function that would update readme and we should test only this function. All of the other functions are just intermediate steps.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just one test, delete the rest?

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@paskhalov I'm not sure about a single test, but we need one function.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, got it

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");
});
});
Loading