diff --git a/scripts/lib/api/__snapshots__/conversionPipeline.test.ts.snap b/scripts/lib/api/__snapshots__/conversionPipeline.test.ts.snap index 1186fa329d1..a81d590e2b0 100644 --- a/scripts/lib/api/__snapshots__/conversionPipeline.test.ts.snap +++ b/scripts/lib/api/__snapshots__/conversionPipeline.test.ts.snap @@ -54,123 +54,111 @@ python_api_name: api_example.Electron # Electron - + + Bases: \`object\` -\`api_example.Electron(size=None, name=None)\` [GitHub](https://github.com/Qiskit/qiskit_sphinx_theme/tree/stable/0.1/api_example/electron.py "view source code") + A representation of an electron. -Bases: \`object\` + **Examples** -A representation of an electron. + \`\`\`python + from api_example import Electron -**Examples** + ELECTRON = Electron(size="2GB", name="QuantumComputing") + \`\`\` -\`\`\`python -from api_example import Electron + ### size -ELECTRON = Electron(size="2GB", name="QuantumComputing") -\`\`\` + + How big the Electron is. - + **Type** -### size + str + -How big the Electron is. + ### name -**Type** + + What the Electron is called. + -str + Create an electron. - + **Parameters** -### name + * **size** (*str*) – How big should this thing be? + * **name** (*str*) – The name we’ll call the electron. Nicknames preferred. -What the Electron is called. + **Raises** -Create an electron. + **ValueError** – You did something wrong -**Parameters** + ## Attributes -* **size** (*str*) – How big should this thing be? -* **name** (*str*) – The name we’ll call the electron. Nicknames preferred. + ### CLASS\\_ATTRIBUTE -**Raises** + -**ValueError** – You did something wrong + ### charge -## Attributes + - + ### mass -### CLASS\\_ATTRIBUTE + + The mass of the electron. + -\`= re.compile('abc')\` + ### really\\_long\\_named\\_attribute\\_that\\_probably\\_does\\_not\\_fit\\_nicely - + + A bit too verbose if you ask me. + -### charge + ## Methods - + ### compute\\_momentum -### mass + + Compute the electron’s velocity. -The mass of the electron. + **Parameters** - + **velocity** (*float*) – The electron’s velocity -### really\\_long\\_named\\_attribute\\_that\\_probably\\_does\\_not\\_fit\\_nicely + **Returns** -A bit too verbose if you ask me. + The computed momentum. -## Methods + **Raises** -### compute\\_momentum + **ValueError** – You did something wrong - + **Return type** -\`compute_momentum(velocity)\` + float + -Compute the electron’s velocity. + ### method\\_with\\_a\\_reallyyreallreallyreallyreallyreallyreallreallyreallyreallyreally\\_long\\_title -**Parameters** + + blahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblah + -**velocity** (*float*) – The electron’s velocity + ### overloaded\\_func -**Returns** + + This is meant to test out [https://github.com/Qiskit/qiskit\\_sphinx\\_theme/pull/319](https://github.com/Qiskit/qiskit_sphinx_theme/pull/319). -The computed momentum. + **Parameters** -**Raises** - -**ValueError** – You did something wrong - -**Return type** - -float - -### method\\_with\\_a\\_reallyyreallreallyreallyreallyreallyreallreallyreallyreallyreally\\_long\\_title - - - -\`method_with_a_reallyyreallreallyreallyreallyreallyreallreallyreallyreallyreally_long_title()\` - -blahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblahblah - -### overloaded\\_func - - - -\`overloaded_func(arg1: tuple[str, str], arg2: list[str], arg3: int, arg4: Electron) → None\` - -\`overloaded_func(arg1: tuple[int, int], arg2: list[int], arg3: bool, arg4: set[Electron]) → None\` - -This is meant to test out [https://github.com/Qiskit/qiskit\\_sphinx\\_theme/pull/319](https://github.com/Qiskit/qiskit_sphinx_theme/pull/319). - -**Parameters** - -* **arg1** – Tuples ftw! -* **arg2** – But lists are more flexy. -* **arg3** – Primitive values are good too. -* **arg4** – Recursionnnnn. + * **arg1** – Tuples ftw! + * **arg2** – But lists are more flexy. + * **arg3** – Primitive values are good too. + * **arg4** – Recursionnnnn. + + " `; @@ -188,24 +176,22 @@ python_api_name: api_example.my_function # api\\_example.my\\_function - - -\`api_example.my_function(input1, input2, input3=None, **kwargs)\` [GitHub](https://github.com/Qiskit/qiskit_sphinx_theme/tree/stable/0.1/api_example.py "view source code") + + A function that does awesome stuff. -A function that does awesome stuff. + **Returns** -**Returns** + Did the function work. -Did the function work. + **Raises** -**Raises** + * **ValueError** – If the inputs are not the correct values. + * **TypeError** – If the inputs are not strings. -* **ValueError** – If the inputs are not the correct values. -* **TypeError** – If the inputs are not strings. + **Return type** -**Return type** - -int + int + " `; @@ -227,94 +213,82 @@ This is a page to test out how we render classes and functions included inline i Every time you use this program, you’ll want to create an instance of [\`api_example.inline_classes.SimpleInlineClass\`](#api_example.inline_classes.SimpleInlineClass "api_example.inline_classes.SimpleInlineClass"). It has a simple interface: - - -\`api_example.inline_classes.SimpleInlineClass(arg1)\` [GitHub](https://github.com/Qiskit/qiskit_sphinx_theme/tree/stable/0.1/api_example/inline_classes.py "view source code") + + This is a simple class that does not have any methods or attributes. -This is a simple class that does not have any methods or attributes. - -It only has the class description and constructor. Many classes in Qiskit docs are simple like this. + It only has the class description and constructor. Many classes in Qiskit docs are simple like this. + It can be useful to use free functions rather than the class: ### -\`api_example.my_function(input1, input2, input3=None, **kwargs)\` [GitHub](https://github.com/Qiskit/qiskit_sphinx_theme/tree/stable/0.1/api_example.py "view source code") - -A function that does awesome stuff. + + A function that does awesome stuff. -**Returns** + **Returns** -Did the function work. + Did the function work. -**Raises** + **Raises** -* **ValueError** – If the inputs are not the correct values. -* **TypeError** – If the inputs are not strings. + * **ValueError** – If the inputs are not the correct values. + * **TypeError** – If the inputs are not strings. -**Return type** + **Return type** -int + int + Sometimes, you even need to use a really complex class! - - -\`api_example.inline_classes.InlineClassWithMethods\` [GitHub](https://github.com/Qiskit/qiskit_sphinx_theme/tree/stable/0.1/api_example/inline_classes.py "view source code") - -Bases: \`object\` - -This class is more involved. - -Note how the methods and attributes are rendered and indented when this class is inlined on the docs page. + + Bases: \`object\` -## Attributes + This class is more involved. - + Note how the methods and attributes are rendered and indented when this class is inlined on the docs page. -### CLASS\\_ATTRIBUTE + ## Attributes -\`str\` + ### CLASS\\_ATTRIBUTE -\`= 'An important part of any API.'\` + - + ### interest\\_rate -### interest\\_rate + -## Methods + ## Methods -### method1 + ### method1 - + + A simple method. -\`method1()\` + **Return type** -A simple method. + int + -**Return type** + ### method2 -int + + A method with a lot of args! -### method2 + This method will use a Hamiltonian to reach quantum advantage. Hamilton: great play & the secret to quantum computing. What a polymath. - + **Parameters** -\`method2(arg1, arg2, description)\` + * **arg1** (*int | float*) – All numbers accepted. + * **arg2** (*list\\[*[*InlineClassWithMethods*](#api_example.inline_classes.InlineClassWithMethods "api_example.inline_classes.InlineClassWithMethods")*]*) – A list of other instances, although these will be discarded. + * **description** (*str*) – If your description is too boring or too cryptic, this program will crash your computer. -A method with a lot of args! + **Return type** -This method will use a Hamiltonian to reach quantum advantage. Hamilton: great play & the secret to quantum computing. What a polymath. - -**Parameters** - -* **arg1** (*int | float*) – All numbers accepted. -* **arg2** (*list\\[*[*InlineClassWithMethods*](#api_example.inline_classes.InlineClassWithMethods "api_example.inline_classes.InlineClassWithMethods")*]*) – A list of other instances, although these will be discarded. -* **description** (*str*) – If your description is too boring or too cryptic, this program will crash your computer. - -**Return type** - -tuple\\[int, [*InlineClassWithMethods*](#api_example.inline_classes.InlineClassWithMethods "api_example.inline_classes.InlineClassWithMethods")] + tuple\\[int, [*InlineClassWithMethods*](#api_example.inline_classes.InlineClassWithMethods "api_example.inline_classes.InlineClassWithMethods")] + + ### Warning: exceptions @@ -322,11 +296,9 @@ The above APIs might raise an exception! Be careful! ### CustomException - - -\`api_example.inline_classes.CustomException\` [GitHub](https://github.com/Qiskit/qiskit_sphinx_theme/tree/stable/0.1/api_example/inline_classes.py "view source code") - -See how exceptions render too. + + See how exceptions render too. + ## Other prose @@ -373,22 +345,22 @@ Testing internal references… [\`Electron.compute_momentum()\`](api_example.Ele ### -\`api_example.my_function(input1, input2, input3=None, **kwargs)\` [GitHub](https://github.com/Qiskit/qiskit_sphinx_theme/tree/stable/0.1/api_example.py "view source code") - -A function that does awesome stuff. + + A function that does awesome stuff. -**Returns** + **Returns** -Did the function work. + Did the function work. -**Raises** + **Raises** -* **ValueError** – If the inputs are not the correct values. -* **TypeError** – If the inputs are not strings. + * **ValueError** – If the inputs are not the correct values. + * **TypeError** – If the inputs are not strings. -**Return type** + **Return type** -int + int + " `; diff --git a/scripts/lib/api/generateApiComponents.test.ts b/scripts/lib/api/generateApiComponents.test.ts index d4e3d8e2290..c37c91eee05 100644 --- a/scripts/lib/api/generateApiComponents.test.ts +++ b/scripts/lib/api/generateApiComponents.test.ts @@ -79,6 +79,7 @@ describe("createOpeningTag()", () => { name='run' attributeTypeHint='undefined' attributeValue='undefined' + isDedicatedPage='undefined' github='undefined' signature='Estimator.run(circuits, observables, parameter_values=None, **kwargs)' extraSignatures='[]' @@ -100,9 +101,10 @@ describe("createOpeningTag()", () => { name='run' attributeTypeHint='undefined' attributeValue='undefined' + isDedicatedPage='undefined' github='undefined' signature='Estimator.run(circuits, observables, parameter_values=None, **kwargs)' - extraSignatures='[${APOSTROPHE_HEX_CODE}Estimator.run(circuits, observables, parameter_values=None, **kwargs)${APOSTROPHE_HEX_CODE}, ${APOSTROPHE_HEX_CODE}Estimator.run(circuits, observables, parameter_values=None, **kwargs)${APOSTROPHE_HEX_CODE}]' + extraSignatures='["Estimator.run(circuits, observables, parameter_values=None, **kwargs)", "Estimator.run(circuits, observables, parameter_values=None, **kwargs)"]' > `); }); @@ -121,6 +123,7 @@ describe("createOpeningTag()", () => { name='instance' attributeTypeHint='str | None' attributeValue='None' + isDedicatedPage='undefined' github='undefined' signature='' extraSignatures='[]' @@ -139,6 +142,7 @@ describe("createOpeningTag()", () => { name='undefined' attributeTypeHint='undefined' attributeValue='undefined' + isDedicatedPage='undefined' github='undefined' signature='' extraSignatures='[]' diff --git a/scripts/lib/api/generateApiComponents.ts b/scripts/lib/api/generateApiComponents.ts index 8e43bba2772..e97b78b973d 100644 --- a/scripts/lib/api/generateApiComponents.ts +++ b/scripts/lib/api/generateApiComponents.ts @@ -10,24 +10,253 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -import { CheerioAPI, Cheerio } from "cheerio"; +import { CheerioAPI, Cheerio, Element } from "cheerio"; import { unified } from "unified"; import rehypeParse from "rehype-parse"; import rehypeRemark from "rehype-remark"; import remarkStringify from "remark-stringify"; -import { APOSTROPHE_HEX_CODE } from "../stringUtils"; +import { ApiType } from "./Metadata"; +import { + getLastPartFromFullIdentifier, + APOSTROPHE_HEX_CODE, +} from "../stringUtils"; export type ComponentProps = { - id: string; + id?: string; name?: string; attributeTypeHint?: string; attributeValue?: string; githubSourceLink?: string; rawSignature?: string; extraRawSignatures?: string[]; + isDedicatedPage?: boolean; }; +const APITYPE_TO_TAG: Record = { + class: "class", + exception: "class", + attribute: "attribute", + property: "attribute", + function: "function", + method: "function", +}; + +export async function processMdxComponent( + $: CheerioAPI, + $main: Cheerio, + signatures: Cheerio[], + $dl: Cheerio, + priorApiType: ApiType | undefined, + apiType: ApiType, + id: string, +): Promise<[string, string]> { + const tagName = APITYPE_TO_TAG[apiType]; + + const $firstSignature = signatures.shift()!; + const componentProps = prepareProps( + $, + $main, + $firstSignature, + $dl, + priorApiType, + apiType, + id, + ); + + const extraProps = signatures.flatMap( + ($overloadedSignature) => + prepareProps($, $main, $overloadedSignature, $dl, apiType, apiType, id) ?? + [], + ); + addExtraSignatures(componentProps, extraProps); + + return [await createOpeningTag(tagName, componentProps), ``]; +} + +// ------------------------------------------------------------------ +// Prepare props for MDX components +// ------------------------------------------------------------------ + +function prepareProps( + $: CheerioAPI, + $main: Cheerio, + $child: Cheerio, + $dl: Cheerio, + priorApiType: ApiType | undefined, + apiType: ApiType, + id: string, +): ComponentProps { + const preparePropsPerApiType: Record ComponentProps> = { + class: () => prepareClassProps($child, githubSourceLink, id), + property: () => + preparePropertyProps($child, $dl, priorApiType, githubSourceLink, id), + method: () => + prepareMethodProps($, $child, $dl, priorApiType, githubSourceLink, id), + attribute: () => + prepareAttributeProps($, $child, $dl, priorApiType, githubSourceLink, id), + function: () => + prepareFunctionOrExceptionProps($, $child, $dl, id, githubSourceLink), + exception: () => + prepareFunctionOrExceptionProps($, $child, $dl, id, githubSourceLink), + }; + + const githubSourceLink = prepareGitHubLink($child, apiType === "method"); + findByText($, $main, "em.property", apiType).remove(); + + if (!(apiType in preparePropsPerApiType)) { + throw new Error(`Unhandled Python type: ${apiType}`); + } + + return preparePropsPerApiType[apiType](); +} + +function prepareClassProps( + $child: Cheerio, + githubSourceLink: string | undefined, + id: string, +): ComponentProps { + return { + id, + rawSignature: $child.html()!, + githubSourceLink, + }; +} + +function preparePropertyProps( + $child: Cheerio, + $dl: Cheerio, + priorApiType: string | undefined, + githubSourceLink: string | undefined, + id: string, +): ComponentProps { + const rawSignature = $child.find("em").text()?.replace(/^:\s+/, ""); + const props = { + id, + name: getLastPartFromFullIdentifier(id), + rawSignature, + githubSourceLink, + }; + + if (!priorApiType && id) { + $dl.siblings("h1").text(getLastPartFromFullIdentifier(id)); + return { + ...props, + isDedicatedPage: true, + }; + } + + return props; +} + +function prepareMethodProps( + $: CheerioAPI, + $child: Cheerio, + $dl: Cheerio, + priorApiType: string | undefined, + githubSourceLink: string | undefined, + id: string, +): ComponentProps { + const props = { + id, + name: getLastPartFromFullIdentifier(id), + rawSignature: $child.html()!, + githubSourceLink, + }; + + if (id) { + if (!priorApiType) { + $dl.siblings("h1").text(getLastPartFromFullIdentifier(id)); + return { + ...props, + isDedicatedPage: true, + }; + } else if ($child.attr("id")) { + $(`

${getLastPartFromFullIdentifier(id)}

`).insertBefore($dl); + } + } + return props; +} + +function prepareAttributeProps( + $: CheerioAPI, + $child: Cheerio, + $dl: Cheerio, + priorApiType: string | undefined, + githubSourceLink: string | undefined, + id: string, +): ComponentProps { + if (!priorApiType) { + if (id) { + $dl.siblings("h1").text(getLastPartFromFullIdentifier(id)); + } + const rawSignature = $child.find("em").text()?.replace(/^:\s+/, ""); + return { + id, + rawSignature, + githubSourceLink, + isDedicatedPage: true, + }; + } + + // Else, the attribute is embedded on the class + const text = $child.text(); + + // Index of the default value of the attribute + let equalIndex = text.indexOf("="); + if (equalIndex == -1) { + equalIndex = text.length; + } + // Index of the attribute's type. The type should be + // found before the default value + let colonIndex = text.slice(0, equalIndex).indexOf(":"); + if (colonIndex == -1) { + colonIndex = text.length; + } + + $(`

${getLastPartFromFullIdentifier(id)}

`).insertBefore($dl); + // The attributes have the following shape: name [: type] [= value] + const name = text.slice(0, Math.min(colonIndex, equalIndex)).trim(); + const attributeTypeHint = text + .slice(Math.min(colonIndex + 1, equalIndex), equalIndex) + .trim(); + const attributeValue = text.slice(equalIndex, text.length).trim(); + + return { + id, + name, + attributeTypeHint, + attributeValue, + }; +} + +function prepareFunctionOrExceptionProps( + $: CheerioAPI, + $child: Cheerio, + $dl: Cheerio, + id: string, + githubSourceLink: string | undefined, +): ComponentProps { + const props = { + id, + name: getLastPartFromFullIdentifier(id), + rawSignature: $child.html()!, + githubSourceLink, + }; + + const pageHeading = $dl.siblings("h1").text(); + if (id.endsWith(pageHeading) && pageHeading != "") { + // Page is already dedicated to apiType; no heading needed + return { + ...props, + isDedicatedPage: true, + }; + } + $(`

${getLastPartFromFullIdentifier(id)}

`).insertBefore($dl); + + return props; +} + // ------------------------------------------------------------------ // Generate MDX components // ------------------------------------------------------------------ @@ -52,11 +281,7 @@ export async function createOpeningTag( const signature = await htmlSignatureToMd(props.rawSignature!); const extraSignatures: string[] = []; for (const sig of props.extraRawSignatures ?? []) { - extraSignatures.push( - `${APOSTROPHE_HEX_CODE}${await htmlSignatureToMd( - sig!, - )}${APOSTROPHE_HEX_CODE}`, - ); + extraSignatures.push(`"${await htmlSignatureToMd(sig!)}"`); } return `<${tagName} @@ -64,6 +289,7 @@ export async function createOpeningTag( name='${props.name}' attributeTypeHint='${attributeTypeHint}' attributeValue='${attributeValue}' + isDedicatedPage='${props.isDedicatedPage}' github='${props.githubSourceLink}' signature='${signature}' extraSignatures='[${extraSignatures.join(", ")}]' @@ -139,6 +365,7 @@ export async function htmlSignatureToMd( return String(file) .replaceAll("\n", "") .replaceAll("'", APOSTROPHE_HEX_CODE) + .replaceAll('"', '\\"') .replace(/^`/, "") .replace(/`$/, ""); } diff --git a/scripts/lib/api/htmlToMd.test.ts b/scripts/lib/api/htmlToMd.test.ts index c77c95580f6..cd6188ab541 100644 --- a/scripts/lib/api/htmlToMd.test.ts +++ b/scripts/lib/api/htmlToMd.test.ts @@ -173,42 +173,38 @@ Can be either (1) a dictionary mapping XX angle values to fidelity at that angle ).toMatchInlineSnapshot(` "# DAGCircuit - + + Bases: \`object\` - \`qiskit.dagcircuit.DAGCircuit\` [GitHub](https://github.com/Qiskit/qiskit-ibm-runtime/tree/0.9.2/qiskit/dagcircuit/dagcircuit.py "view source code") + Quantum circuit as a directed acyclic graph. - Bases: \`object\` + ## Attributes - Quantum circuit as a directed acyclic graph. + ### ancillas - ## Attributes + + Returns a list of ancilla bits in the order that the registers were added. + - + ## Methods - ### ancillas + ### add\\_calibration - Returns a list of ancilla bits in the order that the registers were added. + + Register a low-level, custom pulse definition for the given gate. - ## Methods + **Parameters** - ### add\\_calibration + * **gate** (*Union\\[*[*Gate*](qiskit.circuit.Gate#qiskit.circuit.Gate "qiskit.circuit.Gate")*, str]*) – Gate information. + * **qubits** (*Union\\[int, Tuple\\[int]]*) – List of qubits to be measured. + * **schedule** ([*Schedule*](qiskit.pulse.Schedule#qiskit.pulse.Schedule "qiskit.pulse.Schedule")) – Schedule information. + * **params** (*Optional\\[List\\[Union\\[float,* [*Parameter*](qiskit.circuit.Parameter#qiskit.circuit.Parameter "qiskit.circuit.Parameter")*]]]*) – A list of parameters. - + **Raises** - \`add_calibration(gate, qubits, schedule, params=None)\` - - Register a low-level, custom pulse definition for the given gate. - - **Parameters** - - * **gate** (*Union\\[*[*Gate*](qiskit.circuit.Gate#qiskit.circuit.Gate "qiskit.circuit.Gate")*, str]*) – Gate information. - * **qubits** (*Union\\[int, Tuple\\[int]]*) – List of qubits to be measured. - * **schedule** ([*Schedule*](qiskit.pulse.Schedule#qiskit.pulse.Schedule "qiskit.pulse.Schedule")) – Schedule information. - * **params** (*Optional\\[List\\[Union\\[float,* [*Parameter*](qiskit.circuit.Parameter#qiskit.circuit.Parameter "qiskit.circuit.Parameter")*]]]*) – A list of parameters. - - **Raises** - - **Exception** – if the gate is of type string and params is None. + **Exception** – if the gate is of type string and params is None. + + " `); }); @@ -389,21 +385,21 @@ for execution on present day noisy quantum systems.

).toMatchInlineSnapshot(` "In addition to the public abstract methods, subclasses should also implement the following private methods: - \`classmethod _default_options()\` - - Return the default options + + Return the default options - This method will return a [\`qiskit.providers.Options\`](qiskit.providers.Options#qiskit.providers.Options "qiskit.providers.Options") subclass object that will be used for the default options. These should be the default parameters to use for the options of the backend. + This method will return a [\`qiskit.providers.Options\`](qiskit.providers.Options#qiskit.providers.Options "qiskit.providers.Options") subclass object that will be used for the default options. These should be the default parameters to use for the options of the backend. - **Returns** + **Returns** - **A options object with** + **A options object with** - default values set + default values set - **Return type** + **Return type** - [qiskit.providers.Options](qiskit.providers.Options#qiskit.providers.Options "qiskit.providers.Options") + [qiskit.providers.Options](qiskit.providers.Options#qiskit.providers.Options "qiskit.providers.Options") + " `); }); @@ -683,11 +679,9 @@ for execution on present day noisy quantum systems.

"isReleaseNotes": false, "markdown": "# Estimator - - - \`SamplerExample(circuits=None, parameters=None) \`[GitHub](https://github.com/Qiskit/qiskit-ibm-runtime/tree/0.9.2/qiskit_ibm_runtime/sampler.py "view source code") - - Class for interacting with Qiskit Runtime Sampler primitive service. + + Class for interacting with Qiskit Runtime Sampler primitive service. + ", "meta": { "apiName": "qiskit_ibm_runtime.Sampler", @@ -737,11 +731,9 @@ for execution on present day noisy quantum systems.

"isReleaseNotes": false, "markdown": "# circuits - - - \`tuple[qiskit.circuit.quantumcircuit.QuantumCircuit, ...]\` - - Quantum circuits that represents quantum states. + + Quantum circuits that represents quantum states. + ", "meta": { "apiName": "qiskit_ibm_runtime.Estimator.circuits", @@ -770,11 +762,9 @@ for execution on present day noisy quantum systems.

"isReleaseNotes": false, "markdown": "# run - - - \`Estimator.run(circuits, observables, parameter_values=None, **kwargs)\` - - Submit a request to the estimator primitive program. + + Submit a request to the estimator primitive program. + ", "meta": { "apiName": "qiskit_ibm_runtime.Estimator.run", @@ -803,9 +793,7 @@ for execution on present day noisy quantum systems.

"isReleaseNotes": false, "markdown": "# callback - - - \`Optional[Callable] = None\` + ", "meta": { "apiName": "qiskit_ibm_runtime.options.EnvironmentOptions.callback", @@ -853,21 +841,19 @@ By default this is sys.stdout.

# job\\_monitor - - - \`job_monitor(job, interval=None, output=<_io.TextIOWrapper name='' mode='w' encoding='utf-8'>)\` [GitHub](https://github.com/Qiskit/qiskit-ibm-runtime/tree/0.9.2/qiskit_ibm_provider/job/job_monitor.py "view source code") + + Monitor the status of an \`IBMJob\` instance. - Monitor the status of an \`IBMJob\` instance. - - **Parameters** + **Parameters** - * **job** (\`IBMJob\`) – Job to monitor. - * **interval** (\`Optional\`\\[\`float\`]) – Time interval between status queries. - * **output** (\`TextIO\`) – The file like object to write status messages to. By default this is sys.stdout. + * **job** (\`IBMJob\`) – Job to monitor. + * **interval** (\`Optional\`\\[\`float\`]) – Time interval between status queries. + * **output** (\`TextIO\`) – The file like object to write status messages to. By default this is sys.stdout. - **Return type** + **Return type** - \`None\` + \`None\` + ", "meta": { "apiName": "qiskit_ibm_provider.job.job_monitor", @@ -904,13 +890,11 @@ By default this is sys.stdout.

# IBMJobError - - - \`IBMJobError(*message)\` [GitHub](https://github.com/Qiskit/qiskit-ibm-runtime/tree/0.9.2/qiskit_ibm_provider/job/exceptions.py "view source code") - - Base class for errors raised by the job modules. + + Base class for errors raised by the job modules. - Set the error message. + Set the error message. + ", "meta": { "apiName": "qiskit_ibm_provider.job.IBMJobError", diff --git a/scripts/lib/api/htmlToMd.ts b/scripts/lib/api/htmlToMd.ts index 98a747c3b73..a826912114a 100644 --- a/scripts/lib/api/htmlToMd.ts +++ b/scripts/lib/api/htmlToMd.ts @@ -27,7 +27,7 @@ import { Emphasis, Root, Content } from "mdast"; import { processHtml } from "./processHtml"; import { HtmlToMdResult } from "./HtmlToMdResult"; import { Metadata } from "./Metadata"; -import { removePrefix, removeSuffix } from "../stringUtils"; +import { removePrefix, removeSuffix, capitalize } from "../stringUtils"; import { remarkStringifyOptions } from "./commonParserConfig"; export async function sphinxHtmlToMarkdown(options: { @@ -37,7 +37,7 @@ export async function sphinxHtmlToMarkdown(options: { determineGithubUrl: (fileName: string) => string; releaseNotesTitle: string; }): Promise { - const processedHtml = processHtml(options); + const processedHtml = await processHtml(options); const markdown = await generateMarkdownFile( processedHtml.html, processedHtml.meta, @@ -123,6 +123,15 @@ function prepareHandlers(meta: Metadata): Record { return defaultHandlers.div(h, node); }, + class(h, node: any): any { + return buildApiComponent(h, node); + }, + function(h, node: any): any { + return buildApiComponent(h, node); + }, + attribute(h, node: any): any { + return buildApiComponent(h, node); + }, }; return handlers; @@ -301,3 +310,64 @@ function buildMathExpression(node: any, type: "math" | "inlineMath"): any { } return { type: type, value }; } + +function buildApiComponent(h: H, node: any): any { + const componentName = capitalize(node.tagName); + + const hastTree = { + type: "mdxJsxFlowElement", + name: componentName, + attributes: [], + children: all(h, node), + }; + + maybeAddAttribute(hastTree, "id", node.properties.id); + maybeAddAttribute(hastTree, "name", node.properties.name); + maybeAddAttribute( + hastTree, + "attributeTypeHint", + node.properties.attributetypehint, + ); + maybeAddAttribute(hastTree, "attributeValue", node.properties.attributevalue); + maybeAddExpressionAttribute( + hastTree, + "isDedicatedPage", + node.properties.isdedicatedpage, + ); + maybeAddAttribute(hastTree, "github", node.properties.github); + maybeAddAttribute(hastTree, "signature", node.properties.signature); + maybeAddExpressionAttribute( + hastTree, + "extraSignatures", + node.properties.extrasignatures, + ); + + return hastTree; +} + +function maybeAddAttribute(hastTree: any, name: string, value: string): void { + if (value && value.trim() && value != "undefined") { + hastTree.attributes.push({ + type: "mdxJsxAttribute", + name, + value, + }); + } +} + +function maybeAddExpressionAttribute( + hastTree: any, + name: string, + value: string, +): void { + if (value && value != "undefined" && value != "[]") { + hastTree.attributes.push({ + type: "mdxJsxAttribute", + name, + value: { + type: "mdxJsxAttributeValueExpression", + value, + }, + }); + } +} diff --git a/scripts/lib/api/mergeClassMembers.test.ts b/scripts/lib/api/mergeClassMembers.test.ts index 32ace2a5dd5..300186c1c02 100644 --- a/scripts/lib/api/mergeClassMembers.test.ts +++ b/scripts/lib/api/mergeClassMembers.test.ts @@ -19,17 +19,19 @@ describe("mergeClassMembers", () => { test("merge class members", async () => { const results: HtmlToMdResultWithUrl[] = [ { - markdown: `## Attributes + markdown: ` + ## Attributes -| | | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | -| [\`RuntimeOptions.backend\`](qiskit_ibm_runtime.RuntimeOptions.backend#qiskit_ibm_runtime.RuntimeOptions.backend "qiskit_ibm_runtime.RuntimeOptions.backend") | | + | | | + | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | --------------- | + | [\`RuntimeOptions.backend\`](qiskit_ibm_runtime.RuntimeOptions.backend#qiskit_ibm_runtime.RuntimeOptions.backend "qiskit_ibm_runtime.RuntimeOptions.backend") | | -## Methods + ## Methods -| | | -| ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------- | -| [\`RuntimeOptions.validate\`](qiskit_ibm_runtime.RuntimeOptions.validate#qiskit_ibm_runtime.RuntimeOptions.validate "qiskit_ibm_runtime.RuntimeOptions.validate")(channel) | Validate options. |`, + | | | + | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------ | ----------------- | + | [\`RuntimeOptions.validate\`](qiskit_ibm_runtime.RuntimeOptions.validate#qiskit_ibm_runtime.RuntimeOptions.validate "qiskit_ibm_runtime.RuntimeOptions.validate")(channel) | Validate options. | +`, meta: { apiType: "class", apiName: "RuntimeOptions", @@ -40,8 +42,7 @@ describe("mergeClassMembers", () => { }, { markdown: `# RuntimeOptions.backend - -\`Optional[str] = None\` + `, meta: { apiType: "attribute", @@ -54,7 +55,7 @@ describe("mergeClassMembers", () => { { markdown: `# RuntimeOptions.circuits -\`Optional[str] = None\` + `, meta: { apiType: "property", @@ -68,21 +69,21 @@ describe("mergeClassMembers", () => { markdown: ` # RuntimeOptions.validate -\`RuntimeOptions.validate(channel)\` + + Validate options. -Validate options. + * Parameters: -* Parameters: + **channel** (\`str\`) – channel type. - **channel** (\`str\`) – channel type. + * Raises: -* Raises: + **IBMInputValueError** – If one or more option is invalid. - **IBMInputValueError** – If one or more option is invalid. + * Return type: -* Return type: - - \`None\` + \`None\` + `, meta: { apiType: "method", @@ -96,35 +97,37 @@ Validate options. const merged = await mergeClassMembers(results); expect(merged.find((item) => item.meta.apiType === "class")?.markdown) .toMatchInlineSnapshot(` - "## Attributes - - ### RuntimeOptions.backend + " + ## Attributes - \`Optional[str] = None\` + ### RuntimeOptions.backend - ### RuntimeOptions.circuits + - \`Optional[str] = None\` + ### RuntimeOptions.circuits - ## Methods + - ### RuntimeOptions.validate + ## Methods - \`RuntimeOptions.validate(channel)\` + ### RuntimeOptions.validate - Validate options. + + Validate options. - * Parameters: + * Parameters: - **channel** (\`str\`) – channel type. + **channel** (\`str\`) – channel type. - * Raises: + * Raises: - **IBMInputValueError** – If one or more option is invalid. + **IBMInputValueError** – If one or more option is invalid. - * Return type: + * Return type: - \`None\` + \`None\` + + " `); expect(merged.length).toEqual(1); diff --git a/scripts/lib/api/mergeClassMembers.ts b/scripts/lib/api/mergeClassMembers.ts index 78193fc3f9f..5198745b099 100644 --- a/scripts/lib/api/mergeClassMembers.ts +++ b/scripts/lib/api/mergeClassMembers.ts @@ -18,6 +18,7 @@ import remarkGfm from "remark-gfm"; import remarkMath from "remark-math"; import remarkStringify from "remark-stringify"; import { Content, Root } from "mdast"; +import { MdxJsxFlowElement, MdxJsxAttribute } from "mdast-util-mdx-jsx"; import { visit } from "unist-util-visit"; import { HtmlToMdResultWithUrl } from "./HtmlToMdResult"; @@ -62,17 +63,32 @@ export async function mergeClassMembers( .use(remarkGfm) .use(remarkMath) .use(() => { - return async (tree: Root) => { - for (const node of tree.children) { + return async (root: Root) => { + // The attribute and method's section can be found under the class component + const mdxClassElement = root.children.find( + (node): node is MdxJsxFlowElement => + node.type == "mdxJsxFlowElement" && node.name == "Class", + ); + + if (!mdxClassElement) { + return; + } + + for (const node of mdxClassElement.children) { await replaceMembersAfterTitle( - tree, + mdxClassElement, node, "Attributes", attributesAndProps, ); - await replaceMembersAfterTitle(tree, node, "Methods", methods); await replaceMembersAfterTitle( - tree, + mdxClassElement, + node, + "Methods", + methods, + ); + await replaceMembersAfterTitle( + mdxClassElement, node, "Methods Defined Here", methods, @@ -94,7 +110,7 @@ export async function mergeClassMembers( } async function replaceMembersAfterTitle( - tree: Root, + tree: MdxJsxFlowElement, node: Content, title: string, members: HtmlToMdResultWithUrl[], @@ -136,6 +152,15 @@ async function parseMarkdownIncreasingHeading( visit(root, "heading", (node: any) => { node.depth = node.depth + depthIncrease; }); + visit(root, "mdxJsxFlowElement", (node: any) => { + // We are inlining functions and attributes and thus we need to remove + // the `isDedicatedPage` prop + if (node.name == "Attribute" || node.name == "Function") { + node.attributes = node.attributes.filter( + (attr: MdxJsxAttribute) => attr.name != "isDedicatedPage", + ); + } + }); }); const root = pipeline.parse(md); diff --git a/scripts/lib/api/processHtml.test.ts b/scripts/lib/api/processHtml.test.ts index d73db331d9d..c977250f891 100644 --- a/scripts/lib/api/processHtml.test.ts +++ b/scripts/lib/api/processHtml.test.ts @@ -383,7 +383,7 @@ describe("maybeSetModuleMetadata()", () => { }); describe("processMembersAndSetMeta()", () => { - test("function with added heading", () => { + test("function with added heading", async () => { const html = `

Circuit Converters

@@ -398,10 +398,10 @@ describe("processMembersAndSetMeta()", () => { `; const doc = CheerioDoc.load(html); const meta: Metadata = {}; - processMembersAndSetMeta(doc.$, doc.$main, meta); + await processMembersAndSetMeta(doc.$, doc.$main, meta); doc.expectHtml(`

Circuit Converters

-

circuit_to_dag

-qiskit.converters.circuit_to_dag(circuit, copy_operations=True, *, qubit_order=None, clbit_order=None) GitHub

+

circuit_to_dag

+

Build a DAGCircuit object from a QuantumCircuit.

Parameters:
@@ -409,14 +409,15 @@ describe("processMembersAndSetMeta()", () => {
  • circuit – the input circuit.

  • copy_operations – Deep copy the operation objects in the QuantumCircuit for the output DAGCircuit.

  • -
    `); +
    +`); expect(meta).toEqual({ apiType: "function", apiName: "qiskit.converters.circuit_to_dag", }); }); - test("function without added heading", () => { + test("function without added heading", async () => { const html = `

    least_busy

    @@ -445,10 +446,10 @@ backends may not have this attribute.

    `; const doc = CheerioDoc.load(html); const meta: Metadata = {}; - processMembersAndSetMeta(doc.$, doc.$main, meta); + await processMembersAndSetMeta(doc.$, doc.$main, meta); doc.expectHtml(`

    least_busy

    -

    -least_busy(backends) GitHub

    +
    +

    Return the least busy backend from a list.

    Return the least busy available backend for those that have a pending_jobs in their status. Note that local @@ -469,14 +470,15 @@ backends may not have this attribute.

    does not have the pending_jobs attribute in its status.

    -
    `); + +`); expect(meta).toEqual({ apiType: "function", apiName: "qiskit_ibm_provider.least_busy", }); }); - test("exception with added heading", () => { + test("exception with added heading", async () => { const html = `

    Top-level exceptions (qiskit.exceptions)

    All Qiskit-related errors raised by Qiskit are subclasses of the base:

    @@ -486,7 +488,6 @@ backends may not have this attribute.

    Base class for errors raised by Qiskit.

    Set the error message.

    -

    Note

    Errors that are just general programming errors, such as incorrect typing, may still raise @@ -505,16 +506,16 @@ particular error, which subclasses both

    Top-level exceptions (qiskit.exceptions)

    All Qiskit-related errors raised by Qiskit are subclasses of the base:

    -

    QiskitError

    -qiskit.exceptions.QiskitError(*message) GitHub

    +

    QiskitError

    +

    Base class for errors raised by Qiskit.

    Set the error message.

    -
    - +
    +

    Note

    Errors that are just general programming errors, such as incorrect typing, may still raise diff --git a/scripts/lib/api/processHtml.ts b/scripts/lib/api/processHtml.ts index c5e41c7e36f..8daa34709ec 100644 --- a/scripts/lib/api/processHtml.ts +++ b/scripts/lib/api/processHtml.ts @@ -10,12 +10,11 @@ // copyright notice, and modified files need to carry a notice indicating // that they have been altered from the originals. -import { CheerioAPI, Cheerio, load } from "cheerio"; +import { CheerioAPI, Cheerio, load, Element } from "cheerio"; import { Image } from "./HtmlToMdResult"; import { Metadata, ApiType } from "./Metadata"; -import { getLastPartFromFullIdentifier } from "../stringUtils"; -import { findByText, prepareGitHubLink } from "./generateApiComponents"; +import { processMdxComponent } from "./generateApiComponents"; export type ProcessedHtml = { html: string; @@ -24,13 +23,13 @@ export type ProcessedHtml = { isReleaseNotes: boolean; }; -export function processHtml(options: { +export async function processHtml(options: { html: string; fileName: string; imageDestination: string; determineGithubUrl: (fileName: string) => string; releaseNotesTitle: string; -}): ProcessedHtml { +}): Promise { const { html, fileName, @@ -61,7 +60,7 @@ export function processHtml(options: { preserveMathBlockWhitespace($, $main); const meta: Metadata = {}; - processMembersAndSetMeta($, $main, meta); + await processMembersAndSetMeta($, $main, meta); maybeSetModuleMetadata($, $main, meta); if (meta.apiType === "module") { updateModuleHeadings($, $main, meta); @@ -260,11 +259,11 @@ export function removeColonSpans($main: Cheerio): void { $main.find(".colon").remove(); } -export function processMembersAndSetMeta( +export async function processMembersAndSetMeta( $: CheerioAPI, $main: Cheerio, meta: Metadata, -): void { +): Promise { let continueMapMembers = true; while (continueMapMembers) { // members can be recursive, so we need to pick elements one by one @@ -283,173 +282,41 @@ export function processMembersAndSetMeta( const id = $dl.find("dt").attr("id") || ""; const apiType = getApiType($dl); - const replacement = $dl - .children() - .toArray() - .map((child) => { - const $child = $(child); - if (child.name !== "dt" || !apiType) { - return `

    ${$child.html()}
    `; - } - - const priorApiType = meta.apiType; - if (!priorApiType) { - meta.apiType = apiType; - meta.apiName = id; - } - - return processMember($, $main, $child, $dl, priorApiType, apiType, id); - }) - .join("\n"); - - $dl.replaceWith(`
    ${replacement}
    `); - } -} - -function processMember( - $: CheerioAPI, - $main: Cheerio, - $child: Cheerio, - $dl: Cheerio, - priorApiType: string | undefined, - apiType: string, - id: string, -) { - const githubUrl = prepareGitHubLink($child, apiType === "method"); - const githubSourceLink = githubUrl - ? ` GitHub` - : ""; - - findByText($, $main, "em.property", apiType).remove(); - - if (apiType == "class") { - return `

    ${$child.html()}${githubSourceLink}

    `; - } - - if (apiType == "property") { - return processProperty($child, $dl, priorApiType, id, githubSourceLink); - } - - if (apiType == "method") { - return processMethod($, $child, $dl, priorApiType, id, githubSourceLink); - } - - if (apiType == "attribute") { - return processAttribute($child, $dl, priorApiType, id, githubSourceLink); - } - - if (apiType === "function" || apiType === "exception") { - return processFunctionOrException($child, $dl, id, githubSourceLink); - } - - throw new Error(`Unhandled Python type: ${apiType}`); -} - -function processProperty( - $child: Cheerio, - $dl: Cheerio, - priorApiType: string | undefined, - id: string, - githubSourceLink: string, -) { - if (!priorApiType && id) { - $dl.siblings("h1").text(getLastPartFromFullIdentifier(id)); - } - - const signature = $child.find("em").text()?.replace(/^:\s+/, ""); - if (signature.trim().length === 0) return; - return `

    ${signature}${githubSourceLink}

    `; -} - -function processMethod( - $: CheerioAPI, - $child: Cheerio, - $dl: Cheerio, - priorApiType: string | undefined, - id: string, - githubSourceLink: string, -) { - if (id) { + const priorApiType = meta.apiType; if (!priorApiType) { - $dl.siblings("h1").text(getLastPartFromFullIdentifier(id)); - } else if (!$child.attr("id")) { - // Overload methods have more than one
    tag, but only the first one - // contains an id. - return `

    ${$child.html()}${githubSourceLink}

    `; - } else { - // Inline methods - $(`

    ${getLastPartFromFullIdentifier(id)}

    `).insertBefore($dl); + meta.apiType = apiType; + meta.apiName = id; } - } - return `

    ${$child.html()}${githubSourceLink}

    `; -} + const bodyElements: string[] = []; + const signatures: Cheerio[] = []; -function processAttribute( - $child: Cheerio, - $dl: Cheerio, - priorApiType: string | undefined, - id: string, - githubSourceLink: string, -) { - if (!priorApiType) { - if (id) { - $dl.siblings("h1").text(getLastPartFromFullIdentifier(id)); + for (const child of $dl.children().toArray()) { + const $child = $(child); + if (child.name !== "dt" || !apiType) { + bodyElements.push(`
    ${$child.html()}
    `); + continue; + } + signatures.push($child); } - const signature = $child.find("em").text()?.replace(/^:\s+/, ""); - if (signature.trim().length === 0) return; - return `

    ${signature}${githubSourceLink}

    `; - } - - // Else, the attribute is embedded on the class - const text = $child.text(); - - // Index of the default value of the attribute - let equalIndex = text.indexOf("="); - if (equalIndex == -1) { - equalIndex = text.length; - } - // Index of the attribute's type. The type should be - // found before the default value - let colonIndex = text.slice(0, equalIndex).indexOf(":"); - if (colonIndex == -1) { - colonIndex = text.length; - } - - // The attributes have the following shape: name [: type] [= value] - const name = text.slice(0, Math.min(colonIndex, equalIndex)).trim(); - const type = text - .slice(Math.min(colonIndex + 1, equalIndex), equalIndex) - .trim(); - const value = text.slice(equalIndex, text.length).trim(); - - const output = [`

    ${name}

    `]; - if (type) { - output.push(`

    ${type}

    `); - } - if (value) { - output.push(`

    ${value}

    `); - } - return output.join("\n"); -} - -function processFunctionOrException( - $child: Cheerio, - $dl: Cheerio, - id: string, - githubSourceLink: string, -) { - const descriptionHtml = `

    ${$child.html()}${githubSourceLink}

    `; - - const pageHeading = $dl.siblings("h1").text(); - if (id.endsWith(pageHeading) && pageHeading != "") { - // Page is already dedicated to apiType; no heading needed - return descriptionHtml; + if (signatures.length == 0) { + $dl.replaceWith(`
    ${bodyElements.join("\n")}
    `); + } else { + const [openTag, closeTag] = await processMdxComponent( + $, + $main, + signatures, + $dl, + priorApiType, + apiType!, + id, + ); + $dl.replaceWith( + `
    ${openTag}\n${bodyElements.join("\n")}\n${closeTag}
    `, + ); + } } - - const apiName = id.split(".").slice(-1)[0]; - return `

    ${apiName}

    ${descriptionHtml}`; } export function maybeSetModuleMetadata(