Skip to content

Commit

Permalink
🗳 Add new table-of-contents validator (#1151)
Browse files Browse the repository at this point in the history
  • Loading branch information
agoose77 authored Apr 30, 2024
1 parent 8851eca commit 50d0375
Show file tree
Hide file tree
Showing 12 changed files with 433 additions and 6 deletions.
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 <[email protected]>",
"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[];
Loading

0 comments on commit 50d0375

Please sign in to comment.