Line data Source code
1 : import * as dashql from '@ankoh/dashql-core';
2 :
3 : import { ConnectorType } from '../../connections/connector_info.js';
4 : import { SNIPPET_ROOT_CONNECTORS, snippetRootIsFolder } from './snippet.js';
5 :
6 : /// One snippet file, `<root>/<folders>/<name>.sql` with at least one folder, a leading comment describes the snippet.
7 : /// The files of the roots with the same folders and name are the variants of one snippet. A root that is a folder of
8 : /// the browser is part of the id, so its files are variants of nothing.
9 : export interface SnippetFile {
10 : /// The path of the file, starting at its root folder
11 : path: string;
12 : /// The id of the snippet, `<category>/<name>`
13 : id: string;
14 : /// The category of the snippet, the path of its folder in the tree of the browser
15 : category: string;
16 : /// The connectors that the body of the file runs on, those of its root
17 : connectors: readonly ConnectorType[];
18 : /// The text of the file, with its description
19 : body: string;
20 : }
21 :
22 1 : const encoder = new TextEncoder();
23 :
24 : /// The comments of a snippet file
25 : interface SnippetComments {
26 : /// The comment tag blocks, a snippet has none
27 : tags: dashql.CommentTag[];
28 : /// The ordinary comment blocks that don't lead the file, a snippet has none
29 : inner: number;
30 : /// The number of statements
31 : statements: number;
32 : }
33 :
34 : /// Read the comment blocks of a text with the DashQL parser
35 1 : function readComments(core: dashql.DashQL, text: string, bytes: Uint8Array): SnippetComments {
36 1 : const catalog = core.createCatalog();
37 1 : const script = core.createScript(catalog);
38 1 : try {
39 1 : script.insertTextAt(0, text);
40 1 : script.parse();
41 1 : const parsed = script.getParsed();
42 1 : const reader = parsed.read();
43 : // The comments before the first statement describe the snippet
44 1 : const statements = reader.statementsLength();
45 1 : const bodyBegin = statements > 0 ? reader.statements(0)!.statementSpan()!.offset() : bytes.length;
46 1 : const block = new dashql.buffers.parser.CommentBlock();
47 1 : let inner = 0;
48 1 : for (let i = 0; i < reader.commentBlocksLength(); ++i) {
49 1 : reader.commentBlocks(i, block);
50 1 : if (block.blockType() === dashql.buffers.parser.CommentBlockType.COMMENT && block.textSpan()!.offset() >= bodyBegin) ++inner;
51 1 : }
52 1 : return { tags: dashql.readCommentTags(reader, bytes), inner, statements };
53 1 : } finally {
54 1 : script.destroy();
55 1 : catalog.destroy();
56 1 : }
57 1 : }
58 :
59 : /// Parse a snippet file, throws if the path or the comments are invalid
60 1 : export function parseSnippetFile(core: dashql.DashQL, path: string, raw: string): SnippetFile {
61 1 : const pathMatch = /^([a-z0-9_-]+)\/((?:[a-z0-9_-]+\/)*[a-z0-9_-]+)\/([a-z0-9_-]+)\.sql$/.exec(path);
62 1 : if (pathMatch == null) {
63 1 : throw new Error(`invalid snippet path: ${path}`);
64 : }
65 1 : const [, root, folders, name] = pathMatch;
66 1 : const connectors = SNIPPET_ROOT_CONNECTORS.get(root);
67 1 : if (connectors === undefined) {
68 1 : throw new Error(`unknown root folder "${root}" of snippet ${path}`);
69 : }
70 1 : const category = snippetRootIsFolder(root) ? `${root}/${folders}` : folders;
71 :
72 1 : const bytes = encoder.encode(raw);
73 1 : const { tags, inner, statements } = readComments(core, raw, bytes);
74 : // The formatter of the preview moves comments within a statement, explanations belong into the description
75 1 : if (inner > 0) {
76 1 : throw new Error(`snippet ${path} has comments after its description, move them into the description`);
77 : }
78 1 : if (tags.length > 0) {
79 1 : throw new Error(`snippet ${path} has a @${tags[0].name} tag, snippets only have a description`);
80 : }
81 : // The description stays in the applied text, a snippet needs statements besides it
82 1 : if (statements === 0) {
83 1 : throw new Error(`snippet ${path} has no body`);
84 : }
85 1 : return {
86 1 : path,
87 1 : id: `${category}/${name}`,
88 1 : category,
89 1 : connectors,
90 1 : body: raw.trim(),
91 1 : };
92 1 : }
|