@art-md/constructs 0.0.1 → 0.0.3
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/package.json +2 -2
- package/src/factories/constructs/Document/factory/createDocument.test.ts +17 -0
- package/src/factories/constructs/Document/factory/createDocument.ts +11 -0
- package/src/factories/constructs/Document/factory/types.ts +5 -0
- package/src/factories/constructs/Document/index.ts +3 -0
- package/src/factories/constructs/FieldBlock/factory/createFieldBlock.test.ts +26 -0
- package/src/factories/constructs/FieldBlock/factory/createFieldBlock.ts +15 -0
- package/src/factories/constructs/FieldBlock/factory/types.ts +8 -0
- package/src/factories/constructs/FieldBlock/index.ts +4 -0
- package/src/factories/constructs/FieldBlock/types.ts +11 -0
- package/src/factories/constructs/FieldInline/factory/createFieldInline.test.ts +26 -0
- package/src/factories/constructs/FieldInline/factory/createFieldInline.ts +15 -0
- package/src/factories/constructs/FieldInline/factory/types.ts +8 -0
- package/src/factories/constructs/FieldInline/index.ts +4 -0
- package/src/factories/constructs/FieldInline/types.ts +11 -0
- package/src/factories/constructs/NaturalBlock/factory/createNaturalBlock.test.ts +36 -0
- package/src/factories/constructs/NaturalBlock/factory/createNaturalBlock.ts +25 -0
- package/src/factories/constructs/NaturalBlock/factory/types.ts +13 -0
- package/src/factories/constructs/NaturalBlock/index.ts +4 -0
- package/src/factories/constructs/NaturalBlock/types.ts +14 -0
- package/src/factories/constructs/NaturalExpression/factory/createNaturalExpression.test.ts +22 -0
- package/src/factories/constructs/NaturalExpression/factory/createNaturalExpression.ts +13 -0
- package/src/factories/constructs/NaturalExpression/factory/types.ts +8 -0
- package/src/factories/constructs/NaturalExpression/index.ts +4 -0
- package/src/factories/constructs/NaturalExpression/types.ts +9 -0
- package/src/factories/constructs/SectionBlock/factory/createSectionBlock.test.ts +30 -0
- package/src/factories/constructs/SectionBlock/factory/createSectionBlock.ts +15 -0
- package/src/factories/constructs/SectionBlock/factory/types.ts +11 -0
- package/src/factories/constructs/SectionBlock/index.ts +4 -0
- package/src/factories/constructs/SectionBlock/types.ts +12 -0
- package/src/factories/constructs/Tag/factory/createTag.test.ts +10 -0
- package/src/factories/constructs/Tag/factory/createTag.ts +7 -0
- package/src/factories/constructs/Tag/factory/types.ts +3 -0
- package/src/factories/constructs/Tag/index.ts +4 -0
- package/src/factories/constructs/Tag/types.ts +6 -0
- package/src/factories/index.ts +9 -0
- package/src/factories/public.ts +47 -0
- package/src/factories/types.ts +36 -0
- package/src/index.ts +3 -0
- package/src/parser/constructs/FieldBlock/createFieldBlockParser.test.ts +13 -0
- package/src/parser/constructs/FieldBlock/createFieldBlockParser.ts +9 -0
- package/src/parser/constructs/FieldBlock/index.ts +1 -0
- package/src/parser/constructs/FieldBlock/private/helpers/createFieldBlockFromNode.test.ts +63 -0
- package/src/parser/constructs/FieldBlock/private/helpers/createFieldBlockFromNode.ts +33 -0
- package/src/parser/constructs/FieldBlock/private/helpers/onBeforeConstruct.test.ts +30 -0
- package/src/parser/constructs/FieldBlock/private/helpers/onBeforeConstruct.ts +19 -0
- package/src/parser/constructs/FieldBlock/private/index.ts +2 -0
- package/src/parser/constructs/FieldBlock/private/integrator/createFieldBlockIntegrator.test.ts +23 -0
- package/src/parser/constructs/FieldBlock/private/integrator/createFieldBlockIntegrator.ts +13 -0
- package/src/parser/constructs/FieldBlock/private/processor/createFieldBlockProcessor.test.ts +63 -0
- package/src/parser/constructs/FieldBlock/private/processor/createFieldBlockProcessor.ts +21 -0
- package/src/parser/constructs/FieldInline/createFieldInlineParser.test.ts +12 -0
- package/src/parser/constructs/FieldInline/createFieldInlineParser.ts +8 -0
- package/src/parser/constructs/FieldInline/index.ts +1 -0
- package/src/parser/constructs/FieldInline/private/helpers/createFieldInlineFromNode.test.ts +100 -0
- package/src/parser/constructs/FieldInline/private/helpers/createFieldInlineFromNode.ts +51 -0
- package/src/parser/constructs/FieldInline/private/helpers/trimFieldEdges.test.ts +48 -0
- package/src/parser/constructs/FieldInline/private/helpers/trimFieldEdges.ts +18 -0
- package/src/parser/constructs/FieldInline/private/index.ts +1 -0
- package/src/parser/constructs/FieldInline/private/processor/createFieldInlineProcessor.test.ts +57 -0
- package/src/parser/constructs/FieldInline/private/processor/createFieldInlineProcessor.ts +21 -0
- package/src/parser/constructs/NaturalBlock/createNaturalBlockParser.test.ts +12 -0
- package/src/parser/constructs/NaturalBlock/createNaturalBlockParser.ts +8 -0
- package/src/parser/constructs/NaturalBlock/index.ts +1 -0
- package/src/parser/constructs/NaturalBlock/private/helpers/createNaturalBlockFromNode.test.ts +101 -0
- package/src/parser/constructs/NaturalBlock/private/helpers/createNaturalBlockFromNode.ts +51 -0
- package/src/parser/constructs/NaturalBlock/private/index.ts +1 -0
- package/src/parser/constructs/NaturalBlock/private/processor/createNaturalBlockProcessor.test.ts +24 -0
- package/src/parser/constructs/NaturalBlock/private/processor/createNaturalBlockProcessor.ts +10 -0
- package/src/parser/constructs/SectionBlock/createSectionBlockParser.test.ts +13 -0
- package/src/parser/constructs/SectionBlock/createSectionBlockParser.ts +9 -0
- package/src/parser/constructs/SectionBlock/index.ts +1 -0
- package/src/parser/constructs/SectionBlock/private/constants.test.ts +17 -0
- package/src/parser/constructs/SectionBlock/private/constants.ts +1 -0
- package/src/parser/constructs/SectionBlock/private/helpers/createSectionBlockFromNode.test.ts +58 -0
- package/src/parser/constructs/SectionBlock/private/helpers/createSectionBlockFromNode.ts +28 -0
- package/src/parser/constructs/SectionBlock/private/helpers/findParentSection.test.ts +44 -0
- package/src/parser/constructs/SectionBlock/private/helpers/findParentSection.ts +14 -0
- package/src/parser/constructs/SectionBlock/private/index.ts +2 -0
- package/src/parser/constructs/SectionBlock/private/integrator/createSectionBlockIntegrator.test.ts +60 -0
- package/src/parser/constructs/SectionBlock/private/integrator/createSectionBlockIntegrator.ts +31 -0
- package/src/parser/constructs/SectionBlock/private/processor/createSectionBlockProcessor.test.ts +34 -0
- package/src/parser/constructs/SectionBlock/private/processor/createSectionBlockProcessor.ts +15 -0
- package/src/parser/document/createArtDocumentFromNode.test.ts +23 -0
- package/src/parser/document/createArtDocumentFromNode.ts +10 -0
- package/src/parser/document/index.ts +1 -0
- package/src/parser/fields/constants.test.ts +23 -0
- package/src/parser/fields/constants.ts +1 -0
- package/src/parser/fields/index.ts +3 -0
- package/src/parser/fields/isFieldStrong.test.ts +27 -0
- package/src/parser/fields/isFieldStrong.ts +9 -0
- package/src/parser/fields/stripStrong.test.ts +38 -0
- package/src/parser/fields/stripStrong.ts +12 -0
- package/src/parser/index.ts +8 -0
- package/src/parser/mdast/index.ts +1 -0
- package/src/parser/mdast/rawSlice.test.ts +43 -0
- package/src/parser/mdast/rawSlice.ts +8 -0
- package/src/parser/naturalExpression/createNaturalExpressionFromNode.test.ts +40 -0
- package/src/parser/naturalExpression/createNaturalExpressionFromNode.ts +25 -0
- package/src/parser/naturalExpression/index.ts +1 -0
- package/src/parser/public.ts +16 -0
- package/src/parser/tags/extractTags.test.ts +85 -0
- package/src/parser/tags/extractTags.ts +23 -0
- package/src/parser/tags/index.ts +1 -0
- package/src/parser/types.ts +21 -0
- package/src/serializer/constructs/Document/createDocumentToMdast.test.ts +22 -0
- package/src/serializer/constructs/Document/createDocumentToMdast.ts +15 -0
- package/src/serializer/constructs/Document/index.ts +1 -0
- package/src/serializer/constructs/FieldBlock/createFieldBlockToMdast.test.ts +36 -0
- package/src/serializer/constructs/FieldBlock/createFieldBlockToMdast.ts +26 -0
- package/src/serializer/constructs/FieldBlock/index.ts +1 -0
- package/src/serializer/constructs/FieldInline/createFieldInlineToMdast.test.ts +38 -0
- package/src/serializer/constructs/FieldInline/createFieldInlineToMdast.ts +27 -0
- package/src/serializer/constructs/FieldInline/index.ts +1 -0
- package/src/serializer/constructs/NaturalBlock/createNaturalBlockToMdast.test.ts +122 -0
- package/src/serializer/constructs/NaturalBlock/createNaturalBlockToMdast.ts +30 -0
- package/src/serializer/constructs/NaturalBlock/index.ts +1 -0
- package/src/serializer/constructs/NaturalExpression/createNaturalExpressionToMdast.test.ts +29 -0
- package/src/serializer/constructs/NaturalExpression/createNaturalExpressionToMdast.ts +15 -0
- package/src/serializer/constructs/NaturalExpression/index.ts +1 -0
- package/src/serializer/constructs/SectionBlock/createSectionBlockToMdast.test.ts +83 -0
- package/src/serializer/constructs/SectionBlock/createSectionBlockToMdast.ts +29 -0
- package/src/serializer/constructs/SectionBlock/index.ts +1 -0
- package/src/serializer/index.ts +8 -0
- package/src/serializer/public.ts +18 -0
- package/src/serializer/tags/index.ts +2 -0
- package/src/serializer/tags/tagToMdast.test.ts +10 -0
- package/src/serializer/tags/tagToMdast.ts +7 -0
- package/src/serializer/tags/tagsToMdast.test.ts +19 -0
- package/src/serializer/tags/tagsToMdast.ts +10 -0
- package/src/serializer/types.ts +10 -0
- package/src/test/helpers/constructs/FieldBlock/createFieldBlockFromNodeMock.ts +17 -0
- package/src/test/helpers/constructs/FieldBlock/createFieldBlockMock.ts +17 -0
- package/src/test/helpers/constructs/FieldBlock/makeFieldBlockFixture.ts +16 -0
- package/src/test/helpers/constructs/FieldBlock/makeFieldBlockMock.ts +16 -0
- package/src/test/helpers/constructs/FieldInline/createFieldInlineFromNodeMock.ts +17 -0
- package/src/test/helpers/constructs/FieldInline/createFieldInlineMock.ts +17 -0
- package/src/test/helpers/constructs/FieldInline/makeFieldInlineFixture.ts +16 -0
- package/src/test/helpers/constructs/FieldInline/makeFieldInlineMock.ts +16 -0
- package/src/test/helpers/constructs/NaturalBlock/createNaturalBlockFromNodeMock.ts +17 -0
- package/src/test/helpers/constructs/NaturalBlock/createNaturalBlockMock.ts +17 -0
- package/src/test/helpers/constructs/NaturalBlock/makeNaturalBlockFixture.ts +22 -0
- package/src/test/helpers/constructs/NaturalBlock/makeNaturalBlockMock.ts +22 -0
- package/src/test/helpers/constructs/NaturalExpression/makeNaturalExpressionFixture.ts +18 -0
- package/src/test/helpers/constructs/NaturalExpression/makeNaturalExpressionMock.ts +18 -0
- package/src/test/helpers/constructs/SectionBlock/createSectionBlockFromNodeMock.ts +17 -0
- package/src/test/helpers/constructs/SectionBlock/createSectionBlockMock.ts +17 -0
- package/src/test/helpers/constructs/SectionBlock/makeSectionBlockFixture.ts +20 -0
- package/src/test/helpers/constructs/Tag/makeTagFixture.ts +10 -0
- package/src/test/helpers/constructs/index.ts +14 -0
- package/src/test/helpers/index.ts +2 -0
- package/src/test/helpers/parsers/fields/stripStrongMock.ts +11 -0
- package/src/test/helpers/parsers/index.ts +5 -0
- package/src/test/helpers/parsers/mdast/rawSliceMock.ts +9 -0
- package/src/test/helpers/parsers/natural-expression/createNaturalExpressionFromNodeMock.ts +16 -0
- package/src/test/helpers/parsers/tags/extractTagsMock.ts +12 -0
- package/src/test/helpers/parsers/tags/tagToMdastMock.ts +9 -0
- package/dist/esm/index.js +0 -3135
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
2
|
+
|
|
3
|
+
// eslint-disable-next-line import/order
|
|
4
|
+
import { rawSliceMock } from '../../test/helpers';
|
|
5
|
+
// eslint-disable-next-line import/order
|
|
6
|
+
import { rawSlice } from '../mdast';
|
|
7
|
+
|
|
8
|
+
import { stripStrong } from './stripStrong';
|
|
9
|
+
|
|
10
|
+
vi.mock('../mdast', () => {
|
|
11
|
+
return rawSliceMock();
|
|
12
|
+
});
|
|
13
|
+
|
|
14
|
+
describe('stripStrong', () => {
|
|
15
|
+
it('WHEN stripping ** wrappers', () => {
|
|
16
|
+
vi.mocked(rawSlice).mockReturnValue('**hello**');
|
|
17
|
+
|
|
18
|
+
const result = stripStrong({ type: 'strong', children: [] }, { markdown: '' } as never);
|
|
19
|
+
|
|
20
|
+
expect(result).toBe('hello');
|
|
21
|
+
});
|
|
22
|
+
|
|
23
|
+
it('WHEN stripping __ wrappers', () => {
|
|
24
|
+
vi.mocked(rawSlice).mockReturnValue('__hello__');
|
|
25
|
+
|
|
26
|
+
const result = stripStrong({ type: 'strong', children: [] }, { markdown: '' } as never);
|
|
27
|
+
|
|
28
|
+
expect(result).toBe('hello');
|
|
29
|
+
});
|
|
30
|
+
|
|
31
|
+
it('WHEN not wrapped returns raw', () => {
|
|
32
|
+
vi.mocked(rawSlice).mockReturnValue('hello');
|
|
33
|
+
|
|
34
|
+
const result = stripStrong({ type: 'strong', children: [] }, { markdown: '' } as never);
|
|
35
|
+
|
|
36
|
+
expect(result).toBe('hello');
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import type { ParserVisitContext } from '@art-md/primitives';
|
|
2
|
+
import type { Strong } from 'mdast';
|
|
3
|
+
|
|
4
|
+
import { rawSlice } from '../mdast';
|
|
5
|
+
|
|
6
|
+
export function stripStrong(node: Strong, context: ParserVisitContext): string {
|
|
7
|
+
const raw = rawSlice(node, context);
|
|
8
|
+
return raw.length >= 4 &&
|
|
9
|
+
((raw.startsWith('**') && raw.endsWith('**')) || (raw.startsWith('__') && raw.endsWith('__')))
|
|
10
|
+
? raw.slice(2, -2)
|
|
11
|
+
: raw;
|
|
12
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
export { createArtDocumentFromNode } from './document';
|
|
2
|
+
|
|
3
|
+
export * from './constructs/FieldBlock';
|
|
4
|
+
export * from './constructs/FieldInline';
|
|
5
|
+
export * from './constructs/NaturalBlock';
|
|
6
|
+
export * from './constructs/SectionBlock';
|
|
7
|
+
|
|
8
|
+
export type * from './types';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { rawSlice } from './rawSlice';
|
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { rawSlice } from './rawSlice';
|
|
4
|
+
|
|
5
|
+
describe('rawSlice', () => {
|
|
6
|
+
it('FOR a node with position returns the raw slice', () => {
|
|
7
|
+
const node = {
|
|
8
|
+
type: 'text',
|
|
9
|
+
position: {
|
|
10
|
+
start: { line: 1, column: 1, offset: 0 },
|
|
11
|
+
end: { line: 1, column: 6, offset: 5 },
|
|
12
|
+
},
|
|
13
|
+
};
|
|
14
|
+
const context = { source: { markdown: 'Hello world' } } as never;
|
|
15
|
+
|
|
16
|
+
const result = rawSlice(node, context);
|
|
17
|
+
|
|
18
|
+
expect(result).toBe('Hello');
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('WHEN node lacks position returns empty string', () => {
|
|
22
|
+
const node = { type: 'text' };
|
|
23
|
+
const context = { source: { markdown: 'Hello world' } } as never;
|
|
24
|
+
|
|
25
|
+
const result = rawSlice(node, context);
|
|
26
|
+
|
|
27
|
+
expect(result).toBe('');
|
|
28
|
+
});
|
|
29
|
+
|
|
30
|
+
it('WHEN node lacks position end returns empty string', () => {
|
|
31
|
+
const node = {
|
|
32
|
+
type: 'text',
|
|
33
|
+
position: {
|
|
34
|
+
start: { line: 1, column: 1, offset: 0 },
|
|
35
|
+
},
|
|
36
|
+
} as never;
|
|
37
|
+
const context = { source: { markdown: 'Hello world' } } as never;
|
|
38
|
+
|
|
39
|
+
const result = rawSlice(node, context);
|
|
40
|
+
|
|
41
|
+
expect(result).toBe('');
|
|
42
|
+
});
|
|
43
|
+
});
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { MdastNode, ParserVisitContext } from '@art-md/primitives';
|
|
2
|
+
|
|
3
|
+
export function rawSlice(node: MdastNode, context: ParserVisitContext): string {
|
|
4
|
+
if (!node.position?.start || !node.position?.end) {
|
|
5
|
+
return '';
|
|
6
|
+
}
|
|
7
|
+
return context.source.markdown.slice(node.position.start.offset, node.position.end.offset);
|
|
8
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { MdastNode } from '@art-md/primitives';
|
|
2
|
+
import { nodePositionMock } from '@art-md/primitives/src/test/helpers';
|
|
3
|
+
import { describe, expect, it, vi } from 'vitest';
|
|
4
|
+
|
|
5
|
+
import { createNaturalExpressionFromNode } from './createNaturalExpressionFromNode';
|
|
6
|
+
|
|
7
|
+
vi.mock('@art-md/primitives/src/parser/helpers', () => {
|
|
8
|
+
return nodePositionMock();
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
describe('createNaturalExpressionFronNode', () => {
|
|
12
|
+
it('WHEN creating a NaturalExpression from a node', async () => {
|
|
13
|
+
const node = {
|
|
14
|
+
type: 'text',
|
|
15
|
+
value: 'hello',
|
|
16
|
+
position: { start: { offset: 0 }, end: { offset: 5 } },
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
const result = createNaturalExpressionFromNode(node as MdastNode);
|
|
20
|
+
|
|
21
|
+
expect(result.construct).toBe('NaturalExpression');
|
|
22
|
+
expect(result.type).toBe('text');
|
|
23
|
+
expect(result.value).toBe('hello');
|
|
24
|
+
});
|
|
25
|
+
|
|
26
|
+
it('WHEN recursively creating children from node children', async () => {
|
|
27
|
+
const node = {
|
|
28
|
+
type: 'paragraph',
|
|
29
|
+
position: { start: { offset: 0 }, end: { offset: 5 } },
|
|
30
|
+
children: [
|
|
31
|
+
{ type: 'text', value: 'hello', position: { start: { offset: 0 }, end: { offset: 5 } } },
|
|
32
|
+
],
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const result = createNaturalExpressionFromNode(node as MdastNode);
|
|
36
|
+
|
|
37
|
+
expect(result.children).toHaveLength(1);
|
|
38
|
+
expect(result.children[0]?.type).toBe('text');
|
|
39
|
+
});
|
|
40
|
+
});
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
import { type MdastNode } from '@art-md/primitives';
|
|
2
|
+
import { nodePosition } from '@art-md/primitives/src/parser/helpers';
|
|
3
|
+
|
|
4
|
+
import type { NaturalExpression } from '../../factories';
|
|
5
|
+
|
|
6
|
+
export function createNaturalExpressionFromNode(node: MdastNode): NaturalExpression {
|
|
7
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
8
|
+
const { position: _, children: mdastChildren, type, value, ...attributes } = node;
|
|
9
|
+
|
|
10
|
+
let children: NaturalExpression[] = [];
|
|
11
|
+
if (Array.isArray(mdastChildren)) {
|
|
12
|
+
children = mdastChildren.map((child: MdastNode) => createNaturalExpressionFromNode(child));
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
const expression: NaturalExpression = {
|
|
16
|
+
construct: 'NaturalExpression',
|
|
17
|
+
type,
|
|
18
|
+
attributes,
|
|
19
|
+
value,
|
|
20
|
+
position: nodePosition(node),
|
|
21
|
+
children,
|
|
22
|
+
};
|
|
23
|
+
|
|
24
|
+
return expression;
|
|
25
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createNaturalExpressionFromNode } from './createNaturalExpressionFromNode';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { createFieldBlockParser } from './constructs/FieldBlock';
|
|
2
|
+
import { createFieldInlineParser } from './constructs/FieldInline';
|
|
3
|
+
import { createNaturalBlockParser } from './constructs/NaturalBlock';
|
|
4
|
+
import { createSectionBlockParser } from './constructs/SectionBlock';
|
|
5
|
+
import type { ConstructParserFactory } from './types';
|
|
6
|
+
|
|
7
|
+
export { createArtDocumentFromNode } from './document/createArtDocumentFromNode';
|
|
8
|
+
|
|
9
|
+
export const CONSTRUCT_PARSERS: ConstructParserFactory[] = [
|
|
10
|
+
createFieldBlockParser,
|
|
11
|
+
createFieldInlineParser,
|
|
12
|
+
createSectionBlockParser,
|
|
13
|
+
];
|
|
14
|
+
export const DEFAULT_CONSTRUCT_PARSER = createNaturalBlockParser;
|
|
15
|
+
|
|
16
|
+
export type * from './types';
|
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { extractTags } from './extractTags';
|
|
4
|
+
|
|
5
|
+
describe('extractTags', () => {
|
|
6
|
+
it('WHEN there are no tags returns empty tags and unmodified text', () => {
|
|
7
|
+
const result = extractTags('Hello there');
|
|
8
|
+
expect(result).toEqual({ tags: [], stripped: 'Hello there' });
|
|
9
|
+
});
|
|
10
|
+
|
|
11
|
+
it('WHEN extracting a single trailing tag', () => {
|
|
12
|
+
const result = extractTags('Hello there (#friend)');
|
|
13
|
+
|
|
14
|
+
expect(result).toEqual({
|
|
15
|
+
tags: [{ construct: 'Tag', name: 'friend' }],
|
|
16
|
+
stripped: 'Hello there',
|
|
17
|
+
});
|
|
18
|
+
});
|
|
19
|
+
|
|
20
|
+
it('WHEN extracting multiple trailing tags', () => {
|
|
21
|
+
const result = extractTags('Hello there (#friend) (#family)');
|
|
22
|
+
|
|
23
|
+
expect(result).toEqual({
|
|
24
|
+
tags: [
|
|
25
|
+
{ construct: 'Tag', name: 'friend' },
|
|
26
|
+
{ construct: 'Tag', name: 'family' },
|
|
27
|
+
],
|
|
28
|
+
stripped: 'Hello there',
|
|
29
|
+
});
|
|
30
|
+
});
|
|
31
|
+
|
|
32
|
+
it('WHEN the only tag is in the middle returns empty tags and unmodified text', () => {
|
|
33
|
+
const result = extractTags('Hello (#friend) there');
|
|
34
|
+
|
|
35
|
+
expect(result).toEqual({
|
|
36
|
+
tags: [],
|
|
37
|
+
stripped: 'Hello (#friend) there',
|
|
38
|
+
});
|
|
39
|
+
});
|
|
40
|
+
|
|
41
|
+
it('WHEN extracting only the trailing valid tag, keeping the middle tag in the text', () => {
|
|
42
|
+
const result = extractTags('Hello (#bad) there (#good)');
|
|
43
|
+
|
|
44
|
+
expect(result).toEqual({
|
|
45
|
+
tags: [{ construct: 'Tag', name: 'good' }],
|
|
46
|
+
stripped: 'Hello (#bad) there',
|
|
47
|
+
});
|
|
48
|
+
});
|
|
49
|
+
|
|
50
|
+
it('WHEN whitespace after the last tag and still extracts it ignores', () => {
|
|
51
|
+
const result = extractTags('Hello there (#friend) ');
|
|
52
|
+
|
|
53
|
+
expect(result).toEqual({
|
|
54
|
+
tags: [{ construct: 'Tag', name: 'friend' }],
|
|
55
|
+
stripped: 'Hello there',
|
|
56
|
+
});
|
|
57
|
+
});
|
|
58
|
+
|
|
59
|
+
it('WHEN there are no tags but trailing whitespace returns empty tags and unmodified text', () => {
|
|
60
|
+
const result = extractTags('Hello there ');
|
|
61
|
+
|
|
62
|
+
expect(result).toEqual({
|
|
63
|
+
tags: [],
|
|
64
|
+
stripped: 'Hello there ',
|
|
65
|
+
});
|
|
66
|
+
});
|
|
67
|
+
|
|
68
|
+
it('WHEN text is only a tag with no preceding text returns empty tags', () => {
|
|
69
|
+
const result = extractTags('(#tag)');
|
|
70
|
+
|
|
71
|
+
expect(result).toEqual({
|
|
72
|
+
tags: [],
|
|
73
|
+
stripped: '(#tag)',
|
|
74
|
+
});
|
|
75
|
+
});
|
|
76
|
+
|
|
77
|
+
it('FOR empty string returns empty tags', () => {
|
|
78
|
+
const result = extractTags('');
|
|
79
|
+
|
|
80
|
+
expect(result).toEqual({
|
|
81
|
+
tags: [],
|
|
82
|
+
stripped: '',
|
|
83
|
+
});
|
|
84
|
+
});
|
|
85
|
+
});
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
import { type Tag, createTag } from '../../factories';
|
|
2
|
+
|
|
3
|
+
const TRAILING_TAGS = /^(.*?)((?:\(#[\w-]+\)\s*)+)$/;
|
|
4
|
+
const TAG = /\(#([\w-]+)\)/g;
|
|
5
|
+
|
|
6
|
+
export function extractTags(text: string): { tags: Tag[]; stripped: string } {
|
|
7
|
+
const trimmed = text.trimEnd();
|
|
8
|
+
const match = trimmed.match(TRAILING_TAGS);
|
|
9
|
+
if (!match || !match[1] || !match[2]) {
|
|
10
|
+
return {
|
|
11
|
+
tags: [],
|
|
12
|
+
stripped: text,
|
|
13
|
+
};
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const tags = [...match[2].matchAll(TAG)].map(matchResult =>
|
|
17
|
+
createTag({ name: matchResult[1] ?? '' }),
|
|
18
|
+
);
|
|
19
|
+
return {
|
|
20
|
+
tags,
|
|
21
|
+
stripped: match[1].trimEnd(),
|
|
22
|
+
};
|
|
23
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { extractTags } from './extractTags';
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
/* eslint-disable @typescript-eslint/no-explicit-any */
|
|
2
|
+
|
|
3
|
+
import type { MdastNode, ParserVisitContext } from '@art-md/primitives';
|
|
4
|
+
|
|
5
|
+
import type { Construct } from '../factories';
|
|
6
|
+
|
|
7
|
+
export type ConstructProcessor = {
|
|
8
|
+
captureNode(context: ParserVisitContext, node: MdastNode): Construct | null;
|
|
9
|
+
};
|
|
10
|
+
|
|
11
|
+
export type ConstructIntegrator = {
|
|
12
|
+
integrate(context: ParserVisitContext, node: MdastNode, construct: Construct): ParserVisitContext;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
export type ConstructParser = {
|
|
16
|
+
readonly name: string;
|
|
17
|
+
processor?: ConstructProcessor;
|
|
18
|
+
integrator?: ConstructIntegrator;
|
|
19
|
+
};
|
|
20
|
+
|
|
21
|
+
export type ConstructParserFactory = () => ConstructParser;
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { Heading, Text } from 'mdast';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { createDocumentToMdast } from './createDocumentToMdast';
|
|
5
|
+
|
|
6
|
+
describe('createDocumentToMdast', () => {
|
|
7
|
+
it('WHEN wrapping children in a root node', () => {
|
|
8
|
+
const toMdast = createDocumentToMdast();
|
|
9
|
+
const heading: Heading = {
|
|
10
|
+
type: 'heading',
|
|
11
|
+
depth: 1,
|
|
12
|
+
children: [{ type: 'text', value: 'Hello' } as Text],
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const result = toMdast.toMdast({ construct: 'Document' } as never, [heading]);
|
|
16
|
+
|
|
17
|
+
expect(result).toEqual({
|
|
18
|
+
type: 'root',
|
|
19
|
+
children: [heading],
|
|
20
|
+
});
|
|
21
|
+
});
|
|
22
|
+
});
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import type { Node } from 'mdast';
|
|
2
|
+
|
|
3
|
+
import type { ConstructSerializer } from '../../types';
|
|
4
|
+
|
|
5
|
+
export function createDocumentToMdast(): ConstructSerializer {
|
|
6
|
+
return {
|
|
7
|
+
name: 'Document',
|
|
8
|
+
toMdast(_node, children) {
|
|
9
|
+
return {
|
|
10
|
+
type: 'root',
|
|
11
|
+
children,
|
|
12
|
+
} as Node;
|
|
13
|
+
},
|
|
14
|
+
};
|
|
15
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createDocumentToMdast } from './createDocumentToMdast';
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Text } from 'mdast';
|
|
2
|
+
import { describe, expect, it } from 'vitest';
|
|
3
|
+
|
|
4
|
+
import { makeFieldBlockFixture, makeTagFixture } from '../../../test/helpers';
|
|
5
|
+
|
|
6
|
+
import { createFieldBlockToMdast } from './createFieldBlockToMdast';
|
|
7
|
+
|
|
8
|
+
describe('createFieldBlockToMdast', () => {
|
|
9
|
+
it('WHEN converting a FieldBlock to a label paragraph', () => {
|
|
10
|
+
const toMdast = createFieldBlockToMdast();
|
|
11
|
+
const value: Text = { type: 'text', value: ' Generate and manage agent instructions.' };
|
|
12
|
+
|
|
13
|
+
const result = toMdast.toMdast(makeFieldBlockFixture({ name: 'Purpose' }) as never, [value]);
|
|
14
|
+
|
|
15
|
+
expect(result).toEqual({
|
|
16
|
+
type: 'paragraph',
|
|
17
|
+
children: [{ type: 'strong', children: [{ type: 'text', value: 'Purpose:' }] }],
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('WHEN present includes tags', () => {
|
|
22
|
+
const toMdast = createFieldBlockToMdast();
|
|
23
|
+
|
|
24
|
+
const result = toMdast.toMdast(
|
|
25
|
+
makeFieldBlockFixture({ name: 'Purpose', tags: [makeTagFixture()] }) as never,
|
|
26
|
+
[],
|
|
27
|
+
);
|
|
28
|
+
expect(result).toEqual({
|
|
29
|
+
type: 'paragraph',
|
|
30
|
+
children: [
|
|
31
|
+
{ type: 'strong', children: [{ type: 'text', value: 'Purpose:' }] },
|
|
32
|
+
{ type: 'text', value: ' (#test)' },
|
|
33
|
+
],
|
|
34
|
+
});
|
|
35
|
+
});
|
|
36
|
+
});
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { Node } from 'mdast';
|
|
2
|
+
|
|
3
|
+
import type { FieldBlock } from '../../../factories';
|
|
4
|
+
import { tagsToMdast } from '../../tags';
|
|
5
|
+
import type { ConstructSerializer } from '../../types';
|
|
6
|
+
|
|
7
|
+
export function createFieldBlockToMdast(): ConstructSerializer {
|
|
8
|
+
return {
|
|
9
|
+
name: 'FieldBlock',
|
|
10
|
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
|
11
|
+
toMdast(node, _children) {
|
|
12
|
+
const field = node as unknown as FieldBlock;
|
|
13
|
+
const tagNode = field.tags?.length ? tagsToMdast(field.tags) : null;
|
|
14
|
+
return {
|
|
15
|
+
type: 'paragraph',
|
|
16
|
+
children: [
|
|
17
|
+
{
|
|
18
|
+
type: 'strong',
|
|
19
|
+
children: [{ type: 'text', value: `${field.name}:` }],
|
|
20
|
+
},
|
|
21
|
+
...(tagNode ? [tagNode] : []),
|
|
22
|
+
],
|
|
23
|
+
} as Node;
|
|
24
|
+
},
|
|
25
|
+
};
|
|
26
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createFieldBlockToMdast } from './createFieldBlockToMdast';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { makeFieldInlineFixture, makeTagFixture } from '../../../test/helpers';
|
|
4
|
+
|
|
5
|
+
import { createFieldInlineToMdast } from './createFieldInlineToMdast';
|
|
6
|
+
|
|
7
|
+
describe('createFieldInlineToMdast', () => {
|
|
8
|
+
it('WHEN converting a FieldInline to a paragraph with strong label', () => {
|
|
9
|
+
const toMdast = createFieldInlineToMdast();
|
|
10
|
+
|
|
11
|
+
const result = toMdast.toMdast(makeFieldInlineFixture({ name: 'Purpose' }) as never, []);
|
|
12
|
+
|
|
13
|
+
expect(result).toEqual({
|
|
14
|
+
type: 'paragraph',
|
|
15
|
+
children: [
|
|
16
|
+
{ type: 'strong', children: [{ type: 'text', value: 'Purpose:' }] },
|
|
17
|
+
{ type: 'text', value: ' ' },
|
|
18
|
+
],
|
|
19
|
+
});
|
|
20
|
+
});
|
|
21
|
+
|
|
22
|
+
it('WHEN present appends tags', () => {
|
|
23
|
+
const toMdast = createFieldInlineToMdast();
|
|
24
|
+
|
|
25
|
+
const result = toMdast.toMdast(
|
|
26
|
+
makeFieldInlineFixture({ name: 'Purpose', tags: [makeTagFixture()] }) as never,
|
|
27
|
+
[],
|
|
28
|
+
);
|
|
29
|
+
expect(result).toEqual({
|
|
30
|
+
type: 'paragraph',
|
|
31
|
+
children: [
|
|
32
|
+
{ type: 'strong', children: [{ type: 'text', value: 'Purpose:' }] },
|
|
33
|
+
{ type: 'text', value: ' ' },
|
|
34
|
+
{ type: 'text', value: ' (#test)' },
|
|
35
|
+
],
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
});
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import type { Node } from 'mdast';
|
|
2
|
+
|
|
3
|
+
import type { FieldInline } from '../../../factories';
|
|
4
|
+
import { tagsToMdast } from '../../tags';
|
|
5
|
+
import type { ConstructSerializer } from '../../types';
|
|
6
|
+
|
|
7
|
+
export function createFieldInlineToMdast(): ConstructSerializer {
|
|
8
|
+
return {
|
|
9
|
+
name: 'FieldInline',
|
|
10
|
+
toMdast(node, children) {
|
|
11
|
+
const field = node as unknown as FieldInline;
|
|
12
|
+
const tagNode = field.tags?.length ? tagsToMdast(field.tags) : null;
|
|
13
|
+
return {
|
|
14
|
+
type: 'paragraph',
|
|
15
|
+
children: [
|
|
16
|
+
{
|
|
17
|
+
type: 'strong',
|
|
18
|
+
children: [{ type: 'text', value: `${field.name}:` }],
|
|
19
|
+
},
|
|
20
|
+
{ type: 'text', value: ' ' },
|
|
21
|
+
...children,
|
|
22
|
+
...(tagNode ? [tagNode] : []),
|
|
23
|
+
],
|
|
24
|
+
} as Node;
|
|
25
|
+
},
|
|
26
|
+
};
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createFieldInlineToMdast } from './createFieldInlineToMdast';
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
import { describe, expect, it } from 'vitest';
|
|
2
|
+
|
|
3
|
+
import { makeNaturalBlockFixture, makeTagFixture } from '../../../test/helpers';
|
|
4
|
+
|
|
5
|
+
import { createNaturalBlockToMdast } from './createNaturalBlockToMdast';
|
|
6
|
+
|
|
7
|
+
describe('createNaturalBlockToMdast', () => {
|
|
8
|
+
it('WHEN parsing a text value into a root with paragraph', () => {
|
|
9
|
+
const toMdast = createNaturalBlockToMdast();
|
|
10
|
+
|
|
11
|
+
const result = toMdast.toMdast(
|
|
12
|
+
makeNaturalBlockFixture({ type: 'text', value: ' Hello world' }) as never,
|
|
13
|
+
[],
|
|
14
|
+
);
|
|
15
|
+
expect(result).toMatchObject({
|
|
16
|
+
type: 'root',
|
|
17
|
+
children: expect.arrayContaining([expect.objectContaining({ type: 'paragraph' })]),
|
|
18
|
+
});
|
|
19
|
+
});
|
|
20
|
+
|
|
21
|
+
it('WHEN parsing a code block value', () => {
|
|
22
|
+
const toMdast = createNaturalBlockToMdast();
|
|
23
|
+
|
|
24
|
+
const result = toMdast.toMdast(
|
|
25
|
+
makeNaturalBlockFixture({
|
|
26
|
+
type: 'code',
|
|
27
|
+
lang: null,
|
|
28
|
+
meta: null,
|
|
29
|
+
value: '```\nconst x = 1;\n```',
|
|
30
|
+
}) as never,
|
|
31
|
+
[],
|
|
32
|
+
);
|
|
33
|
+
expect(result).toMatchObject({
|
|
34
|
+
type: 'root',
|
|
35
|
+
children: expect.arrayContaining([expect.objectContaining({ type: 'code' })]),
|
|
36
|
+
});
|
|
37
|
+
});
|
|
38
|
+
|
|
39
|
+
it('WHEN children in a paragraph includes', () => {
|
|
40
|
+
const toMdast = createNaturalBlockToMdast();
|
|
41
|
+
|
|
42
|
+
const result = toMdast.toMdast(
|
|
43
|
+
makeNaturalBlockFixture({ type: 'paragraph', value: 'Hello' }) as never,
|
|
44
|
+
[{ type: 'text', value: 'child' } as never],
|
|
45
|
+
);
|
|
46
|
+
expect(result).toMatchObject({
|
|
47
|
+
type: 'root',
|
|
48
|
+
children: expect.arrayContaining([
|
|
49
|
+
expect.objectContaining({
|
|
50
|
+
type: 'paragraph',
|
|
51
|
+
children: expect.arrayContaining([expect.objectContaining({ value: 'child' })]),
|
|
52
|
+
}),
|
|
53
|
+
]),
|
|
54
|
+
});
|
|
55
|
+
});
|
|
56
|
+
|
|
57
|
+
it('WHEN present includes tags in a paragraph', () => {
|
|
58
|
+
const toMdast = createNaturalBlockToMdast();
|
|
59
|
+
|
|
60
|
+
const result = toMdast.toMdast(
|
|
61
|
+
makeNaturalBlockFixture({
|
|
62
|
+
type: 'paragraph',
|
|
63
|
+
value: 'Hello',
|
|
64
|
+
tags: [makeTagFixture()],
|
|
65
|
+
}) as never,
|
|
66
|
+
[{ type: 'text', value: 'child' } as never],
|
|
67
|
+
);
|
|
68
|
+
expect(result).toMatchObject({
|
|
69
|
+
type: 'root',
|
|
70
|
+
children: expect.arrayContaining([
|
|
71
|
+
expect.objectContaining({
|
|
72
|
+
type: 'paragraph',
|
|
73
|
+
children: expect.arrayContaining([expect.objectContaining({ value: ' (#test)' })]),
|
|
74
|
+
}),
|
|
75
|
+
]),
|
|
76
|
+
});
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
it('WHEN paragraph has no children does not include tags', () => {
|
|
80
|
+
const toMdast = createNaturalBlockToMdast();
|
|
81
|
+
|
|
82
|
+
const result = toMdast.toMdast(
|
|
83
|
+
makeNaturalBlockFixture({
|
|
84
|
+
type: 'paragraph',
|
|
85
|
+
value: 'Hello',
|
|
86
|
+
tags: [makeTagFixture()],
|
|
87
|
+
}) as never,
|
|
88
|
+
[],
|
|
89
|
+
);
|
|
90
|
+
expect(result).toMatchObject({
|
|
91
|
+
type: 'root',
|
|
92
|
+
children: expect.arrayContaining([
|
|
93
|
+
expect.objectContaining({
|
|
94
|
+
type: 'paragraph',
|
|
95
|
+
children: expect.not.arrayContaining([expect.objectContaining({ value: ' (#test)' })]),
|
|
96
|
+
}),
|
|
97
|
+
]),
|
|
98
|
+
});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it('WHEN paragraph tags are empty does not include tags', () => {
|
|
102
|
+
const toMdast = createNaturalBlockToMdast();
|
|
103
|
+
|
|
104
|
+
const result = toMdast.toMdast(
|
|
105
|
+
makeNaturalBlockFixture({
|
|
106
|
+
type: 'paragraph',
|
|
107
|
+
value: 'Hello',
|
|
108
|
+
tags: [],
|
|
109
|
+
}) as never,
|
|
110
|
+
[{ type: 'text', value: 'child' } as never],
|
|
111
|
+
);
|
|
112
|
+
expect(result).toMatchObject({
|
|
113
|
+
type: 'root',
|
|
114
|
+
children: expect.arrayContaining([
|
|
115
|
+
expect.objectContaining({
|
|
116
|
+
type: 'paragraph',
|
|
117
|
+
children: expect.arrayContaining([expect.objectContaining({ value: 'child' })]),
|
|
118
|
+
}),
|
|
119
|
+
]),
|
|
120
|
+
});
|
|
121
|
+
});
|
|
122
|
+
});
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
import type { Node } from 'mdast';
|
|
2
|
+
import { fromMarkdown } from 'mdast-util-from-markdown';
|
|
3
|
+
|
|
4
|
+
import type { NaturalBlock } from '../../../factories';
|
|
5
|
+
import { tagsToMdast } from '../../tags';
|
|
6
|
+
import type { ConstructSerializer } from '../../types';
|
|
7
|
+
|
|
8
|
+
export function createNaturalBlockToMdast(): ConstructSerializer {
|
|
9
|
+
return {
|
|
10
|
+
name: 'NaturalBlock',
|
|
11
|
+
toMdast(node, children) {
|
|
12
|
+
const block = node as unknown as NaturalBlock;
|
|
13
|
+
const parsed = fromMarkdown(block.value);
|
|
14
|
+
if (block.type === 'paragraph' && children.length > 0) {
|
|
15
|
+
const paragraph = parsed.children.find(child => child.type === 'paragraph');
|
|
16
|
+
if (paragraph && 'children' in paragraph) {
|
|
17
|
+
const tagNode = block.tags?.length ? tagsToMdast(block.tags) : null;
|
|
18
|
+
paragraph.children = [
|
|
19
|
+
...children,
|
|
20
|
+
...(tagNode ? [tagNode] : []),
|
|
21
|
+
] as typeof paragraph.children;
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return {
|
|
25
|
+
type: 'root',
|
|
26
|
+
children: parsed.children,
|
|
27
|
+
} as Node;
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { createNaturalBlockToMdast } from './createNaturalBlockToMdast';
|