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 enumerator customization to numbering items #1803

Merged
merged 2 commits into from
Jan 23, 2025
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
6 changes: 6 additions & 0 deletions .changeset/old-houses-stare.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
---
'myst-frontmatter': patch
'myst-transforms': patch
---

Add enumerator customization to numbering items
17 changes: 16 additions & 1 deletion packages/myst-frontmatter/src/numbering/numbering.yml
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,7 @@ cases:
normalized:
numbering:
enumerator:
template: ''
enumerator: ''
all:
enabled: true
figure:
Expand Down Expand Up @@ -268,3 +268,18 @@ cases:
all:
enabled: true
warnings: 3
- title: enumerator validates
raw:
numbering:
title:
enumerator: '{number}'
figure:
enumerator: '{number}'
normalized:
numbering:
title:
enumerator: '{number}'
enabled: true
figure:
enumerator: '{number}'
enabled: true
5 changes: 3 additions & 2 deletions packages/myst-frontmatter/src/numbering/types.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
export type NumberingItem = {
enabled?: boolean;
start?: number;
enumerator?: string;
template?: string;
continue?: boolean;
offset?: number; // only applies to title
};

export type Numbering = {
enumerator?: NumberingItem; // start, enabled, and continue ignored
all?: NumberingItem; // start and template ignored
enumerator?: NumberingItem; // start, enabled, continue, and template ignored
all?: NumberingItem; // start, template, enumerator ignored
title?: NumberingItem; // start, continue, and template ignored
figure?: NumberingItem;
subfigure?: NumberingItem;
Expand Down
24 changes: 21 additions & 3 deletions packages/myst-frontmatter/src/numbering/validators.ts
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export const NUMBERING_KEYS = [
...HEADING_KEYS,
];

const NUMBERING_ITEM_KEYS = ['enabled', 'start', 'template', 'continue'];
const NUMBERING_ITEM_KEYS = ['enabled', 'start', 'enumerator', 'template', 'continue'];

const CONTINUE_STRINGS = ['continue', 'next'];

Expand Down Expand Up @@ -111,6 +111,13 @@ export function validateNumberingItem(
output.enabled = output.enabled ?? true;
}
}
if (defined(value.enumerator)) {
const enumerator = validateString(value.enumerator, incrementOptions('enumerator', opts));
if (defined(enumerator)) {
output.enumerator = enumerator;
output.enabled = output.enabled ?? true;
}
}
if (defined(value.continue)) {
const cont = validateBoolean(value.continue, incrementOptions('continue', opts));
if (defined(cont)) {
Expand All @@ -121,15 +128,16 @@ export function validateNumberingItem(
if (Object.keys(output).length === 0) return undefined;
return output;
}

export function validateTitleItem(input: any, opts: ValidationOptions): NumberingItem | undefined {
if (isBoolean(input)) {
input = { enabled: input };
} else if (typeof input === 'number') {
input = { offset: input };
}
const value = validateObjectKeys(input, { optional: ['enabled', 'offset'] }, opts);
const value = validateObjectKeys(input, { optional: ['enabled', 'offset', 'enumerator'] }, opts);
if (value === undefined) return undefined;
const output: { enabled?: boolean; offset?: number } = {};
const output: { enabled?: boolean; offset?: number; enumerator?: string } = {};
if (defined(value.enabled)) {
const enabled = validateBoolean(value.enabled, incrementOptions('enabled', opts));
if (defined(enabled)) output.enabled = enabled;
Expand All @@ -146,6 +154,13 @@ export function validateTitleItem(input: any, opts: ValidationOptions): Numberin
output.enabled = output.enabled ?? true;
}
}
if (defined(value.enumerator)) {
const enumerator = validateString(value.enumerator, incrementOptions('enumerator', opts));
if (defined(enumerator)) {
output.enumerator = enumerator;
output.enabled = output.enabled ?? true;
}
}
if (Object.keys(output).length === 0) return undefined;
return output;
}
Expand All @@ -167,6 +182,9 @@ export function validateNumbering(input: any, opts: ValidationOptions): Numberin
let headings: NumberingItem | undefined;
if (defined(value.enumerator)) {
const enumeratorOpts = incrementOptions('enumerator', opts);
if (typeof value.enumerator === 'string') {
value.enumerator = { enumerator: value.enumerator };
}
output.enumerator = validateNumberingItem(value.enumerator, enumeratorOpts);
if (output.enumerator?.enabled != null) {
if (output.enumerator.enabled !== true) {
Expand Down
24 changes: 22 additions & 2 deletions packages/myst-transforms/src/enumerate.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,26 @@ describe('Heading counts and formatting', () => {
});

describe('enumeration', () => {
test('figure enumerators', () => {
const tree = u('root', [
u('heading', { identifier: 'ha', depth: 2 }),
u('heading', { identifier: 'hb', depth: 3 }),
u('heading', { identifier: 'hc', depth: 3 }),
u('container', { kind: 'figure', identifier: 'fig1' }),
]);
const state = new ReferenceState('my-file.md', {
frontmatter: {
numbering: {
heading_1: { enabled: true },
heading_2: { enabled: true },
figure: { enumerator: 'FancyTemplateSoon.%s' },
},
},
vfile: new VFile(),
});
enumerateTargetsTransform(tree, { state });
expect(state.getTarget('fig1')?.node.enumerator).toBe('FancyTemplateSoon.1');
});
test('sub-equations', () => {
const tree = u('root', [
u('mathGroup', { identifier: 'eq:1' }, [
Expand All @@ -51,7 +71,7 @@ describe('enumeration', () => {
]),
]);
const state = new ReferenceState('my-file.md', {
frontmatter: { numbering: { enumerator: { template: 'A.%s' } } },
frontmatter: { numbering: { enumerator: { enumerator: 'A.%s' } } },
vfile: new VFile(),
});
enumerateTargetsTransform(tree, { state });
Expand Down Expand Up @@ -91,7 +111,7 @@ describe('enumeration', () => {
u('container', { identifier: 'fig:2', kind: 'figure' }, []),
]);
const state = new ReferenceState('my-file.md', {
frontmatter: { numbering: { enumerator: { template: 'A.%s' } } },
frontmatter: { numbering: { enumerator: { enumerator: 'A.%s' } } },
vfile: new VFile(),
});
enumerateTargetsTransform(tree, { state });
Expand Down
26 changes: 18 additions & 8 deletions packages/myst-transforms/src/enumerate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -360,7 +360,7 @@ export class ReferenceState implements IReferenceStateResolver {
);
this.enumerator = formatHeadingEnumerator(
this.targetCounts.heading,
this.numbering.enumerator?.template,
this.numbering.title?.enumerator ?? this.numbering.enumerator?.enumerator,
);
}
this.identifiers = opts?.identifiers ?? [];
Expand All @@ -376,7 +376,6 @@ export class ReferenceState implements IReferenceStateResolver {
if (!isTargetIdentifierNode(node)) return;
const kind = kindFromNode(node);
const numberNode = shouldEnumerateNode(node, kind, this.numbering, this.offset);
// if (numberNode && !node.enumerator) { // Is this change a problem?
if (numberNode) {
this.incrementCount(node, kind as TargetKind);
}
Expand All @@ -400,8 +399,8 @@ export class ReferenceState implements IReferenceStateResolver {
};
}

resolveEnumerator(val: any): string {
const prefix = this.numbering.enumerator?.template;
resolveEnumerator(val: any, enumerator?: string): string {
const prefix = enumerator ?? this.numbering.enumerator?.enumerator;
return prefix ? prefix.replace(/%s/g, String(val)) : String(val);
}

Expand All @@ -427,7 +426,9 @@ export class ReferenceState implements IReferenceStateResolver {
);
enumerator = formatHeadingEnumerator(
this.targetCounts.heading,
this.numbering.enumerator?.template,
this.numbering[
`heading_${node.depth - (this.numbering?.title?.enabled ? 0 : 1) + this.offset}`
]?.enumerator ?? this.numbering.enumerator?.enumerator,
);
node.enumerator = enumerator;
return enumerator;
Expand All @@ -442,15 +443,24 @@ export class ReferenceState implements IReferenceStateResolver {
((this.targetCounts[countKind].sub - 1) % 26) + 'a'.charCodeAt(0),
);
if (node.subcontainer) {
node.parentEnumerator = this.resolveEnumerator(this.targetCounts[countKind].main);
node.parentEnumerator = this.resolveEnumerator(
this.targetCounts[countKind].main,
this.numbering[countKind]?.enumerator,
);
enumerator = letter;
} else {
enumerator = this.resolveEnumerator(this.targetCounts[countKind].main + letter);
enumerator = this.resolveEnumerator(
this.targetCounts[countKind].main + letter,
this.numbering[countKind]?.enumerator,
);
}
} else {
this.targetCounts[kind].main += 1;
this.targetCounts[kind].sub = 0;
enumerator = this.resolveEnumerator(this.targetCounts[kind].main);
enumerator = this.resolveEnumerator(
this.targetCounts[kind].main,
this.numbering[kind]?.enumerator,
);
}
node.enumerator = enumerator;
return enumerator;
Expand Down
2 changes: 1 addition & 1 deletion packages/myst-transforms/tests/enumerators.yml
Original file line number Diff line number Diff line change
Expand Up @@ -717,7 +717,7 @@ cases:
opts:
numbering:
enumerator:
template: A%s
enumerator: A%s
heading_1:
enabled: true
before:
Expand Down