sdocs 0.0.41 → 0.0.42

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.
@@ -1,109 +0,0 @@
1
- import { parse } from 'svelte/compiler';
2
- import { readFile } from 'node:fs/promises';
3
- import { resolve, dirname } from 'node:path';
4
- /** Extract meta and imports from a .sdoc file */
5
- export async function parseDocFile(filePath) {
6
- const source = await readFile(filePath, 'utf-8');
7
- return parseDocSource(source, filePath);
8
- }
9
- /** Parse meta from .sdoc source */
10
- export function parseDocSource(source, filePath) {
11
- const ast = parse(source, { modern: true });
12
- const scriptContent = extractScriptContent(source);
13
- if (!scriptContent) {
14
- return {
15
- meta: { title: guessTitle(filePath) },
16
- componentPath: null,
17
- imports: [],
18
- };
19
- }
20
- const imports = extractImports(scriptContent);
21
- const meta = extractMeta(scriptContent);
22
- const componentPath = resolveComponentPath(meta, imports, filePath);
23
- return { meta, componentPath, imports };
24
- }
25
- /** Extract the content of the <script> tag */
26
- function extractScriptContent(source) {
27
- const match = source.match(/<script[^>]*>([\s\S]*?)<\/script>/);
28
- return match ? match[1] : null;
29
- }
30
- /** Extract import statements */
31
- function extractImports(scriptContent) {
32
- const imports = [];
33
- const regex = /^\s*import\s+.+$/gm;
34
- let match;
35
- while ((match = regex.exec(scriptContent)) !== null) {
36
- imports.push(match[0].trim());
37
- }
38
- return imports;
39
- }
40
- /** Extract meta object from `export const meta = { ... }` using brace counting */
41
- function extractMeta(scriptContent) {
42
- // Find where the meta object starts
43
- const startMatch = scriptContent.match(/export\s+const\s+meta\s*(?::\s*\w+\s*)?=\s*\{/);
44
- if (!startMatch || startMatch.index === undefined)
45
- return { title: 'Untitled' };
46
- // Find the opening brace
47
- const braceStart = startMatch.index + startMatch[0].length - 1;
48
- let depth = 1;
49
- let i = braceStart + 1;
50
- // Count braces to find the matching closing brace
51
- while (i < scriptContent.length && depth > 0) {
52
- const ch = scriptContent[i];
53
- if (ch === '{')
54
- depth++;
55
- else if (ch === '}')
56
- depth--;
57
- // Skip strings
58
- else if (ch === "'" || ch === '"' || ch === '`') {
59
- i++;
60
- while (i < scriptContent.length && scriptContent[i] !== ch) {
61
- if (scriptContent[i] === '\\')
62
- i++; // skip escaped chars
63
- i++;
64
- }
65
- }
66
- i++;
67
- }
68
- const metaStr = scriptContent.slice(braceStart, i);
69
- try {
70
- // Replace component: Identifier with component: 'Identifier'
71
- const cleaned = metaStr.replace(/component:\s*([A-Z]\w*)/, "component: '$1'");
72
- const fn = new Function(`return (${cleaned})`);
73
- const result = fn();
74
- return result;
75
- }
76
- catch {
77
- return extractMetaFields(metaStr);
78
- }
79
- }
80
- /** Fallback: extract meta fields with regex */
81
- function extractMetaFields(metaStr) {
82
- const title = metaStr.match(/title:\s*['"](.+?)['"]/)?.[1] ?? 'Untitled';
83
- const description = metaStr.match(/description:\s*['"](.+?)['"]/)?.[1];
84
- return { title, description };
85
- }
86
- /** Resolve the component import path to an absolute path */
87
- function resolveComponentPath(meta, imports, docFilePath) {
88
- const componentValue = typeof meta.component === 'string' ? meta.component : null;
89
- if (!componentValue)
90
- return null;
91
- // If component is a relative path (e.g. './Button.svelte'), resolve directly
92
- if (componentValue.startsWith('./') || componentValue.startsWith('../')) {
93
- return resolve(dirname(docFilePath), componentValue);
94
- }
95
- // Otherwise it's an identifier (e.g. 'Button') — find matching import
96
- for (const imp of imports) {
97
- const match = imp.match(new RegExp(`import\\s+${componentValue}\\s+from\\s+['"](.+?)['"]`));
98
- if (match) {
99
- const importPath = match[1];
100
- return resolve(dirname(docFilePath), importPath);
101
- }
102
- }
103
- return null;
104
- }
105
- /** Guess a title from the file path */
106
- function guessTitle(filePath) {
107
- const fileName = filePath.split('/').pop() ?? '';
108
- return fileName.replace(/\.sdoc$/, '').replace(/\./g, ' ');
109
- }
@@ -1,11 +0,0 @@
1
- import type { ExtractedSnippet } from '../types.js';
2
- /** Extract top-level {#snippet} blocks from .sdoc source, handling nested snippets */
3
- export declare function extractSnippets(source: string): ExtractedSnippet[];
4
- /** Check if a Default snippet exists or needs auto-generation */
5
- export declare function hasDefaultSnippet(snippets: ExtractedSnippet[]): boolean;
6
- /** Generate an auto-generated Default snippet body */
7
- export declare function generateAutoDefault(componentName: string): string;
8
- /** Extract the markup body of a .sdoc file (everything outside <script> and <style>) */
9
- export declare function extractMarkupBody(source: string): string;
10
- /** Get the snippet names in order (Default first, then alphabetical) */
11
- export declare function getSnippetOrder(snippets: ExtractedSnippet[]): string[];
@@ -1,83 +0,0 @@
1
- /** Extract top-level {#snippet} blocks from .sdoc source, handling nested snippets */
2
- export function extractSnippets(source) {
3
- const snippets = [];
4
- // Strip <script> and <style> to search only markup
5
- const markup = source
6
- .replace(/<script[^>]*>[\s\S]*?<\/script>/g, '')
7
- .replace(/<style[^>]*>[\s\S]*?<\/style>/g, '');
8
- const openRegex = /\{#snippet\s+(\w+)\s*\([^)]*\)\}/g;
9
- let match;
10
- let searchFrom = 0;
11
- while (true) {
12
- openRegex.lastIndex = searchFrom;
13
- match = openRegex.exec(markup);
14
- if (!match)
15
- break;
16
- const name = match[1];
17
- const bodyStart = match.index + match[0].length;
18
- // Count nesting depth to find the matching {/snippet}
19
- let depth = 1;
20
- let i = bodyStart;
21
- while (i < markup.length && depth > 0) {
22
- if (markup.startsWith('{/snippet}', i)) {
23
- depth--;
24
- if (depth === 0)
25
- break;
26
- i += '{/snippet}'.length;
27
- }
28
- else if (markup.startsWith('{#snippet', i)) {
29
- depth++;
30
- const closeBrace = markup.indexOf('}', i);
31
- i = closeBrace !== -1 ? closeBrace + 1 : i + 1;
32
- }
33
- else {
34
- i++;
35
- }
36
- }
37
- if (depth === 0) {
38
- const body = dedent(markup.slice(bodyStart, i).trim());
39
- snippets.push({ name, body });
40
- searchFrom = i + '{/snippet}'.length;
41
- }
42
- else {
43
- break; // malformed source, stop
44
- }
45
- }
46
- return snippets;
47
- }
48
- /** Remove common leading whitespace from all lines */
49
- function dedent(text) {
50
- const lines = text.split('\n');
51
- // Skip first line (already trimmed) when computing minimum indent
52
- const rest = lines.slice(1).filter((l) => l.trim().length > 0);
53
- const indents = rest.map((l) => l.match(/^(\s*)/)?.[1].length ?? 0);
54
- const min = indents.length > 0 ? Math.min(...indents) : 0;
55
- if (min === 0)
56
- return text;
57
- return [lines[0], ...lines.slice(1).map((l) => l.slice(min))].join('\n');
58
- }
59
- /** Check if a Default snippet exists or needs auto-generation */
60
- export function hasDefaultSnippet(snippets) {
61
- return snippets.some((s) => s.name === 'Default');
62
- }
63
- /** Generate an auto-generated Default snippet body */
64
- export function generateAutoDefault(componentName) {
65
- return `<${componentName} {...args} />`;
66
- }
67
- /** Extract the markup body of a .sdoc file (everything outside <script> and <style>) */
68
- export function extractMarkupBody(source) {
69
- return source
70
- .replace(/<script[^>]*>[\s\S]*?<\/script>/g, '')
71
- .replace(/<style[^>]*>[\s\S]*?<\/style>/g, '')
72
- .trim();
73
- }
74
- /** Get the snippet names in order (Default first, then alphabetical) */
75
- export function getSnippetOrder(snippets) {
76
- const names = snippets.map((s) => s.name);
77
- const defaultIndex = names.indexOf('Default');
78
- if (defaultIndex > 0) {
79
- names.splice(defaultIndex, 1);
80
- names.unshift('Default');
81
- }
82
- return names;
83
- }
@@ -1,3 +0,0 @@
1
- import type { TocHeading } from '../types.js';
2
- /** Extract ToC headings from HTML markup (for .sdoc pages) */
3
- export declare function extractTocFromHtml(body: string): TocHeading[];
@@ -1,23 +0,0 @@
1
- /** Slugify a heading text into an ID */
2
- function slugify(text) {
3
- return text
4
- .toLowerCase()
5
- .replace(/[^\w\s-]/g, '')
6
- .replace(/\s+/g, '-')
7
- .replace(/-+/g, '-')
8
- .trim();
9
- }
10
- /** Extract ToC headings from HTML markup (for .sdoc pages) */
11
- export function extractTocFromHtml(body) {
12
- const headings = [];
13
- const regex = /<h([2-4])[^>]*>([\s\S]*?)<\/h[2-4]>/gi;
14
- let match;
15
- while ((match = regex.exec(body)) !== null) {
16
- const level = parseInt(match[1]);
17
- const text = match[2].replace(/<[^>]*>/g, '').trim();
18
- if (text) {
19
- headings.push({ text, level, id: slugify(text) });
20
- }
21
- }
22
- return headings;
23
- }