Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add objects.inv #522

Merged
merged 51 commits into from
Jan 17, 2024
Merged
Show file tree
Hide file tree
Changes from 31 commits
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
3dbbb7b
Squash commits
frankharkins Dec 14, 2023
217c706
Improve download logic
frankharkins Dec 22, 2023
2cddffc
Continue
frankharkins Dec 29, 2023
7c204c2
Merge branch 'main' of https://github.com/Qiskit/documentation into F…
frankharkins Dec 29, 2023
fbcbc93
lint :sparkles:
frankharkins Dec 29, 2023
4d6bd50
checkpoint
frankharkins Jan 2, 2024
f609020
Merge branch 'main' of https://github.com/Qiskit/documentation into F…
frankharkins Jan 2, 2024
a35e55a
Checkpoint
frankharkins Jan 2, 2024
eb6d46d
Re-fix merge conflict
frankharkins Jan 2, 2024
87f15e1
Fix and neaten tests
frankharkins Jan 3, 2024
7d90e7e
Remove tutorials from objects.inv
frankharkins Jan 3, 2024
da3a2f8
Remove console log
frankharkins Jan 3, 2024
490cd4b
Pull main
frankharkins Jan 10, 2024
aac8ca8
Fix tests
frankharkins Jan 10, 2024
8ccd12d
Handle special cases in objects.inv
frankharkins Jan 10, 2024
ccc5456
Add objects.inv files
frankharkins Jan 10, 2024
0e88fbf
pull main
frankharkins Jan 10, 2024
f1ad63d
Remove unneeded compress/expand (happens on read/write)
frankharkins Jan 10, 2024
f3da368
Fix relative filepaths
frankharkins Jan 10, 2024
8dbb25a
Ignore files from objects.inv
frankharkins Jan 10, 2024
a646ffe
Regen objects.inv
frankharkins Jan 10, 2024
ee58482
Disable link-checking in objects.inv
frankharkins Jan 11, 2024
b953189
Ignore more files
frankharkins Jan 11, 2024
fda5840
Convert anchors to lower case
frankharkins Jan 11, 2024
869ec07
Regen objects.inv
frankharkins Jan 11, 2024
994f9ed
Update snapshots
frankharkins Jan 11, 2024
3014893
Bugfixes
frankharkins Jan 12, 2024
b189efd
Move objects.inv files to /public
frankharkins Jan 12, 2024
ffef5fb
Rename 'markdown.ts' -> 'extractLinks.ts'
frankharkins Jan 12, 2024
c28c20a
Disable link checking
frankharkins Jan 12, 2024
1c8fc2c
Remove release notes and legacy release notes
frankharkins Jan 12, 2024
10f2c15
Apply suggestions from code review
frankharkins Jan 15, 2024
ada7bd4
Merge branch 'main' of https://github.com/Qiskit/documentation into F…
frankharkins Jan 15, 2024
6c66a6e
Merge branch 'FH/objects.inv' of https://github.com/Qiskit/documentat…
frankharkins Jan 15, 2024
acee94e
Feedback: allow passing directory only
frankharkins Jan 15, 2024
79de476
Feedback: Function naming
frankharkins Jan 15, 2024
5f79584
Feedback: Do not allow links to .inv files
frankharkins Jan 15, 2024
0131c9d
Feedback: Improve comment
frankharkins Jan 15, 2024
5078141
Update tests
frankharkins Jan 15, 2024
0698d16
Rename folder
frankharkins Jan 15, 2024
12ceb68
Use ignore.ts mechanism to ignore broken links
frankharkins Jan 15, 2024
0988f2c
Merge branch 'main' of https://github.com/Qiskit/documentation into F…
frankharkins Jan 15, 2024
81b0c17
Remove code duplication
frankharkins Jan 15, 2024
8ab0bab
Apply suggestions from code review
frankharkins Jan 17, 2024
860e191
Review suggestion: Always write to objects.inv
frankharkins Jan 17, 2024
21b7582
Review suggestion: _ => #
frankharkins Jan 17, 2024
ac85d66
Review suggestion: toMatchInlineSnapshot -> toEqual
frankharkins Jan 17, 2024
6f6bcd4
Fix link checker
frankharkins Jan 17, 2024
b47fd42
Merge branch 'main' of https://github.com/Qiskit/documentation into F…
frankharkins Jan 17, 2024
08596b6
Merge branch 'main' of https://github.com/Qiskit/documentation into F…
frankharkins Jan 17, 2024
7315df4
Update scripts/lib/api/objectsInv.ts
frankharkins Jan 17, 2024
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
Binary file added public/api/qiskit-ibm-provider/objects.inv
Binary file not shown.
Binary file added public/api/qiskit-ibm-runtime/objects.inv
Binary file not shown.
Binary file added public/api/qiskit/objects.inv
Binary file not shown.
11 changes: 9 additions & 2 deletions scripts/commands/checkLinks.ts
Original file line number Diff line number Diff line change
Expand Up @@ -119,8 +119,10 @@ async function determineCurrentDocsFileBatch(
): Promise<FileBatch> {
const toCheck = [
"docs/**/*.{ipynb,md,mdx}",
"public/api/**/objects.inv",
frankharkins marked this conversation as resolved.
Show resolved Hide resolved
// Ignore historical versions
"!docs/api/{qiskit,qiskit-ibm-provider,qiskit-ibm-runtime}/[0-9]*/*",
"!public/api/{qiskit,qiskit-ibm-provider,qiskit-ibm-runtime}/[0-9]*/*",
];
const toLoad = [
"docs/api/qiskit/0.44/{algorithms,opflow}.md",
Expand All @@ -129,7 +131,9 @@ async function determineCurrentDocsFileBatch(
];

if (!args.currentApis) {
toCheck.push("!docs/api/{qiskit,qiskit-ibm-provider,qiskit-ibm-runtime}/*");
toCheck.push(
"!{public,docs}/api/{qiskit,qiskit-ibm-provider,qiskit-ibm-runtime}/*",
);
toLoad.push("docs/api/{qiskit,qiskit-ibm-provider,qiskit-ibm-runtime}/*");
}

Expand Down Expand Up @@ -163,7 +167,10 @@ async function determineHistoricalFileBatches(
const result = [];
for (const folder of historicalFolders) {
const fileBatch = await FileBatch.fromGlobs(
[`docs/api/${projectName}/${folder.name}/*`],
[
`docs/api/${projectName}/${folder.name}/*`,
`public/api/${projectName}/${folder.name}/objects.inv`,
frankharkins marked this conversation as resolved.
Show resolved Hide resolved
],
toLoad,
`${projectName} v${folder.name}`,
);
Expand Down
10 changes: 8 additions & 2 deletions scripts/commands/updateApiDocs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import { mkdirp } from "mkdirp";
import yargs from "yargs/yargs";
import { hideBin } from "yargs/helpers";
import transformLinks from "transform-markdown-links";

import { ObjectsInv } from "../lib/api/objectsInv";
frankharkins marked this conversation as resolved.
Show resolved Hide resolved
import { sphinxHtmlToMarkdown } from "../lib/api/htmlToMd";
import { saveImages } from "../lib/api/saveImages";
import { generateToc } from "../lib/api/generateToc";
Expand Down Expand Up @@ -213,6 +213,7 @@ async function convertHtmlToMarkdown(
baseSourceUrl: string,
pkg: Pkg,
) {
const objectsInv = await ObjectsInv.fromFile(join(htmlPath, "objects.inv"));
Copy link
Collaborator

Choose a reason for hiding this comment

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

Nit: you could assume that the file is always called objects.inv, so do ObjectsInv.fromFile(htmlPath) where the arg is called parentDirectoryPath

Copy link
Member Author

Choose a reason for hiding this comment

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

Thanks, incorporated in acee94e

const files = await globby(
[
"apidocs/**.html",
Expand Down Expand Up @@ -276,10 +277,15 @@ async function convertHtmlToMarkdown(
results = await mergeClassMembers(results);
flattenFolders(results);
specialCaseResults(results);
await updateLinks(results, pkg.transformLink);
await updateLinks(results, objectsInv, pkg.transformLink);
await dedupeHtmlIdsFromResults(results);
addFrontMatter(results, pkg);

const objectsInvDestination = pkg.historical
? `public/api/${pkg.name}/${pkg.versionWithoutPatch}`
: `public/api/${pkg.name}`;
Copy link
Member Author

Choose a reason for hiding this comment

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

This logic appears in a few different places, might be nice to consolidate in a future refactor.

Copy link
Collaborator

Choose a reason for hiding this comment

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

Maybe make it a static function: ObjectsInv.determineDest(pkg: Pkg)?

Copy link
Collaborator

Choose a reason for hiding this comment

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

Could also have .write() call this and mkdirp. Have .write() take the arg pkg: Pkg.

Copy link
Member Author

@frankharkins frankharkins Jan 15, 2024

Choose a reason for hiding this comment

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

I don't want ObjectsInv to need to know about Pkg, partially for code decoupling but also to make it easier to write the temp file while testing.

I think #630 is a good compromise. I'll pull in those changes if it's merged. Then this can just be

Suggested change
const objectsInvDestination = pkg.historical
? `public/api/${pkg.name}/${pkg.versionWithoutPatch}`
: `public/api/${pkg.name}`;
const objectsInvDestination = getPkgRoot(pkg, "public");

Could also have .write() call this and mkdirp.

incorporated the acee94e, thanks!

Copy link
Member Author

Choose a reason for hiding this comment

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

See 81b0c17

await mkdirp(join(getRoot(), objectsInvDestination));
await objectsInv.write(join(getRoot(), objectsInvDestination, "objects.inv"));
for (const result of results) {
let path = urlToPath(result.url);

Expand Down
126 changes: 126 additions & 0 deletions scripts/lib/api/objectsInv.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,126 @@
// This code is a Qiskit project.
//
// (C) Copyright IBM 2023.
//
// 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 { describe, expect, test } from "@jest/globals";
import { ObjectsInv, ObjectsInvEntry } from "./objectsInv";
import { unlink, stat } from "fs/promises";

const TEST_FILE = "scripts/lib/api/test/objects.inv";
const TEMP_FILE = TEST_FILE + ".written";

describe("objects.inv", () => {
test("read file and decompress", async () => {
const objectsInv = await ObjectsInv.fromFile(TEST_FILE);

expect(objectsInv.preamble).toMatch(
"# Sphinx inventory version 2\n" +
"# Project: Qiskit\n" +
"# Version: 0.45\n" +
"# The remainder of this file is compressed using zlib.\n",
);

const uriIndices = [10, 88, 107, 1419, 24599];
expect(uriIndices.map((i) => objectsInv.entries[i].uri))
.toMatchInlineSnapshot(`
[
"stubs/qiskit.algorithms.AlgorithmJob.html#qiskit.algorithms.AlgorithmJob.job_id",
"stubs/qiskit.algorithms.FasterAmplitudeEstimation.html#qiskit.algorithms.FasterAmplitudeEstimation.sampler",
"stubs/qiskit.algorithms.Grover.html#qiskit.algorithms.Grover.quantum_instance",
"apidoc/assembler.html#qiskit.assembler.disassemble",
"index.html",
]
`);
const nameIndices = [24599, 25170];
expect(nameIndices.map((i) => objectsInv.entries[i].dispname))
.toMatchInlineSnapshot(`
[
"Qiskit 0.45 documentation",
"Circuits Deprecations",
]
`);
});

test("write file and re-read matches original", async () => {
const originalObjectsInv = await ObjectsInv.fromFile(TEST_FILE);
await originalObjectsInv.write(TEMP_FILE);

const newObjectsInv = await ObjectsInv.fromFile(TEMP_FILE);
expect(originalObjectsInv.entries.length).toEqual(
newObjectsInv.entries.length,
);
expect(originalObjectsInv.preamble).toMatch(newObjectsInv.preamble);
expect(originalObjectsInv.entriesString()).toMatch(
newObjectsInv.entriesString(),
);
});

test("URI transform works correctly", () => {
const preamble = `# Simple preamble\n`;
// Use nonsense transform function to check things are actually changing
const transformFunction = (x: string) => x.replaceAll("i", "a");
const entries: ObjectsInvEntry[] = [
{
name: "qiskit_ibm_runtime.RuntimeJob.job_id",
domainAndRole: "py:method",
priority: "1",
uri: "qiskit_ibm_runtime.RuntimeJob#qiskit_ibm_runtime.RuntimeJob.job_id",
dispname: "-",
},
{
name: "stubs/qiskit_ibm_provider.transpiler.passes.scheduling.ASAPScheduleAnalysis.__call__",
domainAndRole: "std:doc",
priority: "-1",
uri: "stubs/qiskit_ibm_provider.transpiler.passes.scheduling.ASAPScheduleAnalysis.__call__.html",
dispname: "ASAPScheduleAnalysis.__call__",
},
{
name: "search",
domainAndRole: "std:label",
priority: "-1",
uri: "search.html",
dispname: "Search Page",
},
{
name: "release notes_ignis_0.5.0",
domainAndRole: "std:label",
priority: "-1",
uri: "legacy_release_notes.html#release-notes-ignis-0-5-0",
dispname: "Ignis 0.5.0",
},
{
name: "index",
domainAndRole: "std:doc",
priority: "-1",
uri: "index.html",
dispname: "Qiskit IBM Quantum Provider API docs preview",
},
];

const objectsInv = new ObjectsInv(preamble, entries);
objectsInv.updateUris(transformFunction);
expect(objectsInv.entries.map((i) => i.uri)).toMatchInlineSnapshot(`
[
"qaskat_abm_runtame.RuntameJob#qaskat_abm_runtame.RuntameJob.job_ad",
"stubs/qaskat_abm_provader.transpaler.passes.schedulang.ASAPScheduleAnalysas.__call__",
"search",
"legacy_release_notes#release-notes-agnas-0-5-0",
"andex",
]
`);
});

afterAll(async () => {
if (await stat(TEMP_FILE)) {
await unlink(TEMP_FILE);
}
});
});
170 changes: 170 additions & 0 deletions scripts/lib/api/objectsInv.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,170 @@
// This code is a Qiskit project.
//
// (C) Copyright IBM 2023.
frankharkins marked this conversation as resolved.
Show resolved Hide resolved
//
// 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 { readFile, writeFile } from "fs/promises";
import { unzipSync, deflateSync } from "zlib";
import { removeSuffix } from "../stringUtils";

/**
* Some pages exist in the sphinx docs but not in our docs
* If any URIs match these cases, we remove their entries.
**/
const ENTRIES_TO_EXCLUDE = [
frankharkins marked this conversation as resolved.
Show resolved Hide resolved
/^genindex(\.html)?$/,
/^py-modindex(\.html)?$/,
/^search(\.html)?$/,
/^explanation(\.html)?(?=\/|#|$)/,
/^how_to(\.html)?(?=\/|#|$)/,
/^tutorials(\.html)?(?=\/|#|$)/,
/^migration_guides(\.html)?(?=\/|#|$)/,
/^configuration(\.html)?(?=#|$)/,
/^contributing_to_qiskit(\.html)?(?=#|$)/,
/^deprecation_policy(\.html)?(?=#|$)/,
/^faq(\.html)?(?=#|$)/,
/^getting_started(\.html)?(?=#|$)/,
/^intro_tutorial1(\.html)?(?=#|$)/,
/^maintainers_guide(\.html)?(?=#|$)/,
/^qc_intro(\.html)?(?=#|$)/,
/^release[-_]notes(\.html)?(?=#|$)/,
/^legacy_release_notes(\.html)?(?=#|$)/,
];

function shouldExcludePage(uri: string): boolean {
for (let condition of ENTRIES_TO_EXCLUDE) {
if (uri.match(condition)) {
return true;
}
}
return false;
frankharkins marked this conversation as resolved.
Show resolved Hide resolved
}

export type ObjectsInvEntry = {
name: string;
domainAndRole: string;
priority: string;
uri: string;
dispname: string;
};

/**
* Class to hold and operate on data from Sphinx's objects.inv file.
* For information on the syntax, see:
* https://sphobjinv.readthedocs.io/en/stable/syntax.html
*/
export class ObjectsInv {
preamble: string;
entries: ObjectsInvEntry[];
constructor(preamble: string, entries: ObjectsInvEntry[]) {
frankharkins marked this conversation as resolved.
Show resolved Hide resolved
this.preamble = preamble;
this.entries = entries;
}

/**
* Decompress Sphinx's objects.inv.
* This function follows the process from:
* https://github.com/bskinn/sphobjinv/blob/stable/src/sphobjinv/zlib.py
*/
static async fromFile(path: string): Promise<ObjectsInv> {
let buffer = await readFile(path);
// Extract preamble (first 4 lines of file)
let preamble = "";
for (let line = 0; line < 4; line++) {
let nextNewline = buffer.indexOf(10) + 1;
preamble += buffer.toString("utf8", 0, nextNewline);
buffer = buffer.subarray(nextNewline);
}

// Decompress the rest
const lines = unzipSync(buffer).toString("utf8").trim().split("\n");

// Sort the strings into their parts
const entries: ObjectsInvEntry[] = [];
for (const line of lines) {
// Regex from sphinx source
// https://github.com/sphinx-doc/sphinx/blob/2f60b44999d7e610d932529784f082fc1c6af989/sphinx/util/inventory.py#L115-L116
const parts = line.match(/(.+?)\s+(\S+)\s+(-?\d+)\s+?(\S*)\s+(.*)/);
if (parts == null || parts.length != 6) {
console.warn(`Error parsing line of objects.inv: ${line}`);
continue;
}
const entry = {
name: parts[1],
domainAndRole: parts[2],
priority: parts[3],
uri: parts[4],
dispname: parts[5],
};
entry.uri = ObjectsInv._expandUri(entry.uri, entry.name);
if (shouldExcludePage(entry.uri)) {
continue;
}

entries.push(entry);
}

return new ObjectsInv(preamble, entries);
}

static _expandUri(uri: string, name: string): string {
frankharkins marked this conversation as resolved.
Show resolved Hide resolved
if (uri.includes("#") && uri.endsWith("$")) {
// #$ is a shorthand for "anchor==name"; see "For illustration" in
// https://sphobjinv.readthedocs.io/en/stable/syntax.html
uri = removeSuffix(uri, "$") + name;
}
return uri;
}

static _compressUri(uri: string, name: string): string {
if (uri.includes("#") && uri.endsWith(name)) {
uri = removeSuffix(uri, name) + "$";
}
return uri;
}

updateUris(transformLink: Function) {
frankharkins marked this conversation as resolved.
Show resolved Hide resolved
for (const entry of this.entries) {
entry.uri = entry.uri.replace(/\.html/, "");
entry.uri = transformLink(entry.uri);
}
}

/**
* Return all entries joined together as a single string
* to be compressed before writing
*/
entriesString() {
frankharkins marked this conversation as resolved.
Show resolved Hide resolved
const lines: string[] = [];
for (const e of this.entries) {
lines.push(
[
e.name,
e.domainAndRole,
e.priority,
ObjectsInv._compressUri(e.uri, e.name),
e.dispname,
].join(" "),
);
}
return lines.join("\n");
}

/**
* Compress and write to file
*/
async write(path: string) {
frankharkins marked this conversation as resolved.
Show resolved Hide resolved
const preamble = Buffer.from(this.preamble);
const compressed = deflateSync(Buffer.from(this.entriesString(), "utf8"), {
level: 9,
});
await writeFile(path, Buffer.concat([preamble, compressed]));
}
}
17 changes: 17 additions & 0 deletions scripts/lib/api/specialCaseResults.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ import { expect, test } from "@jest/globals";

import {
specialCaseResults,
transformSpecialCaseUrl,
PROVIDER_INDEX_META,
RUNTIME_INDEX_META,
} from "./specialCaseResults";
Expand Down Expand Up @@ -55,3 +56,19 @@ test("specialCaseResults()", () => {
},
]);
});

test("transformSpecialCaseUrl()", () => {
const urls = [
"release_notes",
"release_notes#release-notes-0-2-1-bug-fixes",
"ibm-provider#qiskit-ibm-provider",
];
const transformedUrls = urls.map((x) => transformSpecialCaseUrl(x));
expect(transformedUrls).toMatchInlineSnapshot(`
[
"release-notes",
"release-notes#release-notes-0-2-1-bug-fixes",
"index#qiskit-ibm-provider",
]
`);
});
Loading
Loading