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 new table-of-contents validator #1151

Merged
merged 14 commits into from
Apr 30, 2024
137 changes: 131 additions & 6 deletions package-lock.json

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

4 changes: 4 additions & 0 deletions packages/myst-toc/.eslintrc.cjs
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
module.exports = {
root: true,
extends: ['curvenote'],
};
1 change: 1 addition & 0 deletions packages/myst-toc/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/src/schema.json
35 changes: 35 additions & 0 deletions packages/myst-toc/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
# myst-toc

Utilities to parse a MyST table of contents.

## Overview

The MyST ToC format is defined in `types.ts`, from which a JSON schema definition is compiled. The high-level description is as follows:

1. A TOC comprises of an array of TOC items
2. Each TOC item can be

- A file (document)
- A URL (document)
- A collection of child items

3. TOC items containing children _must_ have either a `title` or a document

## Example

Example `myst.yml` under the `toc:` key:

```yaml
toc:
- title: Main
file: main.md
- title: Overview
children:
- file: overview-1.md
- file: overview-2.md
- url: https://google.com
- file: getting-started.md
children:
- file: getting-started-part-1.md
- file: getting-started-part-2.md
```
41 changes: 41 additions & 0 deletions packages/myst-toc/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
{
"name": "myst-toc",
"version": "0.0.0",
"sideEffects": false,
"license": "MIT",
"description": "MyST Table of Contents types and validation",
"author": "Angus Hollands <goosey15@gmail.com>",
"homepage": "https://github.com/executablebooks/mystmd/tree/main/packages/myst-toc",
"type": "module",
"exports": "./dist/index.js",
"types": "./dist/index.d.ts",
"files": [
"dist"
],
"publishConfig": {
"access": "public"
},
"repository": {
"type": "git",
"url": "git+https://github.com/executablebooks/mystmd.git"
},
"scripts": {
"clean": "rimraf dist ./src/schema.json",
"lint": "eslint \"src/**/!(*.spec).ts\" -c ./.eslintrc.cjs",
"lint:format": "npx prettier --check \"src/**/*.ts\"",
"test": "npm-run-all build:schema && vitest run",
"test:watch": "vitest watch",
"build:esm": "tsc",
"build:schema": "npx ts-json-schema-generator --path src/types.ts --type TOC -o ./src/schema.json",
"build": "npm-run-all -s -l clean build:schema build:esm"
},
"bugs": {
"url": "https://github.com/executablebooks/mystmd/issues"
},
"dependencies": {
"ajv": "^8.12.0"
},
"devDependencies": {
"ts-json-schema-generator": "^1.5.0"
}
}
13 changes: 13 additions & 0 deletions packages/myst-toc/src/guards.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { Entry, FileEntry, URLEntry, PatternEntry } from './types.js';

export function isFile(entry: Entry): entry is FileEntry {
return (entry as any).file !== undefined;
}

export function isURL(entry: Entry): entry is URLEntry {
return (entry as any).url !== undefined;
}

export function isPattern(entry: Entry): entry is PatternEntry {
return (entry as any).pattern !== undefined;
}
3 changes: 3 additions & 0 deletions packages/myst-toc/src/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
export * from './toc.js';
export * from './types.js';
export * from './guards.js';
21 changes: 21 additions & 0 deletions packages/myst-toc/src/toc.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
import type { TOC } from './types.js';
import schema from './schema.json';
import _Ajv from 'ajv';

/**
* validate a MyST table of contents
*
* @param toc: structured TOC data
*/
export function validateTOC(toc: Record<string, unknown>): TOC {
// eslint-disable-next-line
// @ts-ignore
const Ajv = _Ajv.default;
const ajv = new Ajv();
const validate = ajv.compile(schema);
if (!validate(toc)) {
throw new Error(`The given contents do not form a valid TOC.`);
}

return toc as unknown as TOC;
}
57 changes: 57 additions & 0 deletions packages/myst-toc/src/types.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
/**
* Common attributes for all TOC items
* Should be taken as a Partial<>
*/
export type CommonEntry = {
title?: string;
hidden?: boolean;
numbering?: string;
id?: string;
part?: string;
class?: string;
};

/**
* Entry that groups children, with no associated document
*/
export type ParentEntry = {
children: Entry[];
title: string;
} & CommonEntry;

/**
* Entry with a path to a single document with or without the file extension
*/
export type FileEntry = {
file: string;
} & CommonEntry;

/**
* Entry with a URL to an external URL
*/
export type URLEntry = {
url: string;
} & CommonEntry;

/**
* Entry representing several documents through a glob
*/
export type PatternEntry = {
pattern: string;
} & CommonEntry;

/**
* Entry representing a single document
*/
export type DocumentEntry = FileEntry | URLEntry;

/**
* All possible types of Entry
*/
export type Entry =
| DocumentEntry
| (DocumentEntry & Omit<ParentEntry, 'title'>)
| PatternEntry
| ParentEntry;

export type TOC = Entry[];