feat(bgm): derive definition names from role.type
A definition is identified by role.type and named role.type.lang (package.lang for packages). Code blocks declare it via role= on the info string; real files by their filename. Blocks and files without a matching role.type are ignored. file= still overrides the auto-name. Relative asset and $variants paths now resolve against the markdown file's directory (baseDir) rather than the virtual name. Same role.type with different ids groups under one name; duplicate type#id still errors. Migrate fixtures and example games to role=.
This commit is contained in:
@@ -2,12 +2,12 @@ import { describe, expect, it } from 'vitest';
|
||||
import { scanMarkdown } from './markdown.js';
|
||||
|
||||
describe('scanMarkdown', () => {
|
||||
it('extracts a fenced code block with a file= name', () => {
|
||||
it('names a block from its role= as role.type.lang', () => {
|
||||
const md = [
|
||||
'# Title',
|
||||
'',
|
||||
'```yaml file=parts/cargo.yaml',
|
||||
'role: part',
|
||||
'```yaml role=part.cargo',
|
||||
'id: wood',
|
||||
'```',
|
||||
'',
|
||||
'text after',
|
||||
@@ -17,66 +17,72 @@ describe('scanMarkdown', () => {
|
||||
|
||||
expect(fences).toHaveLength(1);
|
||||
expect(fences[0]).toMatchObject({
|
||||
info: 'yaml file=parts/cargo.yaml',
|
||||
content: 'role: part',
|
||||
info: 'yaml role=part.cargo',
|
||||
content: 'id: wood',
|
||||
startLine: 3,
|
||||
endLine: 5,
|
||||
});
|
||||
expect(files).toHaveLength(1);
|
||||
expect(files[0]).toMatchObject({
|
||||
name: 'harbor/parts/cargo.yaml',
|
||||
name: 'harbor/part.cargo.yaml',
|
||||
kind: 'yaml',
|
||||
text: 'role: part',
|
||||
text: 'id: wood',
|
||||
source: 'harbor/harbor.md:3-5',
|
||||
role: { role: 'part', type: 'cargo' },
|
||||
});
|
||||
});
|
||||
|
||||
it('auto-names a block without file= from its content hash', () => {
|
||||
const md = '```yaml\nrole: part\n```';
|
||||
it('names a package block package.yaml', () => {
|
||||
const md = '```yaml role=package\nid: harbor\n```';
|
||||
const { files } = scanMarkdown(md, 'harbor/harbor.md');
|
||||
expect(files).toHaveLength(1);
|
||||
expect(files[0]!.name).toMatch(/^harbor\/[0-9a-f]{8}\.yaml$/);
|
||||
expect(files[0]!.kind).toBe('yaml');
|
||||
expect(files[0]).toMatchObject({ name: 'harbor/package.yaml', role: { role: 'package' } });
|
||||
});
|
||||
|
||||
it('hashes identical blocks to the same auto-name', () => {
|
||||
const md = ['```yaml\nrole: part\n```', '', '```yaml\nrole: part\n```'].join('\n');
|
||||
const { files } = scanMarkdown(md, 'harbor/harbor.md');
|
||||
expect(files).toHaveLength(2);
|
||||
expect(files[0]!.name).toBe(files[1]!.name);
|
||||
});
|
||||
|
||||
it('parses role= metadata from the info string', () => {
|
||||
const md = '```yaml file=cards.yaml role=part.poker-card\nid: 2s\n```';
|
||||
it('names a surface block with type and id', () => {
|
||||
const md = '```yaml role=surface.game#main\n```';
|
||||
const { files } = scanMarkdown(md, 'poker/poker.md');
|
||||
expect(files[0]).toMatchObject({
|
||||
name: 'poker/cards.yaml',
|
||||
role: { role: 'part', type: 'poker-card' },
|
||||
name: 'poker/surface.game.yaml',
|
||||
role: { role: 'surface', type: 'game', id: 'main' },
|
||||
});
|
||||
});
|
||||
|
||||
it('parses role= with type and id', () => {
|
||||
const md = '```yaml file=board.yaml role=surface.game#main\n```';
|
||||
const { files } = scanMarkdown(md, 'poker/poker.md');
|
||||
expect(files[0]!.role).toEqual({ role: 'surface', type: 'game', id: 'main' });
|
||||
it('uses file= to override the role.type name', () => {
|
||||
const md = '```yaml file=parts/cargo.yaml role=part.cargo\n```';
|
||||
const { files } = scanMarkdown(md, 'harbor/harbor.md');
|
||||
expect(files[0]).toMatchObject({
|
||||
name: 'harbor/parts/cargo.yaml',
|
||||
role: { role: 'part', type: 'cargo' },
|
||||
});
|
||||
});
|
||||
|
||||
it('parses role=package without type or id', () => {
|
||||
const md = '```yaml file=game.yaml role=package\n```';
|
||||
const { files } = scanMarkdown(md, 'poker/poker.md');
|
||||
expect(files[0]!.role).toEqual({ role: 'package' });
|
||||
it('ignores a block without role= or file=', () => {
|
||||
const md = '```yaml\nrole: part\n```';
|
||||
const { files } = scanMarkdown(md, 'harbor/harbor.md');
|
||||
expect(files).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('names a csv block with file= as csv', () => {
|
||||
const md = '```csv file=parts/seats.csv\nseat,x\nstring,number\n0,40\n```';
|
||||
const { files } = scanMarkdown(md, 'harbor/harbor.md');
|
||||
expect(files[0]).toMatchObject({ name: 'harbor/parts/seats.csv', kind: 'csv' });
|
||||
});
|
||||
|
||||
it('throws on an unknown role=', () => {
|
||||
const md = '```yaml file=a.yaml role=widget\n```';
|
||||
const md = '```yaml role=widget\n```';
|
||||
expect(() => scanMarkdown(md, 'poker/poker.md')).toThrow(/Invalid role/);
|
||||
});
|
||||
|
||||
it('throws on role=package with a type', () => {
|
||||
const md = '```yaml file=a.yaml role=package.foo\n```';
|
||||
const md = '```yaml role=package.foo\n```';
|
||||
expect(() => scanMarkdown(md, 'poker/poker.md')).toThrow(/Package role takes no type/);
|
||||
});
|
||||
|
||||
it('throws on a role without a type', () => {
|
||||
const md = '```yaml role=part\n```';
|
||||
expect(() => scanMarkdown(md, 'poker/poker.md')).toThrow(/requires a type/);
|
||||
});
|
||||
|
||||
it('ignores non-definition languages', () => {
|
||||
const md = '```js\nconst x = 1;\n```';
|
||||
const { files, fences } = scanMarkdown(md, 'harbor/harbor.md');
|
||||
@@ -90,23 +96,15 @@ describe('scanMarkdown', () => {
|
||||
expect(files).toHaveLength(0);
|
||||
});
|
||||
|
||||
it('names a csv block with file= as csv', () => {
|
||||
const md = '```csv file=parts/seats.csv\nseat,x\nstring,number\n0,40\n```';
|
||||
const { files } = scanMarkdown(md, 'harbor/harbor.md');
|
||||
expect(files[0]).toMatchObject({ name: 'harbor/parts/seats.csv', kind: 'csv' });
|
||||
});
|
||||
|
||||
it('tracks line numbers across multiple blocks', () => {
|
||||
const md = [
|
||||
'```yaml file=a.yaml',
|
||||
'role: part',
|
||||
'```yaml role=part.a',
|
||||
'```',
|
||||
'',
|
||||
'```yaml file=b.yaml',
|
||||
'role: part',
|
||||
'```yaml role=part.b',
|
||||
'```',
|
||||
].join('\n');
|
||||
const { fences } = scanMarkdown(md, 'harbor/harbor.md');
|
||||
expect(fences.map((f) => f.startLine)).toEqual([1, 5]);
|
||||
expect(fences.map((f) => f.startLine)).toEqual([1, 4]);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user