mirror of
https://github.com/davidgasquez/awesome-duckdb.git
synced 2026-11-04 10:15:31 +00:00
🐛 Check README order within licensing subgroups
This commit is contained in:
@@ -8,7 +8,7 @@ if (!basePath || !currentPath) {
|
||||
process.exit(2);
|
||||
}
|
||||
|
||||
const SORTED_SECTION = "Community Extensions";
|
||||
const SORTED_SECTION = "Extensions / Community Extensions";
|
||||
const IGNORED_SECTIONS = new Set(["Contents"]);
|
||||
|
||||
function headingName(markdown) {
|
||||
@@ -18,12 +18,18 @@ function headingName(markdown) {
|
||||
|
||||
function parseSections(path) {
|
||||
const sections = new Map();
|
||||
const headings = [];
|
||||
let section = null;
|
||||
|
||||
for (const line of readFileSync(path, "utf8").split("\n")) {
|
||||
const heading = line.match(/^#{2,3}\s+(.+?)\s*$/);
|
||||
const heading = line.match(/^(#{2,6})\s+(.+?)\s*$/);
|
||||
if (heading) {
|
||||
section = headingName(heading[1]);
|
||||
const level = heading[1].length;
|
||||
while (headings.length > 0 && headings.at(-1).level >= level) {
|
||||
headings.pop();
|
||||
}
|
||||
headings.push({ level, name: headingName(heading[2]) });
|
||||
section = headings.map(({ name }) => name).join(" / ");
|
||||
if (!sections.has(section)) {
|
||||
sections.set(section, []);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,103 @@
|
||||
import assert from "node:assert/strict";
|
||||
import { spawnSync } from "node:child_process";
|
||||
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
||||
import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import { test } from "node:test";
|
||||
|
||||
const entry = (name) => `- [${name}](https://example.com/${name}) - Description.`;
|
||||
const communityHeading = "## Extensions\n### [Community Extensions](https://duckdb.org/community_extensions/)";
|
||||
const subgroupBase = [
|
||||
"## Tools Powered by DuckDB",
|
||||
"### Business Intelligence & Dashboards",
|
||||
"#### Free & Open Source",
|
||||
entry("Pondview"),
|
||||
"#### Free (Proprietary / Source-Available)",
|
||||
entry("Proprietary"),
|
||||
"### Fun & Demos",
|
||||
"#### Free & Open Source",
|
||||
entry("Demo"),
|
||||
].join("\n");
|
||||
|
||||
const cases = [
|
||||
{
|
||||
name: "append to a flat section",
|
||||
base: `## Resources\n${entry("Existing")}`,
|
||||
current: `## Resources\n${entry("Existing")}\n${entry("New")}`,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "reject insertion before an existing flat entry",
|
||||
base: `## Resources\n${entry("Existing")}`,
|
||||
current: `## Resources\n${entry("New")}\n${entry("Existing")}`,
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
name: "append to a licensing subgroup before another subgroup",
|
||||
base: subgroupBase,
|
||||
current: subgroupBase.replace(entry("Pondview"), `${entry("Pondview")}\n${entry("DUX")}`),
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "reject insertion within a subgroup despite matching entries in another category",
|
||||
base: subgroupBase,
|
||||
current: subgroupBase.replace(entry("Pondview"), `${entry("Demo")}\n${entry("Pondview")}`),
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
name: "move an entry to the bottom of its destination subgroup",
|
||||
base: `${subgroupBase}\n${entry("DUX")}`,
|
||||
current: subgroupBase.replace(entry("Pondview"), `${entry("Pondview")}\n${entry("DUX")}`),
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "reset subgroup context at a top-level section",
|
||||
base: `${subgroupBase}\n## Resources\n${entry("Existing")}`,
|
||||
current: `${subgroupBase}\n${entry("NewDemo")}\n## Resources\n${entry("Existing")}\n${entry("NewResource")}`,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "allow updating an existing description in place",
|
||||
base: `## Resources\n${entry("Existing")}\n${entry("Last")}`,
|
||||
current: `## Resources\n${entry("Existing").replace("Description.", "Updated description.")}\n${entry("Last")}`,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "keep community extensions case-insensitively sorted",
|
||||
base: `${communityHeading}\n${entry("`beta`")}`,
|
||||
current: `${communityHeading}\n${entry("`Alpha`")}\n${entry("`beta`")}`,
|
||||
valid: true,
|
||||
},
|
||||
{
|
||||
name: "reject unsorted community extensions",
|
||||
base: `${communityHeading}\n${entry("`beta`")}`,
|
||||
current: `${communityHeading}\n${entry("`beta`")}\n${entry("`Alpha`")}`,
|
||||
valid: false,
|
||||
},
|
||||
{
|
||||
name: "ignore contents ordering",
|
||||
base: `## Contents\n${entry("Existing")}`,
|
||||
current: `## Contents\n${entry("New")}\n${entry("Existing")}`,
|
||||
valid: true,
|
||||
},
|
||||
];
|
||||
|
||||
for (const { name, base, current, valid } of cases) {
|
||||
test(name, () => {
|
||||
const directory = mkdtempSync(join(tmpdir(), "readme-order-"));
|
||||
try {
|
||||
const basePath = join(directory, "base.md");
|
||||
const currentPath = join(directory, "current.md");
|
||||
writeFileSync(basePath, base);
|
||||
writeFileSync(currentPath, current);
|
||||
const result = spawnSync(process.execPath, [
|
||||
new URL("./check-readme-order.mjs", import.meta.url).pathname,
|
||||
basePath,
|
||||
currentPath,
|
||||
], { encoding: "utf8" });
|
||||
assert.equal(result.status, valid ? 0 : 1, result.stderr || result.stdout);
|
||||
} finally {
|
||||
rmSync(directory, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
}
|
||||
@@ -6,13 +6,13 @@ on:
|
||||
paths:
|
||||
- README.md
|
||||
- .github/lychee.toml
|
||||
- .github/scripts/check-readme-order.mjs
|
||||
- .github/scripts/check-readme-order*.mjs
|
||||
- .github/workflows/linkchecker.yml
|
||||
pull_request:
|
||||
paths:
|
||||
- README.md
|
||||
- .github/lychee.toml
|
||||
- .github/scripts/check-readme-order.mjs
|
||||
- .github/scripts/check-readme-order*.mjs
|
||||
- .github/workflows/linkchecker.yml
|
||||
workflow_dispatch:
|
||||
|
||||
@@ -30,6 +30,9 @@ jobs:
|
||||
steps:
|
||||
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
||||
|
||||
- name: Test entry order checker
|
||||
run: node --test .github/scripts/check-readme-order.test.mjs
|
||||
|
||||
- name: Check entry order
|
||||
if: github.event_name == 'pull_request'
|
||||
env:
|
||||
|
||||
+1
-1
@@ -14,7 +14,7 @@ By participating in this project, you agree to abide by its [Contributor Code of
|
||||
|
||||
### Entry order
|
||||
|
||||
Most sections preserve insertion order. **Append new entries to the bottom of the relevant list**, even when another entry seems similar. Do not alphabetize or otherwise reposition existing entries.
|
||||
Most sections preserve insertion order. **Append new entries to the bottom of the relevant list**, even when another entry seems similar. In **Tools Powered by DuckDB**, append to the bottom of the relevant licensing subgroup within the most specific category, not the bottom of the whole category. Do not alphabetize or otherwise reposition existing entries.
|
||||
|
||||
The **Community Extensions** section is the exception: insert extensions in case-insensitive lexicographic order by extension name. Community extension names use backticks, for example:
|
||||
``- [`extension_name`](https://example.com) - A concise description.``
|
||||
|
||||
Reference in New Issue
Block a user