Autogenerate textbook TOC (#103)

* Change to nuxt-ts to support TypeScript at build time

* Add jest infrastructure

* Automatize creation of content/education/textbook-toc.md for /education preview

* Test the functionality
This commit is contained in:
Salvador de la Puente González 2019-10-29 09:53:51 +01:00 committed by GitHub
parent f7b0a47703
commit c810752dc6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 7238 additions and 21 deletions

View File

@ -7,6 +7,11 @@ module.exports = {
'@nuxtjs' '@nuxtjs'
], ],
rules: { rules: {
// TODO: Remove when fixing:
// https://github.com/typescript-eslint/typescript-eslint/issues/342
// More info:
// https://github.com/typescript-eslint/typescript-eslint/issues/342#issuecomment-484739065
'no-undef': 'off',
'@typescript-eslint/no-unused-vars': 'error' '@typescript-eslint/no-unused-vars': 'error'
} }
} }

View File

@ -1,13 +1,8 @@
## Table of Contents ## Table of Contents
### Preface
- Structure of this Textbook
### Chapter 0. Prerequisites ### Chapter 0. Prerequisites
- Python and Jupyter Notebooks - Python and Jupyter Notebooks
- Qiskit - Qiskit
- Exercises - Linear Algebra
### Chapter 1. Quantum States and Qubits ### Chapter 1. Quantum States and Qubits
- Introduction - Introduction
- The Atoms of Computation - The Atoms of Computation
@ -15,8 +10,6 @@
- Writing Down Qubit States - Writing Down Qubit States
- Pauli Matrices and the Bloch Sphere - Pauli Matrices and the Bloch Sphere
- States for Many Qubits - States for Many Qubits
- Exercises
### Chapter 2. Single-Qubit and Multi-Qubit Gates ### Chapter 2. Single-Qubit and Multi-Qubit Gates
- Introduction - Introduction
- Quantum Gates - Quantum Gates
@ -24,8 +17,6 @@
- The Standard Gate Set - The Standard Gate Set
- Proving Universality - Proving Universality
- Basic Circuit Identities - Basic Circuit Identities
- Exercises
### Chapter 3. Quantum Algorithms ### Chapter 3. Quantum Algorithms
- Quantum Teleportation - Quantum Teleportation
- Deutsch-Josza Algorithm - Deutsch-Josza Algorithm
@ -34,17 +25,14 @@
- Quantum Fourier Transform - Quantum Fourier Transform
- Quantum Phase Estimation - Quantum Phase Estimation
- Grover's Algorithm - Grover's Algorithm
- Exercises
### Chapter 4. Quantum Algorithms for Applications ### Chapter 4. Quantum Algorithms for Applications
- Simulating Molecules using VQE - Simulating Molecules using VQE
- Solving Satisfiability Problems using Grover's Algorithm - Solving Satisfiability Problems using Grover's Algorithm
- Exercises
### Chapter 5. Investigating Quantum Hardware Using Qiskit ### Chapter 5. Investigating Quantum Hardware Using Qiskit
- Calibrating Qubits with OpenPulse - Calibrating Qubits with OpenPulse
- Introduction to Quantum Error Correction using Repetition Codes - Introduction to Quantum Error Correction using Repetition Codes
- Measurement Error Mitigation - Measurement Error Mitigation
- Randomized Benchmarking - Randomized Benchmarking
- Measuring Quantum Volume - Measuring Quantum Volume
- Exercises ### Chapter 6. Implementations of Recent Quantum Algorithms
- Variational Quantum Linear Solver

View File

@ -0,0 +1,16 @@
import fs from 'fs'
import { extractToc, formatTocLines } from './textbook-toc-utils'
/**
* Extract the table of contents at `indexPath` and uses it to generate
* a markdown TOC at `tocPath`, suitable for being rendered in `/education`.
*
* @param indexPath HTML file where the TOC is extracted from.
* @param tocPath output Markdown file path where TOC is generated.
*/
export default function generateTextbookToc(indexPath: string, tocPath: string) {
const indexContent = fs.readFileSync(indexPath, 'utf8')
const toc = extractToc(indexContent)
const mdTocLines = formatTocLines(toc)
fs.writeFileSync(tocPath, mdTocLines.join('\n'))
}

View File

@ -0,0 +1,47 @@
type TocType = Array<[string, string[]]>
export { extractToc, formatTocLines, TocType }
function extractToc(indexContent: string): TocType {
// Chapter titles are of form `Chapter X. Chapter title<`.
const allChapters = (indexContent.match(/Chapter\s+\d+\.\s+[^<]+/g) || [])
.map(entry => entry.trim())
// Topic titles are of form `X.Y <a ...>Topic Title<`
const allTopics = (indexContent.match(/(\d+.\d+\s+)<a[^>]+([^<]+)/g) || [])
.map(entry => entry.replace(/<a[^>]+>/, '').trim())
return allChapters.reduce<TocType>((output, title, index) => {
const chapters = getTopics(index, allTopics)
output.push([title, chapters])
return output
}, [])
function getTopics(index: number, allTopics: string[]) {
return allTopics.filter(topic => topic.startsWith(`${index}.`))
}
}
function formatTocLines(toc: TocType, header: string = 'Table of Contents'): string[] {
return withHeader(
header,
toc.reduce<string[]>((output, [title, chapters]) => {
output.push(formatTitle(title))
output.push(...formatChapters(chapters))
return output
}, [])
)
function withHeader(title: string, lines: string[]): string[] {
lines.unshift(`## ${title}`)
return lines
}
function formatTitle(title: string): string {
return `### ${title}`
}
function formatChapters(chapters: string[]): string[] {
return chapters
.map(title => `- ${(title.match(/\s.+/) as string[])[0].trim()}`)
}
}

18
jest.config.js Normal file
View File

@ -0,0 +1,18 @@
// Created following:
// https://github.com/Al-un/learn-nuxt-ts/blob/master/docs/06.test.md
module.exports = {
moduleNameMapper: {
'^@/(.*)$': '<rootDir>/$1',
'^~/(.*)$': '<rootDir>/$1'
},
transform: {
'^.+\\.ts?$': 'ts-jest',
'.*\\.(vue)$': 'vue-jest'
},
moduleFileExtensions: ['ts', 'js', 'vue', 'json'],
testMatch: ['**/tests/**/*.spec.ts'],
collectCoverageFrom: [
'hooks/**/*.ts'
]
}

View File

@ -5,6 +5,7 @@ import miLinkAttributes from 'markdown-it-link-attributes'
import miAnchor from 'markdown-it-anchor' import miAnchor from 'markdown-it-anchor'
import uslug from 'uslug' import uslug from 'uslug'
import pkg from './package' import pkg from './package'
import generateTextbookToc from './hooks/generate-textbook-toc'
const md = markdownIt({ const md = markdownIt({
linkify: true, linkify: true,
@ -162,5 +163,16 @@ export default {
.map(filename => `/experiments/${path.parse(filename).name}`) .map(filename => `/experiments/${path.parse(filename).name}`)
return events.concat(experiments) return events.concat(experiments)
})() })()
},
hooks: {
build: {
before() {
generateTextbookToc(
'./static/textbook/index.html',
'./content/education/textbook-toc.md'
)
}
}
} }
} }

7084
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -5,12 +5,12 @@
"author": "IBM Q Community Team", "author": "IBM Q Community Team",
"private": true, "private": true,
"scripts": { "scripts": {
"dev": "nuxt", "dev": "nuxt-ts",
"dev-debug": "node --debug node_modules/.bin/nuxt", "dev-debug": "node --debug node_modules/.bin/nuxt-ts",
"test": "echo \"Error: no test specified\" && exit 0", "test": "jest",
"build": "npm run generate", "build": "npm run generate",
"start": "nuxt start", "start": "nuxt-ts start",
"generate": "nuxt generate", "generate": "nuxt-ts generate",
"lint": "eslint --ext .ts,.js,.vue --ignore-path .gitignore ." "lint": "eslint --ext .ts,.js,.vue --ignore-path .gitignore ."
}, },
"dependencies": { "dependencies": {
@ -22,9 +22,13 @@
"@nuxt/typescript-build": "^0.1.11", "@nuxt/typescript-build": "^0.1.11",
"@nuxt/typescript-runtime": "^0.1.5", "@nuxt/typescript-runtime": "^0.1.5",
"@nuxtjs/eslint-config": "0.0.1", "@nuxtjs/eslint-config": "0.0.1",
"@types/jest": "^24.0.20",
"@types/markdown-it": "0.0.7", "@types/markdown-it": "0.0.7",
"@typescript-eslint/eslint-plugin": "^1.13.0", "@typescript-eslint/eslint-plugin": "^1.13.0",
"@typescript-eslint/parser": "^1.13.0", "@typescript-eslint/parser": "^1.13.0",
"@vue/test-utils": "^1.0.0-beta.29",
"babel-bridge": "^1.12.11",
"babel-core": "^6.26.3",
"d3": "^5.11.0", "d3": "^5.11.0",
"datamaps": "github:delapuente/datamaps#0.5.10", "datamaps": "github:delapuente/datamaps#0.5.10",
"eslint": "^5.16.0", "eslint": "^5.16.0",
@ -36,13 +40,16 @@
"eslint-plugin-standard": "^4.0.1", "eslint-plugin-standard": "^4.0.1",
"eslint-plugin-vue": "^5.2.3", "eslint-plugin-vue": "^5.2.3",
"frontmatter-markdown-loader": "^1.8.0", "frontmatter-markdown-loader": "^1.8.0",
"jest": "^24.9.0",
"markdown-it-anchor": "^5.2.4", "markdown-it-anchor": "^5.2.4",
"markdown-it-link-attributes": "^2.1.0", "markdown-it-link-attributes": "^2.1.0",
"node-sass": "^4.12.0", "node-sass": "^4.12.0",
"nodemon": "^1.19.2", "nodemon": "^1.19.2",
"sass-loader": "^8.0.0", "sass-loader": "^8.0.0",
"topojson": "^3.0.2", "topojson": "^3.0.2",
"ts-jest": "^24.1.0",
"uslug": "^1.0.4", "uslug": "^1.0.4",
"vue-jest": "^3.0.5",
"vue-property-decorator": "^8.2.2" "vue-property-decorator": "^8.2.2"
} }
} }

View File

@ -0,0 +1,39 @@
import { extractToc, formatTocLines, TocType } from '~/hooks/textbook-toc-utils'
const sampleHTML = `<p><strong>Chapter 0. Title 0</strong><br /></p>
<p>&nbsp; &nbsp; 0.0 <a href="./ch-prerequisites/python-and-jupyter-notebooks.html">Topic 0.0</a><br />
&nbsp; &nbsp; 0.1 <a href="./ch-prerequisites/qiskit.html">Topic 0.1</a><br />
<p><strong>Chapter 1. Title 1</strong><br /></p>
<p>&nbsp; &nbsp; 1.0 <a href="./ch-states/introduction.html">Topic 1.0</a><br />
&nbsp; &nbsp; 1.1 <a href="./ch-states/atoms-computation.html">Topic 1.1</a><br />`
const expectedToc: TocType = [
['Chapter 0. Title 0', [
'0.0 Topic 0.0',
'0.1 Topic 0.1'
]],
['Chapter 1. Title 1', [
'1.0 Topic 1.0',
'1.1 Topic 1.1'
]]
]
describe('extractToc', () => {
it('recognizes and matches chapters and topics', () => {
expect(extractToc(sampleHTML)).toEqual(expectedToc)
})
})
describe('formatTocLines', () => {
it('generates a simplified markdown version of the toc', () => {
expect(formatTocLines(expectedToc, 'Title')).toEqual([
'## Title',
'### Chapter 0. Title 0',
'- Topic 0.0',
'- Topic 0.1',
'### Chapter 1. Title 1',
'- Topic 1.0',
'- Topic 1.1'
])
})
})

View File

@ -26,7 +26,8 @@
}, },
"types": [ "types": [
"@types/node", "@types/node",
"@nuxt/types" "@nuxt/types",
"@types/jest"
] ]
}, },
"exclude": [ "exclude": [