@udx/mq 0.1.1
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/examples/analyze-document.js +191 -0
- package/examples/cross-linker.js +47 -0
- package/examples/demo-architecture.js +93 -0
- package/examples/demo.js +200 -0
- package/examples/filter-code-blocks.js +64 -0
- package/examples/generate-toc.js +71 -0
- package/examples/make-collapsible.js +61 -0
- package/examples/query-headings.js +56 -0
- package/examples/toc-generator.js +44 -0
- package/lib/core.js +347 -0
- package/lib/integrations/mcurl.js +125 -0
- package/lib/operations/analysis.js +344 -0
- package/lib/operations/extractors.js +247 -0
- package/lib/operations/index.js +151 -0
- package/lib/operations/transformers.js +411 -0
- package/lib/utils/parser.js +165 -0
- package/mq.js +656 -0
- package/package.json +67 -0
- package/readme.md +242 -0
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Example: Make Code Blocks Collapsible
|
|
5
|
+
*
|
|
6
|
+
* This example demonstrates how to use mq to transform code blocks
|
|
7
|
+
* into collapsible sections.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import { fromMarkdown } from 'mdast-util-from-markdown';
|
|
11
|
+
import { toMarkdown } from 'mdast-util-to-markdown';
|
|
12
|
+
import { visit } from 'unist-util-visit';
|
|
13
|
+
|
|
14
|
+
// Example markdown content
|
|
15
|
+
const markdown = `# Code Examples
|
|
16
|
+
|
|
17
|
+
## JavaScript
|
|
18
|
+
|
|
19
|
+
\`\`\`javascript
|
|
20
|
+
function hello() {
|
|
21
|
+
console.log('Hello, world!');
|
|
22
|
+
}
|
|
23
|
+
\`\`\`
|
|
24
|
+
|
|
25
|
+
## PHP
|
|
26
|
+
|
|
27
|
+
\`\`\`php
|
|
28
|
+
function hello() {
|
|
29
|
+
echo 'Hello, world!';
|
|
30
|
+
}
|
|
31
|
+
\`\`\`
|
|
32
|
+
`;
|
|
33
|
+
|
|
34
|
+
// Parse markdown to AST
|
|
35
|
+
const ast = fromMarkdown(markdown);
|
|
36
|
+
|
|
37
|
+
// Clone the AST to avoid mutating the original
|
|
38
|
+
const transformedAst = { ...ast };
|
|
39
|
+
|
|
40
|
+
// Transform code blocks to collapsible sections
|
|
41
|
+
visit(transformedAst, 'code', (node) => {
|
|
42
|
+
// Convert code node to HTML node with details/summary
|
|
43
|
+
node.type = 'html';
|
|
44
|
+
node.value = `<details>\n<summary>Click to view code example</summary>\n\n\`\`\`${node.lang || ''}\n${node.value}\`\`\`\n</details>`;
|
|
45
|
+
delete node.lang;
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
// Serialize back to markdown
|
|
49
|
+
const result = toMarkdown(transformedAst);
|
|
50
|
+
|
|
51
|
+
console.log('Original Markdown:');
|
|
52
|
+
console.log('=================');
|
|
53
|
+
console.log(markdown);
|
|
54
|
+
console.log('\n');
|
|
55
|
+
|
|
56
|
+
console.log('Transformed Markdown:');
|
|
57
|
+
console.log('===================');
|
|
58
|
+
console.log(result);
|
|
59
|
+
|
|
60
|
+
// How to run this example:
|
|
61
|
+
// node examples/make-collapsible.js
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: Query Headings
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates how to use mdoc to query headings
|
|
5
|
+
* from markdown content.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { fromMarkdown } from 'mdast-util-from-markdown';
|
|
9
|
+
import { visit } from 'unist-util-visit';
|
|
10
|
+
import { toString } from 'mdast-util-to-string';
|
|
11
|
+
|
|
12
|
+
// Example markdown content
|
|
13
|
+
const markdown = `# Sample Documentation
|
|
14
|
+
|
|
15
|
+
This is a sample documentation file to demonstrate heading queries.
|
|
16
|
+
|
|
17
|
+
## Introduction
|
|
18
|
+
|
|
19
|
+
This section introduces the topic.
|
|
20
|
+
|
|
21
|
+
## Getting Started
|
|
22
|
+
|
|
23
|
+
This section helps users get started.
|
|
24
|
+
|
|
25
|
+
### Installation
|
|
26
|
+
|
|
27
|
+
Installation instructions.
|
|
28
|
+
|
|
29
|
+
### Configuration
|
|
30
|
+
|
|
31
|
+
Configuration instructions.
|
|
32
|
+
|
|
33
|
+
## Advanced Usage
|
|
34
|
+
|
|
35
|
+
This section covers advanced usage scenarios.
|
|
36
|
+
`;
|
|
37
|
+
|
|
38
|
+
// Parse markdown to AST
|
|
39
|
+
const ast = fromMarkdown(markdown);
|
|
40
|
+
|
|
41
|
+
// Query all headings
|
|
42
|
+
console.log('All headings:');
|
|
43
|
+
console.log('============');
|
|
44
|
+
visit(ast, 'heading', (node) => {
|
|
45
|
+
console.log(`${node.depth} ${toString(node)}`);
|
|
46
|
+
});
|
|
47
|
+
console.log('\n');
|
|
48
|
+
|
|
49
|
+
// Query only h2 headings
|
|
50
|
+
console.log('Only h2 headings:');
|
|
51
|
+
console.log('===============');
|
|
52
|
+
visit(ast, 'heading', (node) => {
|
|
53
|
+
if (node.depth === 2) {
|
|
54
|
+
console.log(toString(node));
|
|
55
|
+
}
|
|
56
|
+
});
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Example: TOC Generator
|
|
3
|
+
*
|
|
4
|
+
* This example demonstrates how to use the mdoc TOC generation
|
|
5
|
+
* functionality to enhance markdown documentation.
|
|
6
|
+
*/
|
|
7
|
+
|
|
8
|
+
import { generateTOC } from '../index.js';
|
|
9
|
+
|
|
10
|
+
// Example markdown content
|
|
11
|
+
const markdown = `# Sample Documentation
|
|
12
|
+
|
|
13
|
+
This is a sample documentation file to demonstrate TOC generation.
|
|
14
|
+
|
|
15
|
+
## Introduction
|
|
16
|
+
|
|
17
|
+
This section introduces the topic.
|
|
18
|
+
|
|
19
|
+
## Getting Started
|
|
20
|
+
|
|
21
|
+
This section helps users get started.
|
|
22
|
+
|
|
23
|
+
## Advanced Usage
|
|
24
|
+
|
|
25
|
+
This section covers advanced usage scenarios.
|
|
26
|
+
|
|
27
|
+
## Troubleshooting
|
|
28
|
+
|
|
29
|
+
This section helps users troubleshoot common issues.
|
|
30
|
+
`;
|
|
31
|
+
|
|
32
|
+
// Generate TOC with descriptive links
|
|
33
|
+
const enhancedMarkdown = generateTOC(markdown, {
|
|
34
|
+
descriptive: true
|
|
35
|
+
});
|
|
36
|
+
|
|
37
|
+
console.log('Original Markdown:');
|
|
38
|
+
console.log('=================');
|
|
39
|
+
console.log(markdown);
|
|
40
|
+
console.log('\n');
|
|
41
|
+
|
|
42
|
+
console.log('Enhanced Markdown with TOC:');
|
|
43
|
+
console.log('=========================');
|
|
44
|
+
console.log(enhancedMarkdown);
|
package/lib/core.js
ADDED
|
@@ -0,0 +1,347 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Core utilities for markdown query and transform operations
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import fs from 'fs/promises';
|
|
6
|
+
import path from 'path';
|
|
7
|
+
import { fromMarkdown } from 'mdast-util-from-markdown';
|
|
8
|
+
import { toMarkdown } from 'mdast-util-to-markdown';
|
|
9
|
+
import { toString } from 'mdast-util-to-string';
|
|
10
|
+
import { visit } from 'unist-util-visit';
|
|
11
|
+
import yaml from 'js-yaml';
|
|
12
|
+
|
|
13
|
+
// Export all functions at the top for easier discovery
|
|
14
|
+
export {
|
|
15
|
+
readStdin,
|
|
16
|
+
parseQuery,
|
|
17
|
+
filterNodes,
|
|
18
|
+
constructObject,
|
|
19
|
+
formatResult
|
|
20
|
+
};
|
|
21
|
+
|
|
22
|
+
export default {
|
|
23
|
+
readStdin,
|
|
24
|
+
parseQuery,
|
|
25
|
+
filterNodes,
|
|
26
|
+
constructObject,
|
|
27
|
+
formatResult
|
|
28
|
+
};
|
|
29
|
+
|
|
30
|
+
/**
|
|
31
|
+
* Read from stdin
|
|
32
|
+
*
|
|
33
|
+
* @returns {Promise<string>} Content read from stdin
|
|
34
|
+
*/
|
|
35
|
+
async function readStdin() {
|
|
36
|
+
const chunks = [];
|
|
37
|
+
process.stdin.setEncoding('utf8');
|
|
38
|
+
|
|
39
|
+
for await (const chunk of process.stdin) {
|
|
40
|
+
chunks.push(chunk);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return chunks.join('');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Parse a query into parts
|
|
48
|
+
*
|
|
49
|
+
* Splits a query string by pipe character and trims each part.
|
|
50
|
+
* Used internally by queryMarkdown to break down complex queries.
|
|
51
|
+
*
|
|
52
|
+
* @example
|
|
53
|
+
* Parse a simple query
|
|
54
|
+
* const parts = parseQuery('.headings[]');
|
|
55
|
+
*
|
|
56
|
+
* @example
|
|
57
|
+
* Parse a complex query with pipes
|
|
58
|
+
* const parts = parseQuery('.headings[] | select(.level == 2) | {text}');
|
|
59
|
+
*
|
|
60
|
+
* @param {string} query - Query string to parse
|
|
61
|
+
* @returns {Array} Array of query parts
|
|
62
|
+
*/
|
|
63
|
+
function parseQuery(query) {
|
|
64
|
+
if (!query) return [];
|
|
65
|
+
|
|
66
|
+
// Handle edge case with escaped pipes
|
|
67
|
+
const parts = [];
|
|
68
|
+
let currentPart = '';
|
|
69
|
+
let inQuotes = false;
|
|
70
|
+
|
|
71
|
+
for (let i = 0; i < query.length; i++) {
|
|
72
|
+
const char = query[i];
|
|
73
|
+
|
|
74
|
+
if (char === '"' && (i === 0 || query[i-1] !== '\\')) {
|
|
75
|
+
inQuotes = !inQuotes;
|
|
76
|
+
currentPart += char;
|
|
77
|
+
} else if (char === '|' && !inQuotes) {
|
|
78
|
+
parts.push(currentPart.trim());
|
|
79
|
+
currentPart = '';
|
|
80
|
+
} else {
|
|
81
|
+
currentPart += char;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (currentPart.trim()) {
|
|
86
|
+
parts.push(currentPart.trim());
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
return parts;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
/**
|
|
93
|
+
* Filter nodes based on an expression
|
|
94
|
+
*
|
|
95
|
+
* Filters an array of nodes based on a query expression. Supports various filtering operations
|
|
96
|
+
* including equality checks, contains, and startsWith.
|
|
97
|
+
*
|
|
98
|
+
* @example
|
|
99
|
+
* Filter headings by level
|
|
100
|
+
* const level2Headings = filterNodes(headings, '.level == 2');
|
|
101
|
+
*
|
|
102
|
+
* @example
|
|
103
|
+
* Filter links by URL pattern
|
|
104
|
+
* const externalLinks = filterNodes(links, '.href | startswith("https://")');
|
|
105
|
+
*
|
|
106
|
+
* @example
|
|
107
|
+
* Filter links to specific domain
|
|
108
|
+
* const udxLinks = filterNodes(links, '.href | contains("udx.io")');
|
|
109
|
+
*
|
|
110
|
+
* @param {Array} nodes - Array of nodes to filter
|
|
111
|
+
* @param {string} expr - Filter expression
|
|
112
|
+
* @returns {Array} Filtered array of nodes
|
|
113
|
+
*/
|
|
114
|
+
function filterNodes(nodes, expr) {
|
|
115
|
+
if (!expr.startsWith('select(')) return nodes;
|
|
116
|
+
|
|
117
|
+
// Extract the condition from select()
|
|
118
|
+
const condition = expr.match(/select\((.*)\)/)?.[1];
|
|
119
|
+
if (!condition) return nodes;
|
|
120
|
+
|
|
121
|
+
// Handle common filter patterns
|
|
122
|
+
if (condition.includes(' == ')) {
|
|
123
|
+
// Equality check: .prop == value
|
|
124
|
+
const [propPath, valueStr] = condition.split(' == ').map(s => s.trim());
|
|
125
|
+
const prop = propPath.startsWith('.') ? propPath.slice(1) : propPath;
|
|
126
|
+
|
|
127
|
+
// Parse value (handle quoted strings)
|
|
128
|
+
let value;
|
|
129
|
+
if (valueStr.startsWith('"') && valueStr.endsWith('"')) {
|
|
130
|
+
value = valueStr.slice(1, -1);
|
|
131
|
+
} else if (valueStr === 'true' || valueStr === 'false') {
|
|
132
|
+
value = valueStr === 'true';
|
|
133
|
+
} else if (!isNaN(Number(valueStr))) {
|
|
134
|
+
value = Number(valueStr);
|
|
135
|
+
} else {
|
|
136
|
+
value = valueStr;
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
// Apply filter
|
|
140
|
+
return nodes.filter(node => {
|
|
141
|
+
const propValue = prop.split('.').reduce((obj, p) => obj && obj[p], node);
|
|
142
|
+
|
|
143
|
+
// Special case for filtering headings by level or undefined properties
|
|
144
|
+
if (propValue === undefined) {
|
|
145
|
+
return false; // Skip items with undefined properties
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return propValue === value;
|
|
149
|
+
});
|
|
150
|
+
} else if (condition.includes(' != ')) {
|
|
151
|
+
// Inequality check: .prop != value
|
|
152
|
+
const [propPath, valueStr] = condition.split(' != ').map(s => s.trim());
|
|
153
|
+
const prop = propPath.startsWith('.') ? propPath.slice(1) : propPath;
|
|
154
|
+
|
|
155
|
+
// Parse value (handle quoted strings)
|
|
156
|
+
let value;
|
|
157
|
+
if (valueStr.startsWith('"') && valueStr.endsWith('"')) {
|
|
158
|
+
value = valueStr.slice(1, -1);
|
|
159
|
+
} else if (valueStr === 'true' || valueStr === 'false') {
|
|
160
|
+
value = valueStr === 'true';
|
|
161
|
+
} else if (!isNaN(Number(valueStr))) {
|
|
162
|
+
value = Number(valueStr);
|
|
163
|
+
} else {
|
|
164
|
+
value = valueStr;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
// Apply filter
|
|
168
|
+
return nodes.filter(node => {
|
|
169
|
+
const propValue = prop.split('.').reduce((obj, p) => obj && obj[p], node);
|
|
170
|
+
return propValue !== value;
|
|
171
|
+
});
|
|
172
|
+
} else if (condition.includes(' | contains(')) {
|
|
173
|
+
// Contains check: .prop | contains("value")
|
|
174
|
+
const [propPath, containsExpr] = condition.split(' | ').map(s => s.trim());
|
|
175
|
+
const prop = propPath.startsWith('.') ? propPath.slice(1) : propPath;
|
|
176
|
+
|
|
177
|
+
// More robust regex to extract the value inside contains()
|
|
178
|
+
const valueMatch = containsExpr.match(/contains\(["]([^"]*)["]\)/);
|
|
179
|
+
const valueStr = valueMatch && valueMatch[1] ? valueMatch[1] : '';
|
|
180
|
+
|
|
181
|
+
// Apply filter with improved error handling
|
|
182
|
+
return nodes.filter(node => {
|
|
183
|
+
try {
|
|
184
|
+
const propValue = prop.split('.').reduce((obj, p) => obj && obj[p] !== undefined ? obj[p] : null, node);
|
|
185
|
+
return propValue !== null && String(propValue).includes(valueStr);
|
|
186
|
+
} catch (err) {
|
|
187
|
+
// Silently skip items that cause errors
|
|
188
|
+
return false;
|
|
189
|
+
}
|
|
190
|
+
});
|
|
191
|
+
} else if (condition.includes(' | startswith(')) {
|
|
192
|
+
// StartsWith check: .prop | startswith("value")
|
|
193
|
+
const [propPath, startsWithExpr] = condition.split(' | ').map(s => s.trim());
|
|
194
|
+
const prop = propPath.startsWith('.') ? propPath.slice(1) : propPath;
|
|
195
|
+
const valueStr = startsWithExpr.match(/startswith\("(.*)"\)/)?.[1];
|
|
196
|
+
|
|
197
|
+
if (!valueStr) return nodes;
|
|
198
|
+
|
|
199
|
+
// Apply filter
|
|
200
|
+
return nodes.filter(node => {
|
|
201
|
+
const propValue = prop.split('.').reduce((obj, p) => obj && obj[p], node);
|
|
202
|
+
return typeof propValue === 'string' && propValue.startsWith(valueStr);
|
|
203
|
+
});
|
|
204
|
+
} else if (condition.includes(' > ') || condition.includes(' < ') ||
|
|
205
|
+
condition.includes(' >= ') || condition.includes(' <= ')) {
|
|
206
|
+
// Numeric comparison: .prop > value
|
|
207
|
+
let operator, parts;
|
|
208
|
+
if (condition.includes(' > ')) {
|
|
209
|
+
operator = '>';
|
|
210
|
+
parts = condition.split(' > ');
|
|
211
|
+
} else if (condition.includes(' < ')) {
|
|
212
|
+
operator = '<';
|
|
213
|
+
parts = condition.split(' < ');
|
|
214
|
+
} else if (condition.includes(' >= ')) {
|
|
215
|
+
operator = '>=';
|
|
216
|
+
parts = condition.split(' >= ');
|
|
217
|
+
} else if (condition.includes(' <= ')) {
|
|
218
|
+
operator = '<=';
|
|
219
|
+
parts = condition.split(' <= ');
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
const [propPath, valueStr] = parts.map(s => s.trim());
|
|
223
|
+
const prop = propPath.startsWith('.') ? propPath.slice(1) : propPath;
|
|
224
|
+
const value = Number(valueStr);
|
|
225
|
+
|
|
226
|
+
if (isNaN(value)) return nodes;
|
|
227
|
+
|
|
228
|
+
// Apply filter
|
|
229
|
+
return nodes.filter(node => {
|
|
230
|
+
const propValue = prop.split('.').reduce((obj, p) => obj && obj[p], node);
|
|
231
|
+
switch (operator) {
|
|
232
|
+
case '>': return propValue > value;
|
|
233
|
+
case '<': return propValue < value;
|
|
234
|
+
case '>=': return propValue >= value;
|
|
235
|
+
case '<=': return propValue <= value;
|
|
236
|
+
default: return false;
|
|
237
|
+
}
|
|
238
|
+
});
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
// Default: return unfiltered nodes
|
|
242
|
+
return nodes;
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
/**
|
|
246
|
+
* Construct an object from a pattern
|
|
247
|
+
*
|
|
248
|
+
* Creates objects with specific properties from an array of nodes based on a pattern.
|
|
249
|
+
* Used internally by queryMarkdown to support object construction operations.
|
|
250
|
+
*
|
|
251
|
+
* @example
|
|
252
|
+
* Extract only text and level properties from headings
|
|
253
|
+
* const headingInfo = constructObject(headings, '{text,level}');
|
|
254
|
+
*
|
|
255
|
+
* @example
|
|
256
|
+
* Extract only href property from links
|
|
257
|
+
* const linkUrls = constructObject(links, '{href}');
|
|
258
|
+
*
|
|
259
|
+
* @param {Array} nodes - Array of nodes to extract properties from
|
|
260
|
+
* @param {string} pattern - Pattern string in format '{prop1,prop2,...}'
|
|
261
|
+
* @returns {Array} Array of objects with specified properties
|
|
262
|
+
*/
|
|
263
|
+
function constructObject(nodes, pattern) {
|
|
264
|
+
if (!pattern.startsWith('{') || !pattern.endsWith('}')) return nodes;
|
|
265
|
+
|
|
266
|
+
// Extract properties from pattern {prop1,prop2,...}
|
|
267
|
+
const propsStr = pattern.slice(1, -1);
|
|
268
|
+
const props = propsStr.split(',').map(p => p.trim());
|
|
269
|
+
|
|
270
|
+
// Apply to each node
|
|
271
|
+
return nodes.map(node => {
|
|
272
|
+
const result = {};
|
|
273
|
+
props.forEach(prop => {
|
|
274
|
+
// Handle nested properties: 'meta.author'
|
|
275
|
+
if (prop.includes('.')) {
|
|
276
|
+
const parts = prop.split('.');
|
|
277
|
+
let value = node;
|
|
278
|
+
for (const part of parts) {
|
|
279
|
+
value = value && value[part];
|
|
280
|
+
}
|
|
281
|
+
result[prop] = value;
|
|
282
|
+
} else {
|
|
283
|
+
result[prop] = node[prop];
|
|
284
|
+
}
|
|
285
|
+
});
|
|
286
|
+
return result;
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
/**
|
|
291
|
+
* Format result based on specified format
|
|
292
|
+
*
|
|
293
|
+
* @param {any} result - The result to format
|
|
294
|
+
* @param {string} format - Format to use (json, yaml, markdown)
|
|
295
|
+
* @returns {string} Formatted result
|
|
296
|
+
*/
|
|
297
|
+
function formatResult(result, format = 'markdown') {
|
|
298
|
+
try {
|
|
299
|
+
// Handle different types of results
|
|
300
|
+
if (typeof result === 'string') {
|
|
301
|
+
return result;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
// For empty results, return empty string
|
|
305
|
+
if (Array.isArray(result) && result.length === 0) {
|
|
306
|
+
return '';
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
switch (format.toLowerCase()) {
|
|
310
|
+
case 'json':
|
|
311
|
+
return JSON.stringify(result, null, 2);
|
|
312
|
+
case 'yaml':
|
|
313
|
+
return yaml.dump(result);
|
|
314
|
+
case 'markdown':
|
|
315
|
+
default:
|
|
316
|
+
if (typeof result === 'object' && result.type === 'root') {
|
|
317
|
+
// If it's an AST, convert to markdown
|
|
318
|
+
return toMarkdown(result);
|
|
319
|
+
} else if (Array.isArray(result)) {
|
|
320
|
+
// If it's an array of AST nodes, convert each to markdown
|
|
321
|
+
if (result.some(item => item && item.type)) {
|
|
322
|
+
return result.map(node => {
|
|
323
|
+
if (node && node.type) {
|
|
324
|
+
// Handle single AST node
|
|
325
|
+
const tempAst = { type: 'root', children: [node] };
|
|
326
|
+
return toMarkdown(tempAst);
|
|
327
|
+
} else {
|
|
328
|
+
// Handle plain object
|
|
329
|
+
return JSON.stringify(node, null, 2);
|
|
330
|
+
}
|
|
331
|
+
}).join('\n\n');
|
|
332
|
+
} else {
|
|
333
|
+
// Regular array of objects
|
|
334
|
+
return JSON.stringify(result, null, 2);
|
|
335
|
+
}
|
|
336
|
+
} else {
|
|
337
|
+
// Default to JSON for other object types
|
|
338
|
+
return JSON.stringify(result, null, 2);
|
|
339
|
+
}
|
|
340
|
+
}
|
|
341
|
+
} catch (error) {
|
|
342
|
+
console.error('Error formatting result:', error);
|
|
343
|
+
return String(result);
|
|
344
|
+
}
|
|
345
|
+
}
|
|
346
|
+
|
|
347
|
+
// This default export is already declared at the top of the file
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* mCurl integration utilities for Markdown Query
|
|
3
|
+
*
|
|
4
|
+
* Provides functions for integrating mq with mcurl
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { fromMarkdown } from 'mdast-util-from-markdown';
|
|
8
|
+
import { toMarkdown } from 'mdast-util-to-markdown';
|
|
9
|
+
import _ from 'lodash';
|
|
10
|
+
import { queryMarkdown } from '../operations/query.js';
|
|
11
|
+
import { transformMarkdown } from '../operations/transform.js';
|
|
12
|
+
import { analyzeDocument } from '../operations/analysis.js';
|
|
13
|
+
|
|
14
|
+
/**
|
|
15
|
+
* Main markdown handler for mCurl integration
|
|
16
|
+
*
|
|
17
|
+
* Handles markdown content for mcurl integration, allowing mq queries,
|
|
18
|
+
* transformations, and analysis to be applied to fetched markdown content
|
|
19
|
+
*
|
|
20
|
+
* @param {Object} response - Response object from mcurl
|
|
21
|
+
* @param {Object} options - Options object with mqQuery, mqTransform, and mqAnalyze properties
|
|
22
|
+
* @returns {string} Processed markdown content
|
|
23
|
+
*/
|
|
24
|
+
function markdownHandler(response, options) {
|
|
25
|
+
// Only process markdown content
|
|
26
|
+
if (!response.headers['content-type']?.includes('text/markdown') &&
|
|
27
|
+
!response.url?.endsWith('.md')) {
|
|
28
|
+
return response.body;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
// Get markdown content from response
|
|
32
|
+
const markdown = response.body;
|
|
33
|
+
|
|
34
|
+
try {
|
|
35
|
+
// Parse markdown to AST
|
|
36
|
+
const ast = fromMarkdown(markdown);
|
|
37
|
+
|
|
38
|
+
// Apply query if provided
|
|
39
|
+
if (options.mqQuery) {
|
|
40
|
+
const result = queryMarkdown(ast, options.mqQuery);
|
|
41
|
+
return formatResult(result, options.mqFormat || 'markdown');
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
// Apply transform if provided
|
|
45
|
+
if (options.mqTransform) {
|
|
46
|
+
return transformMarkdown(ast, options.mqTransform);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
// Apply analysis if requested
|
|
50
|
+
if (options.mqAnalyze) {
|
|
51
|
+
return analyzeDocument(ast);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Return unchanged markdown if no operation specified
|
|
55
|
+
return markdown;
|
|
56
|
+
} catch (error) {
|
|
57
|
+
console.error(`Error processing markdown: ${error.message}`);
|
|
58
|
+
return markdown;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
/**
|
|
63
|
+
* Register the markdown handler with mCurl
|
|
64
|
+
*
|
|
65
|
+
* Registers the mq markdown handler with mcurl to enable processing
|
|
66
|
+
* of markdown content fetched by mcurl
|
|
67
|
+
*
|
|
68
|
+
* @returns {boolean} True if registration successful, false otherwise
|
|
69
|
+
*/
|
|
70
|
+
function registerMarkdownHandler() {
|
|
71
|
+
try {
|
|
72
|
+
// Get mcurl module path
|
|
73
|
+
const mcurlPath = '../../mcurl/index.js';
|
|
74
|
+
|
|
75
|
+
// Try to import mcurl
|
|
76
|
+
import(mcurlPath).then(mcurl => {
|
|
77
|
+
// Register handler
|
|
78
|
+
if (mcurl && mcurl.registerContentHandler) {
|
|
79
|
+
mcurl.registerContentHandler('text/markdown', markdownHandler);
|
|
80
|
+
mcurl.registerContentHandler('text/x-markdown', markdownHandler);
|
|
81
|
+
return true;
|
|
82
|
+
}
|
|
83
|
+
return false;
|
|
84
|
+
}).catch(error => {
|
|
85
|
+
console.error(`Error importing mcurl: ${error.message}`);
|
|
86
|
+
return false;
|
|
87
|
+
});
|
|
88
|
+
} catch (error) {
|
|
89
|
+
console.error(`Error registering markdown handler: ${error.message}`);
|
|
90
|
+
return false;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
/**
|
|
95
|
+
* Format result based on specified format
|
|
96
|
+
*
|
|
97
|
+
* @param {any} result - The result to format
|
|
98
|
+
* @param {string} format - Format to use (json, yaml, markdown)
|
|
99
|
+
* @returns {string} Formatted result
|
|
100
|
+
*/
|
|
101
|
+
function formatResult(result, format = 'markdown') {
|
|
102
|
+
switch (format.toLowerCase()) {
|
|
103
|
+
case 'json':
|
|
104
|
+
return JSON.stringify(result, null, 2);
|
|
105
|
+
case 'yaml':
|
|
106
|
+
return yaml.dump(result);
|
|
107
|
+
case 'markdown':
|
|
108
|
+
default:
|
|
109
|
+
if (typeof result === 'string') {
|
|
110
|
+
return result;
|
|
111
|
+
} else if (Array.isArray(result)) {
|
|
112
|
+
return result.map(item =>
|
|
113
|
+
typeof item === 'string' ? item : JSON.stringify(item, null, 2)
|
|
114
|
+
).join('\n\n');
|
|
115
|
+
} else {
|
|
116
|
+
return JSON.stringify(result, null, 2);
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
export {
|
|
122
|
+
markdownHandler,
|
|
123
|
+
registerMarkdownHandler,
|
|
124
|
+
formatResult
|
|
125
|
+
};
|