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

🧮 Numbering improvements for custom containers #790

Merged
merged 2 commits into from
Nov 30, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
7 changes: 7 additions & 0 deletions .changeset/silent-mirrors-guess.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'myst-frontmatter': patch
'myst-directives': patch
'myst-transforms': patch
---

Allow new numbering options to filter through. Add new `kind` option to figure directive
9 changes: 4 additions & 5 deletions docs/cross-references.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,9 +107,8 @@ Use `(label)=` before the element that you want to target, then reference conten
* [](#my-section)
```

````{tip}
:::{tip} How to turn on heading `numbering`
:class: dropdown
# How to turn on heading `numbering`

By default headings are not numbered, see [](#header-numbering) for more information. To turn on numbered headers you need to turn numbering on in the document or project using `numbering` in the frontmatter. You can control this for each heading level:

Expand All @@ -119,8 +118,8 @@ numbering:
heading_2: true
```

These will show up, for example, as `Section 1` and `Section 2.1`.
````
These will show up, for example, as `Section 1` and `Section 2.1`. To turn on all heading numbering, use `headings: true`.
:::

% TODO: We should support pandoc style unnumbered {-} and {.class, #id} syntax

Expand Down Expand Up @@ -251,7 +250,7 @@ ref
(numref-role)=

numref
: The `{numref}` role is exactly the same as the above `{ref}` role, but also allows you to use a `%s` in place of the number, which will get filled in when the content is rendered. For example, `` {numref}`Custom Table %s text <my-table-ref>`. `` will become `Custom Table 3 text`.
: The `{numref}` role is exactly the same as the above `{ref}` role, but also allows you to use a `%s` in place of the number, which will get filled in when the content is rendered. For example, ``{numref}`Custom Table %s text <my-table-ref>`.`` will become `Custom Table 3 text`.

(eq-role)=

Expand Down
6 changes: 5 additions & 1 deletion packages/myst-directives/src/figure.ts
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,10 @@ export const figureDirective: DirectiveSpec = {
doc: 'Disallow implicit subfigure creation from child nodes',
alias: ['no-subfig', 'no-subfigure'],
},
kind: {
type: String,
doc: 'Override the figures "kind", which changes the enumeration to start counting independently for that kind. For example, `kind: "example"`. The default enumeration and referencing will be the capitalized `kind` followed by a number (e.g. "Example 1").',
},
},
body: {
type: 'myst',
Expand Down Expand Up @@ -101,7 +105,7 @@ export const figureDirective: DirectiveSpec = {
const { label, identifier } = normalizeLabel(data.options?.label as string | undefined) || {};
const container: GenericParent = {
type: 'container',
kind: 'figure',
kind: (data.options?.kind as string) || 'figure',
identifier,
label,
class: data.options?.class,
Expand Down
35 changes: 29 additions & 6 deletions packages/myst-frontmatter/src/numbering/numbering.yml
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,25 @@ cases:
- title: empty object returns self
raw:
numbering: {}
normalized:
numbering: {}
- title: extra keys removed
normalized: {}
- title: invalid object errors
raw:
numbering: 1
normalized: {}
errors: 1
- title: extra keys are kept
raw:
numbering:
extra: ''
list: true
normalized:
numbering: {}
warnings: 1
numbering:
list: true
- title: invalid extras keys are removed
raw:
numbering:
list: 1
normalized: {}
errors: 1
- title: full object returns self
raw:
numbering:
Expand Down Expand Up @@ -39,3 +49,16 @@ cases:
heading_4: true
heading_5: true
heading_6: true
- title: headings unpack
raw:
numbering:
sections: true # alias for "headings"
h3: false # alias for heading_3
normalized:
numbering:
heading_1: true
heading_2: true
heading_3: false
heading_4: true
heading_5: true
heading_6: true
68 changes: 48 additions & 20 deletions packages/myst-frontmatter/src/numbering/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,39 +3,67 @@ import {
defined,
incrementOptions,
validateBoolean,
validateObjectKeys,
validateKeys,
validateObject,
validateString,
} from 'simple-validators';
import type { Numbering } from './types.js';

export const NUMBERING_KEYS = [
'enumerator',
'figure',
'equation',
'table',
'code',
'heading_1',
'heading_2',
'heading_3',
'heading_4',
'heading_5',
'heading_6',
];
export const NUMBERING_OPTIONS = ['enumerator', 'headings'];

const HEADING_KEYS = ['heading_1', 'heading_2', 'heading_3', 'heading_4', 'heading_5', 'heading_6'];
export const NUMBERING_KEYS = ['figure', 'equation', 'table', 'code', ...HEADING_KEYS];
Copy link
Collaborator

Choose a reason for hiding this comment

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

And then I guess this just works with the enumerate transform, since it already increments count based on kind. Nice.

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, I was impressed at how much of this already just worked and all we needed to do was to be less strict in validation!


export const NUMBERING_ALIAS = {
sections: 'headings',
h1: 'heading_1',
h2: 'heading_2',
h3: 'heading_3',
h4: 'heading_4',
h5: 'heading_5',
h6: 'heading_6',
heading1: 'heading_1',
heading2: 'heading_2',
heading3: 'heading_3',
heading4: 'heading_4',
heading5: 'heading_5',
heading6: 'heading_6',
};

/**
* Validate Numbering object
*/
export function validateNumbering(input: any, opts: ValidationOptions): Numbering | undefined {
const value = validateObjectKeys(input, { optional: NUMBERING_KEYS }, opts);
const obj = validateObject(input, opts);
if (obj === undefined) return undefined;
const value = validateKeys(
obj,
{ optional: [...NUMBERING_KEYS, ...NUMBERING_OPTIONS], alias: NUMBERING_ALIAS },
// Do not add warnings on this filter process
{ property: '', messages: { errors: [], warnings: [] }, keepExtraKeys: true },
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can also use suppressErrors/suppressWarnings - which will just prevent logging entirely.

Copy link
Member Author

Choose a reason for hiding this comment

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

Fixed in #794.

);
if (value === undefined) return undefined;
const output: Record<string, any> = {};
if (defined(value.enumerator)) {
output.enumerator = validateString(value.enumerator, incrementOptions('enumerator', opts));
}
NUMBERING_KEYS.filter((key) => key !== 'enumerator').forEach((key) => {
if (defined(value[key])) {
output[key] = validateBoolean(value[key], incrementOptions(key, opts));
}
});
if (defined(value.headings)) {
const headings = validateBoolean(value.headings, incrementOptions('headings', opts));
HEADING_KEYS.forEach((headingKey) => {
if (headings && !defined(value[headingKey])) {
// This will be validated next!
value[headingKey] = true;
Copy link
Collaborator

Choose a reason for hiding this comment

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

👍

}
});
}
Object.keys(value)
.filter((key) => !NUMBERING_OPTIONS.includes(key)) // For all the unknown options
.forEach((key) => {
if (defined(value[key])) {
const bool = validateBoolean(value[key], incrementOptions(key, opts));
if (defined(bool)) output[key] = bool;
}
});
if (Object.keys(output).length === 0) return undefined;
return output;
}
2 changes: 0 additions & 2 deletions packages/myst-frontmatter/src/page/page.yml
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,6 @@ cases:
subject: ''
biblio: {}
oxa: ''
numbering: {}
math:
a: b
subtitle: sub
Expand Down Expand Up @@ -80,7 +79,6 @@ cases:
subject: ''
biblio: {}
oxa: ''
numbering: {}
math:
a: b
subtitle: sub
Expand Down
2 changes: 0 additions & 2 deletions packages/myst-frontmatter/src/project/project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -38,7 +38,6 @@ cases:
subject: ''
biblio: {}
oxa: ''
numbering: {}
math:
a: b
keywords:
Expand Down Expand Up @@ -87,7 +86,6 @@ cases:
subject: ''
biblio: {}
oxa: ''
numbering: {}
math:
a: b
keywords:
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,6 @@ const TEST_PAGE_FRONTMATTER: PageFrontmatter = {
subject: '',
biblio: {},
oxa: '',
numbering: {},
math: { a: 'b' },
subtitle: 'sub',
short_title: 'short',
Expand Down Expand Up @@ -64,7 +63,6 @@ const TEST_PROJECT_FRONTMATTER: ProjectFrontmatter = {
subject: '',
biblio: {},
oxa: '',
numbering: {},
math: { a: 'b' },
keywords: ['example', 'test'],
exports: [
Expand Down
6 changes: 3 additions & 3 deletions packages/myst-frontmatter/tests/examples.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,10 +40,10 @@ const casesList = files
});

casesList.forEach(({ title, frontmatter, cases }) => {
const casesToUse = cases.filter((c) => (!only && !c.skip) || (only && c.title === only));
const skippedCases = cases.filter((c) => c.skip || (only && c.title !== only));
if (casesToUse.length === 0) return;
describe(title, () => {
const casesToUse = cases.filter((c) => (!only && !c.skip) || (only && c.title === only));
const skippedCases = cases.filter((c) => c.skip || (only && c.title !== only));
if (casesToUse.length === 0) return;
if (skippedCases.length > 0) {
// Log to test output for visibility
test.skip.each(skippedCases.map((c): [string, TestCase] => [c.title, c]))('%s', () => {});
Expand Down
12 changes: 3 additions & 9 deletions packages/myst-transforms/src/enumerate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -476,7 +476,7 @@
this.filePath = filePath;
}

resolveStateProvider(identifier?: string, page?: string): StateAndFile | undefined {

Check warning on line 479 in packages/myst-transforms/src/enumerate.ts

View workflow job for this annotation

GitHub Actions / lint

'page' is defined but never used
if (!identifier) return undefined;
const local = this.fileState.getTarget(identifier);
if (local) {
Expand Down Expand Up @@ -553,15 +553,9 @@

function getCaptionLabel(kind?: string, subcontainer?: boolean) {
if (subcontainer) return `(%s)`;
switch (kind) {
case 'table':
return `Table %s:`;
case 'code':
return `Program %s:`;
case 'figure':
default:
return `Figure %s:`;
}
if (!kind) return 'Figure %s:';
const template = getDefaultNumberedReferenceLabel(kind);
return `${template}:`;
}

/** Visit all containers and add captionNumber node to caption paragraph
Expand Down
Loading