From 14024bfd38f1f73a345b8863ffa8a57d70dead62 Mon Sep 17 00:00:00 2001 From: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com> Date: Thu, 25 Apr 2024 11:27:37 -0400 Subject: [PATCH 1/6] Group Qiskit modules into sections in left ToC --- scripts/lib/api/Pkg.ts | 18 ++++--- scripts/lib/api/TocModuleGrouping.ts | 63 ++++++++++++++++++++++ scripts/lib/api/conversionPipeline.test.ts | 1 - scripts/lib/api/generateToc.test.ts | 31 +++++++---- scripts/lib/api/generateToc.ts | 56 +++++++++---------- scripts/lib/stringUtils.test.ts | 8 +++ scripts/lib/stringUtils.ts | 4 ++ 7 files changed, 131 insertions(+), 50 deletions(-) create mode 100644 scripts/lib/api/TocModuleGrouping.ts diff --git a/scripts/lib/api/Pkg.ts b/scripts/lib/api/Pkg.ts index 4e733bbd41d..51c91d40993 100644 --- a/scripts/lib/api/Pkg.ts +++ b/scripts/lib/api/Pkg.ts @@ -15,6 +15,10 @@ import { join } from "path/posix"; import { findLegacyReleaseNotes } from "./releaseNotes"; import { getRoot } from "../fs"; import { determineHistoricalQiskitGithubUrl } from "../qiskitMetapackage"; +import { + TocModuleGrouping, + QISKIT_TOC_MODULE_GROUPING, +} from "./TocModuleGrouping"; export interface ReleaseNoteEntry { title: string; @@ -35,7 +39,7 @@ export class Pkg { readonly versionWithoutPatch: string; readonly type: PackageType; readonly releaseNoteEntries: ReleaseNoteEntry[]; - readonly nestModulesInToc: boolean; + readonly tocModuleGrouping?: TocModuleGrouping; static VALID_NAMES = ["qiskit", "qiskit-ibm-runtime", "qiskit-ibm-provider"]; @@ -48,7 +52,7 @@ export class Pkg { versionWithoutPatch: string; type: PackageType; releaseNoteEntries: ReleaseNoteEntry[]; - nestModulesInToc: boolean; + tocModuleGrouping?: TocModuleGrouping; }) { this.name = kwargs.name; this.title = kwargs.title; @@ -58,7 +62,7 @@ export class Pkg { this.versionWithoutPatch = kwargs.versionWithoutPatch; this.type = kwargs.type; this.releaseNoteEntries = kwargs.releaseNoteEntries; - this.nestModulesInToc = kwargs.nestModulesInToc; + this.tocModuleGrouping = kwargs.tocModuleGrouping; } static async fromArgs( @@ -83,7 +87,7 @@ export class Pkg { githubSlug: "qiskit/qiskit", hasSeparateReleaseNotes: true, releaseNoteEntries, - nestModulesInToc: true, + tocModuleGrouping: QISKIT_TOC_MODULE_GROUPING, }); } @@ -95,7 +99,6 @@ export class Pkg { githubSlug: "qiskit/qiskit-ibm-runtime", hasSeparateReleaseNotes: false, releaseNoteEntries: [], - nestModulesInToc: false, }); } @@ -107,7 +110,6 @@ export class Pkg { githubSlug: "qiskit/qiskit-ibm-provider", hasSeparateReleaseNotes: false, releaseNoteEntries: [], - nestModulesInToc: false, }); } @@ -123,7 +125,7 @@ export class Pkg { versionWithoutPatch?: string; type?: PackageType; releaseNoteEntries?: ReleaseNoteEntry[]; - nestModulesInToc?: boolean; + tocModuleGrouping?: TocModuleGrouping; }): Pkg { return new Pkg({ name: kwargs.name ?? "my-quantum-project", @@ -134,7 +136,7 @@ export class Pkg { versionWithoutPatch: kwargs.versionWithoutPatch ?? "0.1", type: kwargs.type ?? "latest", releaseNoteEntries: kwargs.releaseNoteEntries ?? [], - nestModulesInToc: kwargs.nestModulesInToc ?? false, + tocModuleGrouping: kwargs.tocModuleGrouping ?? undefined, }); } diff --git a/scripts/lib/api/TocModuleGrouping.ts b/scripts/lib/api/TocModuleGrouping.ts new file mode 100644 index 00000000000..bb1b46a709b --- /dev/null +++ b/scripts/lib/api/TocModuleGrouping.ts @@ -0,0 +1,63 @@ +// This code is a Qiskit project. +// +// (C) Copyright IBM 2024. +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +import { hasPrefix } from "../stringUtils"; + +export type TocModuleGrouping = { + sections: readonly string[]; + moduleToSection: (module: string) => string; +}; + +const QISKIT_SECTIONS = [ + "Circuit and Operator Construction", + "Transpilation", + "Primitives and Providers", + "Results and Visualizations", + "OpenQASM Support", + "Pulse-level Programming", + "Other", +] as const; +type QiskitSection = (typeof QISKIT_SECTIONS)[number]; + +export const QISKIT_TOC_MODULE_GROUPING: TocModuleGrouping = { + sections: QISKIT_SECTIONS as readonly string[], + moduleToSection: (module: string): QiskitSection => { + if (hasPrefix(module, ["qiskit.circuit", "qiskit.quantum_info"])) { + return "Circuit and Operator Construction"; + } + if ( + hasPrefix(module, [ + "qiskit.dagcircuit", + "qiskit.passmanager", + "qiskit.synthesis", + "qiskit.transpiler", + ]) + ) { + return "Transpilation"; + } + if (hasPrefix(module, ["qiskit.primitive", "qiskit.providers"])) { + return "Primitives and Providers"; + } + if ( + hasPrefix(module, ["qiskit.qpy", "qiskit.result", "qiskit.visualization"]) + ) { + return "Results and Visualizations"; + } + if (hasPrefix(module, ["qiskit.qasm"])) { + return "OpenQASM Support"; + } + if (hasPrefix(module, ["qiskit.scheduler", "qiskit.pulse"])) { + return "Pulse-level Programming"; + } + return "Other"; + }, +}; diff --git a/scripts/lib/api/conversionPipeline.test.ts b/scripts/lib/api/conversionPipeline.test.ts index 94205fbac2f..f449c2cfb1a 100644 --- a/scripts/lib/api/conversionPipeline.test.ts +++ b/scripts/lib/api/conversionPipeline.test.ts @@ -60,7 +60,6 @@ test("qiskit-sphinx-theme", async () => { versionWithoutPatch: "0.1", type: "latest", releaseNoteEntries: [], - nestModulesInToc: false, }); const markdownFolder = pkg.outputDir(docsBaseFolder); diff --git a/scripts/lib/api/generateToc.test.ts b/scripts/lib/api/generateToc.test.ts index 0c09d651565..1cfa98e27d8 100644 --- a/scripts/lib/api/generateToc.test.ts +++ b/scripts/lib/api/generateToc.test.ts @@ -121,8 +121,14 @@ describe("generateToc", () => { `); }); - test("TOC with nested submodules", () => { - const toc = generateToc(Pkg.mock({ nestModulesInToc: true }), [ + test("TOC with grouped modules", () => { + const tocModuleGrouping = { + // Order is intentionally reversed. + sections: ["Group 2", "Group 1"], + moduleToSection: (module: string) => + module == "qiskit_ibm_runtime" ? "Group 1" : "Group 2", + }; + const toc = generateToc(Pkg.mock({ tocModuleGrouping }), [ { meta: { apiType: "module", @@ -149,15 +155,14 @@ describe("generateToc", () => { }, ]); expect(toc).toEqual({ + collapsed: true, + title: "My Quantum Project", children: [ { - title: "qiskit_ibm_runtime", - url: "/docs/runtime", - }, - { + title: "Group 2", children: [ { - title: "Module overview", + title: "qiskit_ibm_runtime.options", url: "/docs/options", }, { @@ -165,15 +170,21 @@ describe("generateToc", () => { url: "/docs/qiskit_ibm_runtime.options.submodule", }, ], - title: "qiskit_ibm_runtime.options", + }, + { + title: "Group 1", + children: [ + { + title: "qiskit_ibm_runtime", + url: "/docs/runtime", + }, + ], }, { title: "Release notes", url: "/api/my-quantum-project/release-notes", }, ], - collapsed: true, - title: "My Quantum Project", }); }); diff --git a/scripts/lib/api/generateToc.ts b/scripts/lib/api/generateToc.ts index 86cb57e2e97..6d21d7a8000 100644 --- a/scripts/lib/api/generateToc.ts +++ b/scripts/lib/api/generateToc.ts @@ -15,6 +15,7 @@ import { Dictionary, isEmpty, keyBy, keys, orderBy } from "lodash"; import { getLastPartFromFullIdentifier } from "../stringUtils"; import { HtmlToMdResultWithUrl } from "./HtmlToMdResult"; import { Pkg } from "./Pkg"; +import type { TocModuleGrouping } from "./TocModuleGrouping"; export type TocEntry = { title: string; @@ -29,12 +30,6 @@ type Toc = { collapsed: boolean; }; -function nestModule(id: string): boolean { - // For example, nest `qiskit.algorithms.submodule`, but - // not `qiskit.algorithms` which should be top-level. - return id.split(".").length > 2; -} - export function generateToc(pkg: Pkg, results: HtmlToMdResultWithUrl[]): Toc { const [modules, items] = getModulesAndItems(results); const tocModules = generateTocModules(modules); @@ -43,10 +38,8 @@ export function generateToc(pkg: Pkg, results: HtmlToMdResultWithUrl[]): Toc { addItemsToModules(items, tocModulesByTitle, tocModuleTitles); - // Most packages don't nest submodules because their module list is so small, - // so it's more useful to show them all and have less nesting. - const sortedTocModules = pkg.nestModulesInToc - ? getNestedTocModulesSorted(tocModules, tocModulesByTitle, tocModuleTitles) + const sortedTocModules = pkg.tocModuleGrouping + ? groupAndSortModules(pkg.tocModuleGrouping, tocModules) : orderEntriesByTitle(tocModules); generateOverviewPage(tocModules); @@ -111,33 +104,34 @@ function addItemsToModules( } } -function getNestedTocModulesSorted( +function groupAndSortModules( + moduleGrouping: TocModuleGrouping, tocModules: TocEntry[], - tocModulesByTitle: Dictionary, - tocModuleTitles: string[], ): TocEntry[] { - const nestedTocModules: TocEntry[] = []; + // Note that Map will preserve the order of sections. + const sectionsToModules = new Map( + moduleGrouping.sections.map((section) => [section, []]), + ); for (const tocModule of tocModules) { - if (!nestModule(tocModule.title)) { - nestedTocModules.push(tocModule); - continue; - } - - const parentModuleTitle = findClosestParentModules( - tocModule.title, - tocModuleTitles, - ); - - if (parentModuleTitle) { - const parentModule = tocModulesByTitle[parentModuleTitle]; - if (!parentModule.children) parentModule.children = []; - parentModule.children.push(tocModule); - } else { - nestedTocModules.push(tocModule); + const section = moduleGrouping.moduleToSection(tocModule.title); + const sectionModules = sectionsToModules.get(section); + if (!sectionModules) { + throw new Error( + `Unexpected section '${section} for the module ${tocModule.title}`, + ); } + sectionModules.push(tocModule); } - return orderEntriesByTitle(nestedTocModules); + const result = []; + for (const [section, modules] of sectionsToModules.entries()) { + if (!modules) continue; + result.push({ + title: section, + children: orderEntriesByTitle(modules), + }); + } + return result; } function generateOverviewPage(tocModules: TocEntry[]): void { diff --git a/scripts/lib/stringUtils.test.ts b/scripts/lib/stringUtils.test.ts index 15a3cd71c87..23d15aa0e78 100644 --- a/scripts/lib/stringUtils.test.ts +++ b/scripts/lib/stringUtils.test.ts @@ -18,6 +18,7 @@ import { removeSuffix, getLastPartFromFullIdentifier, capitalize, + hasPrefix, } from "./stringUtils"; test("removePart()", () => { @@ -55,3 +56,10 @@ test("capitalize()", () => { const input = "hello world!"; expect(capitalize(input)).toEqual("Hello world!"); }); + +test("hasPrefix()", () => { + expect(hasPrefix("abc", ["a", "z"])).toBe(true); + expect(hasPrefix("abc", ["x", "z"])).toBe(false); + expect(hasPrefix("abc", ["x", "a"])).toBe(true); + expect(hasPrefix("abc", [])).toBe(false); +}); diff --git a/scripts/lib/stringUtils.ts b/scripts/lib/stringUtils.ts index d30e9562ed9..b70679e1b02 100644 --- a/scripts/lib/stringUtils.ts +++ b/scripts/lib/stringUtils.ts @@ -42,3 +42,7 @@ export function getLastPartFromFullIdentifier(fullIdentifierName: string) { export function capitalize(text: string) { return text.charAt(0).toUpperCase() + text.slice(1); } + +export function hasPrefix(text: string, prefixes: string[]): boolean { + return prefixes.some((prefix) => text.startsWith(prefix)); +} From e698199e022bbbe1db7a290eddef1a785f5ad927 Mon Sep 17 00:00:00 2001 From: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com> Date: Thu, 25 Apr 2024 17:05:09 -0400 Subject: [PATCH 2/6] Allow specifying modules as top-level entries --- scripts/lib/api/Pkg.ts | 17 +++--- scripts/lib/api/TocGrouping.ts | 90 ++++++++++++++++++++++++++++ scripts/lib/api/TocModuleGrouping.ts | 63 ------------------- scripts/lib/api/generateToc.test.ts | 48 +++++++++------ scripts/lib/api/generateToc.ts | 67 ++++++++++++++------- scripts/lib/stringUtils.test.ts | 1 + 6 files changed, 174 insertions(+), 112 deletions(-) create mode 100644 scripts/lib/api/TocGrouping.ts delete mode 100644 scripts/lib/api/TocModuleGrouping.ts diff --git a/scripts/lib/api/Pkg.ts b/scripts/lib/api/Pkg.ts index 51c91d40993..15643b82621 100644 --- a/scripts/lib/api/Pkg.ts +++ b/scripts/lib/api/Pkg.ts @@ -15,10 +15,7 @@ import { join } from "path/posix"; import { findLegacyReleaseNotes } from "./releaseNotes"; import { getRoot } from "../fs"; import { determineHistoricalQiskitGithubUrl } from "../qiskitMetapackage"; -import { - TocModuleGrouping, - QISKIT_TOC_MODULE_GROUPING, -} from "./TocModuleGrouping"; +import { TocGrouping, QISKIT_TOC_GROUPING } from "./TocGrouping"; export interface ReleaseNoteEntry { title: string; @@ -39,7 +36,7 @@ export class Pkg { readonly versionWithoutPatch: string; readonly type: PackageType; readonly releaseNoteEntries: ReleaseNoteEntry[]; - readonly tocModuleGrouping?: TocModuleGrouping; + readonly tocGrouping?: TocGrouping; static VALID_NAMES = ["qiskit", "qiskit-ibm-runtime", "qiskit-ibm-provider"]; @@ -52,7 +49,7 @@ export class Pkg { versionWithoutPatch: string; type: PackageType; releaseNoteEntries: ReleaseNoteEntry[]; - tocModuleGrouping?: TocModuleGrouping; + tocGrouping?: TocGrouping; }) { this.name = kwargs.name; this.title = kwargs.title; @@ -62,7 +59,7 @@ export class Pkg { this.versionWithoutPatch = kwargs.versionWithoutPatch; this.type = kwargs.type; this.releaseNoteEntries = kwargs.releaseNoteEntries; - this.tocModuleGrouping = kwargs.tocModuleGrouping; + this.tocGrouping = kwargs.tocGrouping; } static async fromArgs( @@ -87,7 +84,7 @@ export class Pkg { githubSlug: "qiskit/qiskit", hasSeparateReleaseNotes: true, releaseNoteEntries, - tocModuleGrouping: QISKIT_TOC_MODULE_GROUPING, + tocGrouping: QISKIT_TOC_GROUPING, }); } @@ -125,7 +122,7 @@ export class Pkg { versionWithoutPatch?: string; type?: PackageType; releaseNoteEntries?: ReleaseNoteEntry[]; - tocModuleGrouping?: TocModuleGrouping; + tocGrouping?: TocGrouping; }): Pkg { return new Pkg({ name: kwargs.name ?? "my-quantum-project", @@ -136,7 +133,7 @@ export class Pkg { versionWithoutPatch: kwargs.versionWithoutPatch ?? "0.1", type: kwargs.type ?? "latest", releaseNoteEntries: kwargs.releaseNoteEntries ?? [], - tocModuleGrouping: kwargs.tocModuleGrouping ?? undefined, + tocGrouping: kwargs.tocGrouping ?? undefined, }); } diff --git a/scripts/lib/api/TocGrouping.ts b/scripts/lib/api/TocGrouping.ts new file mode 100644 index 00000000000..c445d972de3 --- /dev/null +++ b/scripts/lib/api/TocGrouping.ts @@ -0,0 +1,90 @@ +// This code is a Qiskit project. +// +// (C) Copyright IBM 2024. +// +// This code is licensed under the Apache License, Version 2.0. You may +// obtain a copy of this license in the LICENSE file in the root directory +// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. +// +// Any modifications or derivative works of this code must retain this +// copyright notice, and modified files need to carry a notice indicating +// that they have been altered from the originals. + +import { hasPrefix } from "../stringUtils"; + +type Kind = "section" | "module"; +export type TocGroupingEntry = { name: string; kind: Kind }; + +export type TocGrouping = { + // Ordering is respected. + entries: readonly TocGroupingEntry[]; + moduleToSection: (module: string) => string | undefined; +}; + +// Qiskit section names +const _CIRCUITS = "Circuit construction"; +const _TRANSPILATION = "Transpilation"; +const _PRIMITVES = "Primitives and providers"; +const _RESULTS = "Results and visualizations"; +const _QASM = "OpenQASM support"; +const _PULSE = "Pulse-level programming"; +const _OTHER = "Other"; + +// The ordering of Qiskit ToC +const QISKIT_ENTRIES = [ + { name: "qiskit", kind: "module" }, + { name: _CIRCUITS, kind: "section" }, + { name: "qiskit.quantum_info", kind: "module" }, + { name: _TRANSPILATION, kind: "section" }, + { name: _PRIMITVES, kind: "section" }, + { name: _RESULTS, kind: "section" }, + { name: _QASM, kind: "section" }, + { name: _PULSE, kind: "section" }, + { name: _OTHER, kind: "section" }, +] as const; + +export const QISKIT_TOC_GROUPING: TocGrouping = { + entries: QISKIT_ENTRIES, + moduleToSection: (module: string) => { + if (hasPrefix(module, ["qiskit.circuit", "qiskit.quantum_info"])) { + return _CIRCUITS; + } + if ( + hasPrefix(module, [ + "qiskit.dagcircuit", + "qiskit.passmanager", + "qiskit.synthesis", + "qiskit.transpiler", + ]) + ) { + return _TRANSPILATION; + } + if (hasPrefix(module, ["qiskit.primitive", "qiskit.providers"])) { + return _PRIMITVES; + } + if ( + hasPrefix(module, ["qiskit.qpy", "qiskit.result", "qiskit.visualization"]) + ) { + return _RESULTS; + } + if (hasPrefix(module, ["qiskit.qasm"])) { + return _QASM; + } + if (hasPrefix(module, ["qiskit.scheduler", "qiskit.pulse"])) { + return _PULSE; + } + if ( + hasPrefix(module, [ + "qiskit.assembler", + "qiskit.compiler", + "qiskit.converters", + "qiskit.exceptions", + "qiskit.qobj", + "qiskit.utils", + ]) + ) { + return _OTHER; + } + return undefined; + }, +}; diff --git a/scripts/lib/api/TocModuleGrouping.ts b/scripts/lib/api/TocModuleGrouping.ts deleted file mode 100644 index bb1b46a709b..00000000000 --- a/scripts/lib/api/TocModuleGrouping.ts +++ /dev/null @@ -1,63 +0,0 @@ -// This code is a Qiskit project. -// -// (C) Copyright IBM 2024. -// -// This code is licensed under the Apache License, Version 2.0. You may -// obtain a copy of this license in the LICENSE file in the root directory -// of this source tree or at http://www.apache.org/licenses/LICENSE-2.0. -// -// Any modifications or derivative works of this code must retain this -// copyright notice, and modified files need to carry a notice indicating -// that they have been altered from the originals. - -import { hasPrefix } from "../stringUtils"; - -export type TocModuleGrouping = { - sections: readonly string[]; - moduleToSection: (module: string) => string; -}; - -const QISKIT_SECTIONS = [ - "Circuit and Operator Construction", - "Transpilation", - "Primitives and Providers", - "Results and Visualizations", - "OpenQASM Support", - "Pulse-level Programming", - "Other", -] as const; -type QiskitSection = (typeof QISKIT_SECTIONS)[number]; - -export const QISKIT_TOC_MODULE_GROUPING: TocModuleGrouping = { - sections: QISKIT_SECTIONS as readonly string[], - moduleToSection: (module: string): QiskitSection => { - if (hasPrefix(module, ["qiskit.circuit", "qiskit.quantum_info"])) { - return "Circuit and Operator Construction"; - } - if ( - hasPrefix(module, [ - "qiskit.dagcircuit", - "qiskit.passmanager", - "qiskit.synthesis", - "qiskit.transpiler", - ]) - ) { - return "Transpilation"; - } - if (hasPrefix(module, ["qiskit.primitive", "qiskit.providers"])) { - return "Primitives and Providers"; - } - if ( - hasPrefix(module, ["qiskit.qpy", "qiskit.result", "qiskit.visualization"]) - ) { - return "Results and Visualizations"; - } - if (hasPrefix(module, ["qiskit.qasm"])) { - return "OpenQASM Support"; - } - if (hasPrefix(module, ["qiskit.scheduler", "qiskit.pulse"])) { - return "Pulse-level Programming"; - } - return "Other"; - }, -}; diff --git a/scripts/lib/api/generateToc.test.ts b/scripts/lib/api/generateToc.test.ts index 1cfa98e27d8..d3f4441d2cc 100644 --- a/scripts/lib/api/generateToc.test.ts +++ b/scripts/lib/api/generateToc.test.ts @@ -14,6 +14,7 @@ import { describe, expect, test } from "@jest/globals"; import { generateToc } from "./generateToc"; import { Pkg } from "./Pkg"; +import type { TocGroupingEntry } from "./TocGrouping"; const DEFAULT_ARGS = { markdown: "", @@ -122,35 +123,44 @@ describe("generateToc", () => { }); test("TOC with grouped modules", () => { - const tocModuleGrouping = { - // Order is intentionally reversed. - sections: ["Group 2", "Group 1"], + // This ordering is intentional. + const topLevelEntries: TocGroupingEntry[] = [ + { name: "my_project", kind: "module" }, + { name: "Group 2", kind: "section" }, + { name: "Group 1", kind: "section" }, + // Ensure we can handle unused entries. + { name: "unused_module", kind: "module" }, + { name: "Unused section", kind: "section" }, + ]; + const tocGrouping = { + entries: topLevelEntries, moduleToSection: (module: string) => - module == "qiskit_ibm_runtime" ? "Group 1" : "Group 2", + module == "my_project.module" ? "Group 1" : "Group 2", }; - const toc = generateToc(Pkg.mock({ tocModuleGrouping }), [ + + const toc = generateToc(Pkg.mock({ tocGrouping }), [ { meta: { apiType: "module", - apiName: "qiskit_ibm_runtime", + apiName: "my_project", }, - url: "/docs/runtime", + url: "/docs/my_project", ...DEFAULT_ARGS, }, { meta: { apiType: "module", - apiName: "qiskit_ibm_runtime.options", + apiName: "my_project.module", }, - url: "/docs/options", + url: "/docs/my_project.module", ...DEFAULT_ARGS, }, { meta: { apiType: "module", - apiName: "qiskit_ibm_runtime.options.submodule", + apiName: "my_project.module.submodule", }, - url: "/docs/qiskit_ibm_runtime.options.submodule", + url: "/docs/my_project.module.submodule", ...DEFAULT_ARGS, }, ]); @@ -158,16 +168,16 @@ describe("generateToc", () => { collapsed: true, title: "My Quantum Project", children: [ + { + title: "my_project", + url: "/docs/my_project", + }, { title: "Group 2", children: [ { - title: "qiskit_ibm_runtime.options", - url: "/docs/options", - }, - { - title: "qiskit_ibm_runtime.options.submodule", - url: "/docs/qiskit_ibm_runtime.options.submodule", + title: "my_project.module.submodule", + url: "/docs/my_project.module.submodule", }, ], }, @@ -175,8 +185,8 @@ describe("generateToc", () => { title: "Group 1", children: [ { - title: "qiskit_ibm_runtime", - url: "/docs/runtime", + title: "my_project.module", + url: "/docs/my_project.module", }, ], }, diff --git a/scripts/lib/api/generateToc.ts b/scripts/lib/api/generateToc.ts index 6d21d7a8000..9a9b487cd60 100644 --- a/scripts/lib/api/generateToc.ts +++ b/scripts/lib/api/generateToc.ts @@ -15,7 +15,7 @@ import { Dictionary, isEmpty, keyBy, keys, orderBy } from "lodash"; import { getLastPartFromFullIdentifier } from "../stringUtils"; import { HtmlToMdResultWithUrl } from "./HtmlToMdResult"; import { Pkg } from "./Pkg"; -import type { TocModuleGrouping } from "./TocModuleGrouping"; +import type { TocGrouping, TocGroupingEntry } from "./TocGrouping"; export type TocEntry = { title: string; @@ -38,8 +38,8 @@ export function generateToc(pkg: Pkg, results: HtmlToMdResultWithUrl[]): Toc { addItemsToModules(items, tocModulesByTitle, tocModuleTitles); - const sortedTocModules = pkg.tocModuleGrouping - ? groupAndSortModules(pkg.tocModuleGrouping, tocModules) + const sortedTocModules = pkg.tocGrouping + ? groupAndSortModules(pkg.tocGrouping, tocModules, tocModulesByTitle) : orderEntriesByTitle(tocModules); generateOverviewPage(tocModules); @@ -105,32 +105,59 @@ function addItemsToModules( } function groupAndSortModules( - moduleGrouping: TocModuleGrouping, + moduleGrouping: TocGrouping, tocModules: TocEntry[], + tocModulesByTitle: Dictionary, ): TocEntry[] { - // Note that Map will preserve the order of sections. - const sectionsToModules = new Map( - moduleGrouping.sections.map((section) => [section, []]), - ); - for (const tocModule of tocModules) { + const topLevelModules = new Set(); + const sectionsToModules = new Map(); + moduleGrouping.entries.forEach((entry) => { + if (entry.kind === "module") { + topLevelModules.add(entry.name); + } else { + sectionsToModules.set(entry.name, []); + } + }); + + // Go through each module in use and ensure it is either a top-level module + // or assign it to its section. + tocModules.forEach((tocModule) => { + if (topLevelModules.has(tocModule.title)) return; const section = moduleGrouping.moduleToSection(tocModule.title); + if (!section) { + throw new Error( + `Unrecognized module '${tocModule.title}'. It must either be listed as a module in TocGrouping.entries or be matched in TocGrouping.moduleToSection().`, + ); + } const sectionModules = sectionsToModules.get(section); if (!sectionModules) { throw new Error( - `Unexpected section '${section} for the module ${tocModule.title}`, + `Unknown section '${section}' set for the module '${tocModule.title}'. This means TocGrouping.moduleToSection() is not aligned with TocGrouping.entries`, ); } sectionModules.push(tocModule); - } - - const result = []; - for (const [section, modules] of sectionsToModules.entries()) { - if (!modules) continue; - result.push({ - title: section, - children: orderEntriesByTitle(modules), - }); - } + }); + + // Finally, create the ToC by using the ordering from moduleGrouping.entries. + // Note that moduleGrouping.entries might be a superset of the modules/sections + // actually in use for the API version, so we sometimes skip adding individual + // entries to the final result. + const result: TocEntry[] = []; + moduleGrouping.entries.forEach((entry) => { + if (entry.kind === "module") { + const module = tocModulesByTitle[entry.name]; + if (!module) return; + result.push(module); + } else { + const modules = sectionsToModules.get(entry.name); + if (!modules || modules.length === 0) return; + result.push({ + title: entry.name, + // Within a section, sort alphabetically. + children: orderEntriesByTitle(modules), + }); + } + }); return result; } diff --git a/scripts/lib/stringUtils.test.ts b/scripts/lib/stringUtils.test.ts index 23d15aa0e78..7edd616b8d0 100644 --- a/scripts/lib/stringUtils.test.ts +++ b/scripts/lib/stringUtils.test.ts @@ -62,4 +62,5 @@ test("hasPrefix()", () => { expect(hasPrefix("abc", ["x", "z"])).toBe(false); expect(hasPrefix("abc", ["x", "a"])).toBe(true); expect(hasPrefix("abc", [])).toBe(false); + expect(hasPrefix("abc", ["abc"])).toBe(true); }); From 03cf0a35b53974c734b1b9b39dedb297362309ab Mon Sep 17 00:00:00 2001 From: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com> Date: Fri, 26 Apr 2024 17:23:13 -0400 Subject: [PATCH 3/6] Allow customizing the name for top-level modules --- scripts/lib/api/TocGrouping.ts | 20 ++++++++++++++++---- scripts/lib/api/generateToc.test.ts | 6 +++--- scripts/lib/api/generateToc.ts | 9 +++++---- 3 files changed, 24 insertions(+), 11 deletions(-) diff --git a/scripts/lib/api/TocGrouping.ts b/scripts/lib/api/TocGrouping.ts index c445d972de3..5501ddaa126 100644 --- a/scripts/lib/api/TocGrouping.ts +++ b/scripts/lib/api/TocGrouping.ts @@ -12,8 +12,16 @@ import { hasPrefix } from "../stringUtils"; -type Kind = "section" | "module"; -export type TocGroupingEntry = { name: string; kind: Kind }; +type Section = { + name: string; + kind: "section"; +}; +type TopLevelModule = { + moduleId: string; + description: string; + kind: "module"; +}; +export type TocGroupingEntry = Section | TopLevelModule; export type TocGrouping = { // Ordering is respected. @@ -32,9 +40,13 @@ const _OTHER = "Other"; // The ordering of Qiskit ToC const QISKIT_ENTRIES = [ - { name: "qiskit", kind: "module" }, + { moduleId: "qiskit", description: "Module list", kind: "module" }, { name: _CIRCUITS, kind: "section" }, - { name: "qiskit.quantum_info", kind: "module" }, + { + moduleId: "qiskit.quantum_info", + description: "Quantum information (qiskit.quantum_info)", + kind: "module", + }, { name: _TRANSPILATION, kind: "section" }, { name: _PRIMITVES, kind: "section" }, { name: _RESULTS, kind: "section" }, diff --git a/scripts/lib/api/generateToc.test.ts b/scripts/lib/api/generateToc.test.ts index d3f4441d2cc..3ccc3ea0445 100644 --- a/scripts/lib/api/generateToc.test.ts +++ b/scripts/lib/api/generateToc.test.ts @@ -125,11 +125,11 @@ describe("generateToc", () => { test("TOC with grouped modules", () => { // This ordering is intentional. const topLevelEntries: TocGroupingEntry[] = [ - { name: "my_project", kind: "module" }, + { moduleId: "my_project", description: "API index", kind: "module" }, { name: "Group 2", kind: "section" }, { name: "Group 1", kind: "section" }, // Ensure we can handle unused entries. - { name: "unused_module", kind: "module" }, + { moduleId: "unused_module", description: "unused", kind: "module" }, { name: "Unused section", kind: "section" }, ]; const tocGrouping = { @@ -169,7 +169,7 @@ describe("generateToc", () => { title: "My Quantum Project", children: [ { - title: "my_project", + title: "API index", url: "/docs/my_project", }, { diff --git a/scripts/lib/api/generateToc.ts b/scripts/lib/api/generateToc.ts index 9a9b487cd60..4aea3efd5cb 100644 --- a/scripts/lib/api/generateToc.ts +++ b/scripts/lib/api/generateToc.ts @@ -109,11 +109,11 @@ function groupAndSortModules( tocModules: TocEntry[], tocModulesByTitle: Dictionary, ): TocEntry[] { - const topLevelModules = new Set(); + const topLevelModuleIds = new Set(); const sectionsToModules = new Map(); moduleGrouping.entries.forEach((entry) => { if (entry.kind === "module") { - topLevelModules.add(entry.name); + topLevelModuleIds.add(entry.moduleId); } else { sectionsToModules.set(entry.name, []); } @@ -122,7 +122,7 @@ function groupAndSortModules( // Go through each module in use and ensure it is either a top-level module // or assign it to its section. tocModules.forEach((tocModule) => { - if (topLevelModules.has(tocModule.title)) return; + if (topLevelModuleIds.has(tocModule.title)) return; const section = moduleGrouping.moduleToSection(tocModule.title); if (!section) { throw new Error( @@ -145,8 +145,9 @@ function groupAndSortModules( const result: TocEntry[] = []; moduleGrouping.entries.forEach((entry) => { if (entry.kind === "module") { - const module = tocModulesByTitle[entry.name]; + const module = tocModulesByTitle[entry.moduleId]; if (!module) return; + module.title = entry.description; result.push(module); } else { const modules = sectionsToModules.get(entry.name); From 0c08670224dfb80a6a29efd152877fbc0e944387 Mon Sep 17 00:00:00 2001 From: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:05:32 -0400 Subject: [PATCH 4/6] Include earlier modules --- scripts/lib/api/TocGrouping.ts | 50 ++++++++++++++++++++++++++++++++-- 1 file changed, 48 insertions(+), 2 deletions(-) diff --git a/scripts/lib/api/TocGrouping.ts b/scripts/lib/api/TocGrouping.ts index 5501ddaa126..1a43c797c86 100644 --- a/scripts/lib/api/TocGrouping.ts +++ b/scripts/lib/api/TocGrouping.ts @@ -34,9 +34,17 @@ const _CIRCUITS = "Circuit construction"; const _TRANSPILATION = "Transpilation"; const _PRIMITVES = "Primitives and providers"; const _RESULTS = "Results and visualizations"; +const _OPFLOW = "Opflow"; const _QASM = "OpenQASM support"; const _PULSE = "Pulse-level programming"; const _OTHER = "Other"; +const _ALGORITHMS = "Algorithms"; +const _AQUA = "Qiskit Aqua"; +const _IGNIS = "Qiskit Ignis"; +const _FINANCE = "Finance"; +const _CHEMISTRY = "Chemistry"; +const _ML = "Machine learning"; +const _OPTIMIZATION = "Optimization"; // The ordering of Qiskit ToC const QISKIT_ENTRIES = [ @@ -50,15 +58,23 @@ const QISKIT_ENTRIES = [ { name: _TRANSPILATION, kind: "section" }, { name: _PRIMITVES, kind: "section" }, { name: _RESULTS, kind: "section" }, + { name: _OPFLOW, kind: "section" }, { name: _QASM, kind: "section" }, { name: _PULSE, kind: "section" }, { name: _OTHER, kind: "section" }, + { name: _ALGORITHMS, kind: "section" }, + { name: _AQUA, kind: "section" }, + { name: _IGNIS, kind: "section" }, + { name: _FINANCE, kind: "section" }, + { name: _CHEMISTRY, kind: "section" }, + { name: _ML, kind: "section" }, + { name: _OPTIMIZATION, kind: "section" }, ] as const; export const QISKIT_TOC_GROUPING: TocGrouping = { entries: QISKIT_ENTRIES, moduleToSection: (module: string) => { - if (hasPrefix(module, ["qiskit.circuit", "qiskit.quantum_info"])) { + if (hasPrefix(module, ["qiskit.circuit"])) { return _CIRCUITS; } if ( @@ -75,10 +91,18 @@ export const QISKIT_TOC_GROUPING: TocGrouping = { return _PRIMITVES; } if ( - hasPrefix(module, ["qiskit.qpy", "qiskit.result", "qiskit.visualization"]) + hasPrefix(module, [ + "qiskit.qpy", + "qiskit.result", + "qiskit.validation", + "qiskit.visualization", + ]) ) { return _RESULTS; } + if (hasPrefix(module, ["qiskit.opflow"])) { + return _OPFLOW; + } if (hasPrefix(module, ["qiskit.qasm"])) { return _QASM; } @@ -92,11 +116,33 @@ export const QISKIT_TOC_GROUPING: TocGrouping = { "qiskit.converters", "qiskit.exceptions", "qiskit.qobj", + "qiskit.tools", "qiskit.utils", ]) ) { return _OTHER; } + if (hasPrefix(module, ["qiskit.algorithms"])) { + return _ALGORITHMS; + } + if (hasPrefix(module, ["qiskit.aqua"])) { + return _AQUA; + } + if (hasPrefix(module, ["qiskit.ignis"])) { + return _IGNIS; + } + if (hasPrefix(module, ["qiskit.finance"])) { + return _FINANCE; + } + if (hasPrefix(module, ["qiskit.chemistry"])) { + return _CHEMISTRY; + } + if (hasPrefix(module, ["qiskit.ml"])) { + return _ML; + } + if (hasPrefix(module, ["qiskit.optimization"])) { + return _OPTIMIZATION; + } return undefined; }, }; From f28fd15681621c361d76f12e0ec627be53598b8d Mon Sep 17 00:00:00 2001 From: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com> Date: Mon, 29 Apr 2024 16:19:08 -0400 Subject: [PATCH 5/6] Add mechanism to group API ToC into sections --- scripts/lib/api/Pkg.ts | 9 ++- scripts/lib/api/TocGrouping.ts | 118 ---------------------------- scripts/lib/api/generateToc.test.ts | 56 +++++++++++++ scripts/lib/api/generateToc.ts | 54 ++++++++++++- 4 files changed, 114 insertions(+), 123 deletions(-) diff --git a/scripts/lib/api/Pkg.ts b/scripts/lib/api/Pkg.ts index 15643b82621..2640348b978 100644 --- a/scripts/lib/api/Pkg.ts +++ b/scripts/lib/api/Pkg.ts @@ -15,7 +15,7 @@ import { join } from "path/posix"; import { findLegacyReleaseNotes } from "./releaseNotes"; import { getRoot } from "../fs"; import { determineHistoricalQiskitGithubUrl } from "../qiskitMetapackage"; -import { TocGrouping, QISKIT_TOC_GROUPING } from "./TocGrouping"; +import { TocGrouping } from "./TocGrouping"; export interface ReleaseNoteEntry { title: string; @@ -36,6 +36,7 @@ export class Pkg { readonly versionWithoutPatch: string; readonly type: PackageType; readonly releaseNoteEntries: ReleaseNoteEntry[]; + readonly nestModulesInToc: boolean; readonly tocGrouping?: TocGrouping; static VALID_NAMES = ["qiskit", "qiskit-ibm-runtime", "qiskit-ibm-provider"]; @@ -49,6 +50,7 @@ export class Pkg { versionWithoutPatch: string; type: PackageType; releaseNoteEntries: ReleaseNoteEntry[]; + nestModulesInToc?: boolean; tocGrouping?: TocGrouping; }) { this.name = kwargs.name; @@ -59,6 +61,7 @@ export class Pkg { this.versionWithoutPatch = kwargs.versionWithoutPatch; this.type = kwargs.type; this.releaseNoteEntries = kwargs.releaseNoteEntries; + this.nestModulesInToc = kwargs.nestModulesInToc ?? false; this.tocGrouping = kwargs.tocGrouping; } @@ -84,7 +87,7 @@ export class Pkg { githubSlug: "qiskit/qiskit", hasSeparateReleaseNotes: true, releaseNoteEntries, - tocGrouping: QISKIT_TOC_GROUPING, + nestModulesInToc: true, }); } @@ -122,6 +125,7 @@ export class Pkg { versionWithoutPatch?: string; type?: PackageType; releaseNoteEntries?: ReleaseNoteEntry[]; + nestModulesInToc?: boolean; tocGrouping?: TocGrouping; }): Pkg { return new Pkg({ @@ -133,6 +137,7 @@ export class Pkg { versionWithoutPatch: kwargs.versionWithoutPatch ?? "0.1", type: kwargs.type ?? "latest", releaseNoteEntries: kwargs.releaseNoteEntries ?? [], + nestModulesInToc: kwargs.nestModulesInToc ?? false, tocGrouping: kwargs.tocGrouping ?? undefined, }); } diff --git a/scripts/lib/api/TocGrouping.ts b/scripts/lib/api/TocGrouping.ts index 1a43c797c86..1e1270d999f 100644 --- a/scripts/lib/api/TocGrouping.ts +++ b/scripts/lib/api/TocGrouping.ts @@ -28,121 +28,3 @@ export type TocGrouping = { entries: readonly TocGroupingEntry[]; moduleToSection: (module: string) => string | undefined; }; - -// Qiskit section names -const _CIRCUITS = "Circuit construction"; -const _TRANSPILATION = "Transpilation"; -const _PRIMITVES = "Primitives and providers"; -const _RESULTS = "Results and visualizations"; -const _OPFLOW = "Opflow"; -const _QASM = "OpenQASM support"; -const _PULSE = "Pulse-level programming"; -const _OTHER = "Other"; -const _ALGORITHMS = "Algorithms"; -const _AQUA = "Qiskit Aqua"; -const _IGNIS = "Qiskit Ignis"; -const _FINANCE = "Finance"; -const _CHEMISTRY = "Chemistry"; -const _ML = "Machine learning"; -const _OPTIMIZATION = "Optimization"; - -// The ordering of Qiskit ToC -const QISKIT_ENTRIES = [ - { moduleId: "qiskit", description: "Module list", kind: "module" }, - { name: _CIRCUITS, kind: "section" }, - { - moduleId: "qiskit.quantum_info", - description: "Quantum information (qiskit.quantum_info)", - kind: "module", - }, - { name: _TRANSPILATION, kind: "section" }, - { name: _PRIMITVES, kind: "section" }, - { name: _RESULTS, kind: "section" }, - { name: _OPFLOW, kind: "section" }, - { name: _QASM, kind: "section" }, - { name: _PULSE, kind: "section" }, - { name: _OTHER, kind: "section" }, - { name: _ALGORITHMS, kind: "section" }, - { name: _AQUA, kind: "section" }, - { name: _IGNIS, kind: "section" }, - { name: _FINANCE, kind: "section" }, - { name: _CHEMISTRY, kind: "section" }, - { name: _ML, kind: "section" }, - { name: _OPTIMIZATION, kind: "section" }, -] as const; - -export const QISKIT_TOC_GROUPING: TocGrouping = { - entries: QISKIT_ENTRIES, - moduleToSection: (module: string) => { - if (hasPrefix(module, ["qiskit.circuit"])) { - return _CIRCUITS; - } - if ( - hasPrefix(module, [ - "qiskit.dagcircuit", - "qiskit.passmanager", - "qiskit.synthesis", - "qiskit.transpiler", - ]) - ) { - return _TRANSPILATION; - } - if (hasPrefix(module, ["qiskit.primitive", "qiskit.providers"])) { - return _PRIMITVES; - } - if ( - hasPrefix(module, [ - "qiskit.qpy", - "qiskit.result", - "qiskit.validation", - "qiskit.visualization", - ]) - ) { - return _RESULTS; - } - if (hasPrefix(module, ["qiskit.opflow"])) { - return _OPFLOW; - } - if (hasPrefix(module, ["qiskit.qasm"])) { - return _QASM; - } - if (hasPrefix(module, ["qiskit.scheduler", "qiskit.pulse"])) { - return _PULSE; - } - if ( - hasPrefix(module, [ - "qiskit.assembler", - "qiskit.compiler", - "qiskit.converters", - "qiskit.exceptions", - "qiskit.qobj", - "qiskit.tools", - "qiskit.utils", - ]) - ) { - return _OTHER; - } - if (hasPrefix(module, ["qiskit.algorithms"])) { - return _ALGORITHMS; - } - if (hasPrefix(module, ["qiskit.aqua"])) { - return _AQUA; - } - if (hasPrefix(module, ["qiskit.ignis"])) { - return _IGNIS; - } - if (hasPrefix(module, ["qiskit.finance"])) { - return _FINANCE; - } - if (hasPrefix(module, ["qiskit.chemistry"])) { - return _CHEMISTRY; - } - if (hasPrefix(module, ["qiskit.ml"])) { - return _ML; - } - if (hasPrefix(module, ["qiskit.optimization"])) { - return _OPTIMIZATION; - } - return undefined; - }, -}; diff --git a/scripts/lib/api/generateToc.test.ts b/scripts/lib/api/generateToc.test.ts index 3ccc3ea0445..db3e7aced7c 100644 --- a/scripts/lib/api/generateToc.test.ts +++ b/scripts/lib/api/generateToc.test.ts @@ -122,6 +122,62 @@ describe("generateToc", () => { `); }); + test("TOC with nested submodules", () => { + const toc = generateToc(Pkg.mock({ nestModulesInToc: true }), [ + { + meta: { + apiType: "module", + apiName: "qiskit_ibm_runtime", + }, + url: "/docs/runtime", + ...DEFAULT_ARGS, + }, + { + meta: { + apiType: "module", + apiName: "qiskit_ibm_runtime.options", + }, + url: "/docs/options", + ...DEFAULT_ARGS, + }, + { + meta: { + apiType: "module", + apiName: "qiskit_ibm_runtime.options.submodule", + }, + url: "/docs/qiskit_ibm_runtime.options.submodule", + ...DEFAULT_ARGS, + }, + ]); + expect(toc).toEqual({ + children: [ + { + title: "qiskit_ibm_runtime", + url: "/docs/runtime", + }, + { + children: [ + { + title: "Module overview", + url: "/docs/options", + }, + { + title: "qiskit_ibm_runtime.options.submodule", + url: "/docs/qiskit_ibm_runtime.options.submodule", + }, + ], + title: "qiskit_ibm_runtime.options", + }, + { + title: "Release notes", + url: "/api/my-quantum-project/release-notes", + }, + ], + collapsed: true, + title: "My Quantum Project", + }); + }); + test("TOC with grouped modules", () => { // This ordering is intentional. const topLevelEntries: TocGroupingEntry[] = [ diff --git a/scripts/lib/api/generateToc.ts b/scripts/lib/api/generateToc.ts index 4aea3efd5cb..aa5ee4014bf 100644 --- a/scripts/lib/api/generateToc.ts +++ b/scripts/lib/api/generateToc.ts @@ -30,6 +30,12 @@ type Toc = { collapsed: boolean; }; +function nestModule(id: string): boolean { + // For example, nest `qiskit.algorithms.submodule`, but + // not `qiskit.algorithms` which should be top-level. + return id.split(".").length > 2; +} + export function generateToc(pkg: Pkg, results: HtmlToMdResultWithUrl[]): Toc { const [modules, items] = getModulesAndItems(results); const tocModules = generateTocModules(modules); @@ -38,9 +44,22 @@ export function generateToc(pkg: Pkg, results: HtmlToMdResultWithUrl[]): Toc { addItemsToModules(items, tocModulesByTitle, tocModuleTitles); - const sortedTocModules = pkg.tocGrouping - ? groupAndSortModules(pkg.tocGrouping, tocModules, tocModulesByTitle) - : orderEntriesByTitle(tocModules); + let sortedTocModules; + if (pkg.tocGrouping) { + sortedTocModules = groupAndSortModules( + pkg.tocGrouping, + tocModules, + tocModulesByTitle, + ); + } else if (pkg.nestModulesInToc) { + sortedTocModules = getNestedTocModulesSorted( + tocModules, + tocModulesByTitle, + tocModuleTitles, + ); + } else { + sortedTocModules = orderEntriesByTitle(tocModules); + } generateOverviewPage(tocModules); return { @@ -162,6 +181,35 @@ function groupAndSortModules( return result; } +function getNestedTocModulesSorted( + tocModules: TocEntry[], + tocModulesByTitle: Dictionary, + tocModuleTitles: string[], +): TocEntry[] { + const nestedTocModules: TocEntry[] = []; + for (const tocModule of tocModules) { + if (!nestModule(tocModule.title)) { + nestedTocModules.push(tocModule); + continue; + } + + const parentModuleTitle = findClosestParentModules( + tocModule.title, + tocModuleTitles, + ); + + if (parentModuleTitle) { + const parentModule = tocModulesByTitle[parentModuleTitle]; + if (!parentModule.children) parentModule.children = []; + parentModule.children.push(tocModule); + } else { + nestedTocModules.push(tocModule); + } + } + + return orderEntriesByTitle(nestedTocModules); +} + function generateOverviewPage(tocModules: TocEntry[]): void { for (const tocModule of tocModules) { if (tocModule.children && tocModule.children.length > 0) { From 90b66041c7da0b7b458f1cb5205092f104673b16 Mon Sep 17 00:00:00 2001 From: Eric Arellano <14852634+Eric-Arellano@users.noreply.github.com> Date: Tue, 30 Apr 2024 12:11:32 -0400 Subject: [PATCH 6/6] Review feedback: add docstring & rename field --- scripts/lib/api/TocGrouping.ts | 18 ++++++++++++++---- scripts/lib/api/generateToc.test.ts | 4 ++-- scripts/lib/api/generateToc.ts | 25 ++++++++++++++++++++----- 3 files changed, 36 insertions(+), 11 deletions(-) diff --git a/scripts/lib/api/TocGrouping.ts b/scripts/lib/api/TocGrouping.ts index 1e1270d999f..29cd8c239f1 100644 --- a/scripts/lib/api/TocGrouping.ts +++ b/scripts/lib/api/TocGrouping.ts @@ -10,21 +10,31 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -import { hasPrefix } from "../stringUtils"; - +/** A human-written section used to group several modules, e.g. 'Circuit Construction'. */ type Section = { name: string; kind: "section"; }; + +/** A single module that should be shown as top-level in the left ToC. */ type TopLevelModule = { + /** E.g. 'qiskit.quantum_info'. */ moduleId: string; - description: string; + /** The title the left ToC should use. This can be the module name itself, but it's often helpful + * to give a custom name like 'Quantum information (qiskit.quantum_info)'. */ + title: string; kind: "module"; }; + export type TocGroupingEntry = Section | TopLevelModule; +/** A custom grouping for the top-level of the left ToC. */ export type TocGrouping = { - // Ordering is respected. + /** The top-level entries in the left ToC, made up of Sections like 'Circuit construction' and + * top-level modules. Ordering is respected. */ entries: readonly TocGroupingEntry[]; + /** A function to associate an arbitrary module like 'qiskit.circuit' to the corresponding Section from + * `TocGrouping.entries`. The returned string must match a `Section.name` from `TocGrouping.entries`. + * Return `undefined` if the module does not belong in any specific Section.*/ moduleToSection: (module: string) => string | undefined; }; diff --git a/scripts/lib/api/generateToc.test.ts b/scripts/lib/api/generateToc.test.ts index db8fd8300c6..cc564a12c02 100644 --- a/scripts/lib/api/generateToc.test.ts +++ b/scripts/lib/api/generateToc.test.ts @@ -181,11 +181,11 @@ describe("generateToc", () => { test("TOC with grouped modules", () => { // This ordering is intentional. const topLevelEntries: TocGroupingEntry[] = [ - { moduleId: "my_project", description: "API index", kind: "module" }, + { moduleId: "my_project", title: "API index", kind: "module" }, { name: "Group 2", kind: "section" }, { name: "Group 1", kind: "section" }, // Ensure we can handle unused entries. - { moduleId: "unused_module", description: "unused", kind: "module" }, + { moduleId: "unused_module", title: "unused", kind: "module" }, { name: "Unused section", kind: "section" }, ]; const tocGrouping = { diff --git a/scripts/lib/api/generateToc.ts b/scripts/lib/api/generateToc.ts index 16a5923aa58..26be12f6ff8 100644 --- a/scripts/lib/api/generateToc.ts +++ b/scripts/lib/api/generateToc.ts @@ -123,14 +123,20 @@ function addItemsToModules( } } +/** Group the modules into the user-defined tocGrouping, which defines the order + * of the top-level entries in the ToC. + * + * Within each TocGrouping.Section, this function will also sort the modules alphabetically. + */ function groupAndSortModules( - moduleGrouping: TocGrouping, + tocGrouping: TocGrouping, tocModules: TocEntry[], tocModulesByTitle: Dictionary, ): TocEntry[] { + // First, record every valid section and top-level module. const topLevelModuleIds = new Set(); const sectionsToModules = new Map(); - moduleGrouping.entries.forEach((entry) => { + tocGrouping.entries.forEach((entry) => { if (entry.kind === "module") { topLevelModuleIds.add(entry.moduleId); } else { @@ -142,7 +148,7 @@ function groupAndSortModules( // or assign it to its section. tocModules.forEach((tocModule) => { if (topLevelModuleIds.has(tocModule.title)) return; - const section = moduleGrouping.moduleToSection(tocModule.title); + const section = tocGrouping.moduleToSection(tocModule.title); if (!section) { throw new Error( `Unrecognized module '${tocModule.title}'. It must either be listed as a module in TocGrouping.entries or be matched in TocGrouping.moduleToSection().`, @@ -162,11 +168,11 @@ function groupAndSortModules( // actually in use for the API version, so we sometimes skip adding individual // entries to the final result. const result: TocEntry[] = []; - moduleGrouping.entries.forEach((entry) => { + tocGrouping.entries.forEach((entry) => { if (entry.kind === "module") { const module = tocModulesByTitle[entry.moduleId]; if (!module) return; - module.title = entry.description; + module.title = entry.title; result.push(module); } else { const modules = sectionsToModules.get(entry.name); @@ -181,6 +187,11 @@ function groupAndSortModules( return result; } +/** Nest modules so that only top-level modules like qiskit.circuit are at the top + * and submodules like qiskit.circuit.library are nested. + * + * This function sorts alphabetically at every level. + */ function getNestedTocModulesSorted( tocModules: TocEntry[], tocModulesByTitle: Dictionary, @@ -210,6 +221,10 @@ function getNestedTocModulesSorted( return orderEntriesByTitle(nestedTocModules); } +/** Sorts all modules and truncates the package name, e.g. `qiskit_ibm_runtime.options` -> `...options`. + * + * Returns a flat list of modules without any nesting. + */ function sortAndTruncateModules(entries: TocEntry[]): TocEntry[] { const sorted = orderEntriesByTitle(entries); sorted.forEach((entry) => {