patram 0.3.0 → 0.5.0
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/lib/build-graph-identity.js +70 -84
- package/lib/build-graph.js +171 -19
- package/lib/build-graph.types.ts +1 -0
- package/lib/check-directive-metadata.js +36 -4
- package/lib/check-directive-value.js +9 -0
- package/lib/check-graph.js +1 -2
- package/lib/cli-help-metadata.js +12 -0
- package/lib/command-output.js +16 -1
- package/lib/discover-fields.js +9 -1
- package/lib/document-node-identity.js +317 -0
- package/lib/layout-stored-queries.js +122 -29
- package/lib/load-patram-config.js +172 -112
- package/lib/load-patram-config.types.ts +50 -152
- package/lib/parse-claims.js +2 -3
- package/lib/parse-jsdoc-claims.js +3 -3
- package/lib/parse-where-clause.js +237 -66
- package/lib/parse-where-clause.types.ts +21 -6
- package/lib/patram-cli.js +34 -2
- package/lib/patram-config.js +26 -9
- package/lib/patram-config.types.ts +18 -36
- package/lib/query-graph.js +29 -19
- package/lib/query-inspection.js +173 -68
- package/lib/render-field-discovery.js +44 -8
- package/lib/render-output-view.js +72 -27
- package/lib/render-rich-source.js +245 -14
- package/lib/resolve-patram-graph-config.js +40 -2
- package/lib/show-document.js +15 -2
- package/package.json +1 -1
|
@@ -5,6 +5,8 @@
|
|
|
5
5
|
|
|
6
6
|
import { Ansis } from 'ansis';
|
|
7
7
|
|
|
8
|
+
const MAX_TEXT_EVIDENCE_ROWS = 5;
|
|
9
|
+
|
|
8
10
|
/**
|
|
9
11
|
* Render field discovery output.
|
|
10
12
|
*
|
|
@@ -109,21 +111,21 @@ function formatTextFieldSuggestion(field_suggestion, render_options) {
|
|
|
109
111
|
);
|
|
110
112
|
|
|
111
113
|
if (field_suggestion.evidence_references.length > 0) {
|
|
112
|
-
lines.push(render_options.label(' evidence:'));
|
|
113
114
|
lines.push(
|
|
114
|
-
...
|
|
115
|
-
|
|
116
|
-
|
|
115
|
+
...formatTextEvidenceSection(
|
|
116
|
+
' evidence:',
|
|
117
|
+
field_suggestion.evidence_references,
|
|
118
|
+
render_options,
|
|
117
119
|
),
|
|
118
120
|
);
|
|
119
121
|
}
|
|
120
122
|
|
|
121
123
|
if (field_suggestion.conflicting_evidence.length > 0) {
|
|
122
|
-
lines.push(render_options.label(' conflicting evidence:'));
|
|
123
124
|
lines.push(
|
|
124
|
-
...
|
|
125
|
-
|
|
126
|
-
|
|
125
|
+
...formatTextEvidenceSection(
|
|
126
|
+
' conflicting evidence:',
|
|
127
|
+
field_suggestion.conflicting_evidence,
|
|
128
|
+
render_options,
|
|
127
129
|
),
|
|
128
130
|
);
|
|
129
131
|
}
|
|
@@ -131,6 +133,40 @@ function formatTextFieldSuggestion(field_suggestion, render_options) {
|
|
|
131
133
|
return lines;
|
|
132
134
|
}
|
|
133
135
|
|
|
136
|
+
/**
|
|
137
|
+
* @param {string} section_title
|
|
138
|
+
* @param {import('./discover-fields.types.ts').FieldDiscoveryEvidenceReference[]} evidence_references
|
|
139
|
+
* @param {{ header: (value: string) => string, label: (value: string) => string }} render_options
|
|
140
|
+
* @returns {string[]}
|
|
141
|
+
*/
|
|
142
|
+
function formatTextEvidenceSection(
|
|
143
|
+
section_title,
|
|
144
|
+
evidence_references,
|
|
145
|
+
render_options,
|
|
146
|
+
) {
|
|
147
|
+
/** @type {string[]} */
|
|
148
|
+
const lines = [render_options.label(section_title)];
|
|
149
|
+
const visible_evidence_references = evidence_references.slice(
|
|
150
|
+
0,
|
|
151
|
+
MAX_TEXT_EVIDENCE_ROWS,
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
lines.push(
|
|
155
|
+
...visible_evidence_references.map(
|
|
156
|
+
(evidence_reference) =>
|
|
157
|
+
`${render_options.label(' ')}${formatEvidenceReference(evidence_reference)}`,
|
|
158
|
+
),
|
|
159
|
+
);
|
|
160
|
+
|
|
161
|
+
if (evidence_references.length > MAX_TEXT_EVIDENCE_ROWS) {
|
|
162
|
+
const remaining_count = evidence_references.length - MAX_TEXT_EVIDENCE_ROWS;
|
|
163
|
+
|
|
164
|
+
lines.push(render_options.label(` ${remaining_count} more ...`));
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
return lines;
|
|
168
|
+
}
|
|
169
|
+
|
|
134
170
|
/**
|
|
135
171
|
* @param {import('./discover-fields.types.ts').FieldDiscoveryEvidenceReference} evidence_reference
|
|
136
172
|
* @returns {string}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
|
-
/** @import * as $k$$l$output$j$view$k$types$k$ts from './output-view.types.ts'; */
|
|
2
|
-
/* eslint-disable max-lines */
|
|
3
1
|
/**
|
|
4
2
|
* @import { BuildGraphResult, GraphNode } from './build-graph.types.ts';
|
|
5
3
|
* @import { DerivedSummaryEvaluator } from './derived-summary.js';
|
|
6
4
|
* @import { PatramRepoConfig } from './load-patram-config.types.ts';
|
|
7
5
|
* @import { ParsedCliArguments } from './parse-cli-arguments.types.ts';
|
|
8
|
-
* @import { OutputMetadataField, OutputStoredQueryItem, OutputView, ResolvedOutputMode, ShowOutputView } from './output-view.types.ts';
|
|
6
|
+
* @import { OutputDerivedSummary, OutputMetadataField, OutputNodeItem, OutputResolvedLinkItem, OutputResolvedLinkTarget, OutputStoredQueryItem, OutputView, ResolvedOutputMode, ShowOutputView } from './output-view.types.ts';
|
|
9
7
|
*/
|
|
8
|
+
/* eslint-disable max-lines */
|
|
10
9
|
|
|
11
10
|
import { renderJsonOutput } from './render-json-output.js';
|
|
12
11
|
import { renderPlainOutput } from './render-plain-output.js';
|
|
13
12
|
import { renderRichOutput } from './render-rich-output.js';
|
|
13
|
+
import { resolveDocumentNodeId } from './build-graph-identity.js';
|
|
14
14
|
|
|
15
15
|
/**
|
|
16
16
|
* Shared command output views.
|
|
@@ -56,12 +56,15 @@ export function createOutputView(command_name, command_items, command_options) {
|
|
|
56
56
|
* Create a shared output view for the show command.
|
|
57
57
|
*
|
|
58
58
|
* @param {{ path: string, rendered_source: string, resolved_links: Array<{ label: string, reference: number, target: { kind?: string, path: string, status?: string, title: string } }>, source: string }} show_output
|
|
59
|
-
* @param {{ derived_summary_evaluator?: DerivedSummaryEvaluator, graph_nodes?: BuildGraphResult['nodes'], repo_config?: PatramRepoConfig }=} command_options
|
|
59
|
+
* @param {{ derived_summary_evaluator?: DerivedSummaryEvaluator, document_node_ids?: BuildGraphResult['document_node_ids'], graph_nodes?: BuildGraphResult['nodes'], repo_config?: PatramRepoConfig }=} command_options
|
|
60
60
|
* @returns {ShowOutputView}
|
|
61
61
|
*/
|
|
62
62
|
export function createShowOutputView(show_output, command_options = {}) {
|
|
63
|
-
const shown_document_node =
|
|
64
|
-
command_options.graph_nodes
|
|
63
|
+
const shown_document_node = resolveDocumentGraphNode(
|
|
64
|
+
command_options.graph_nodes,
|
|
65
|
+
command_options.document_node_ids,
|
|
66
|
+
show_output.path,
|
|
67
|
+
);
|
|
65
68
|
|
|
66
69
|
return {
|
|
67
70
|
command: 'show',
|
|
@@ -75,20 +78,9 @@ export function createShowOutputView(show_output, command_options = {}) {
|
|
|
75
78
|
)
|
|
76
79
|
: undefined,
|
|
77
80
|
hints: [],
|
|
78
|
-
items: show_output.resolved_links.map((resolved_link) =>
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
reference: resolved_link.reference,
|
|
82
|
-
target: createResolvedLinkTarget(
|
|
83
|
-
resolved_link.target,
|
|
84
|
-
command_options.repo_config?.fields ?? {},
|
|
85
|
-
command_options.graph_nodes?.[`doc:${resolved_link.target.path}`]
|
|
86
|
-
? (command_options.derived_summary_evaluator?.evaluate(
|
|
87
|
-
command_options.graph_nodes[`doc:${resolved_link.target.path}`],
|
|
88
|
-
) ?? null)
|
|
89
|
-
: null,
|
|
90
|
-
),
|
|
91
|
-
})),
|
|
81
|
+
items: show_output.resolved_links.map((resolved_link) =>
|
|
82
|
+
createResolvedLinkOutputItem(resolved_link, command_options),
|
|
83
|
+
),
|
|
92
84
|
path: show_output.path,
|
|
93
85
|
rendered_source: show_output.rendered_source,
|
|
94
86
|
source: show_output.source,
|
|
@@ -178,9 +170,9 @@ function createStoredQueriesOutputView(stored_queries) {
|
|
|
178
170
|
|
|
179
171
|
/**
|
|
180
172
|
* @param {GraphNode} graph_node
|
|
181
|
-
* @param {
|
|
173
|
+
* @param {OutputDerivedSummary | null} derived_summary
|
|
182
174
|
* @param {NonNullable<PatramRepoConfig['fields']>} field_definitions
|
|
183
|
-
* @returns {
|
|
175
|
+
* @returns {OutputNodeItem}
|
|
184
176
|
*/
|
|
185
177
|
function createOutputNodeItem(graph_node, derived_summary, field_definitions) {
|
|
186
178
|
const title = getOutputNodeTitle(graph_node);
|
|
@@ -210,10 +202,16 @@ function createOutputNodeItem(graph_node, derived_summary, field_definitions) {
|
|
|
210
202
|
/**
|
|
211
203
|
* @param {{ kind?: string, path: string, status?: string, title: string }} target
|
|
212
204
|
* @param {NonNullable<PatramRepoConfig['fields']>} field_definitions
|
|
213
|
-
* @param {
|
|
214
|
-
* @
|
|
205
|
+
* @param {OutputDerivedSummary | null} derived_summary
|
|
206
|
+
* @param {GraphNode | undefined} graph_node
|
|
207
|
+
* @returns {OutputResolvedLinkTarget}
|
|
215
208
|
*/
|
|
216
|
-
function createResolvedLinkTarget(
|
|
209
|
+
function createResolvedLinkTarget(
|
|
210
|
+
target,
|
|
211
|
+
field_definitions,
|
|
212
|
+
derived_summary,
|
|
213
|
+
graph_node,
|
|
214
|
+
) {
|
|
217
215
|
/** @type {Record<string, string | string[]>} */
|
|
218
216
|
const fields = {};
|
|
219
217
|
|
|
@@ -221,11 +219,11 @@ function createResolvedLinkTarget(target, field_definitions, derived_summary) {
|
|
|
221
219
|
fields.status = target.status;
|
|
222
220
|
}
|
|
223
221
|
|
|
224
|
-
/** @type {
|
|
222
|
+
/** @type {OutputResolvedLinkTarget} */
|
|
225
223
|
const resolved_target = {
|
|
226
224
|
derived_summary: derived_summary ?? undefined,
|
|
227
225
|
fields,
|
|
228
|
-
id: `doc:${target.path}`,
|
|
226
|
+
id: graph_node ? getOutputNodeId(graph_node) : `doc:${target.path}`,
|
|
229
227
|
kind: target.kind ?? 'document',
|
|
230
228
|
path: target.path,
|
|
231
229
|
title: target.title,
|
|
@@ -286,6 +284,53 @@ function getOutputNodeId(graph_node) {
|
|
|
286
284
|
);
|
|
287
285
|
}
|
|
288
286
|
|
|
287
|
+
/**
|
|
288
|
+
* @param {{ label: string, reference: number, target: { kind?: string, path: string, status?: string, title: string } }} resolved_link
|
|
289
|
+
* @param {{ derived_summary_evaluator?: DerivedSummaryEvaluator, document_node_ids?: BuildGraphResult['document_node_ids'], graph_nodes?: BuildGraphResult['nodes'], repo_config?: PatramRepoConfig }} command_options
|
|
290
|
+
* @returns {OutputResolvedLinkItem}
|
|
291
|
+
*/
|
|
292
|
+
function createResolvedLinkOutputItem(resolved_link, command_options) {
|
|
293
|
+
const target_graph_node = resolveDocumentGraphNode(
|
|
294
|
+
command_options.graph_nodes,
|
|
295
|
+
command_options.document_node_ids,
|
|
296
|
+
resolved_link.target.path,
|
|
297
|
+
);
|
|
298
|
+
|
|
299
|
+
return {
|
|
300
|
+
kind: 'resolved_link',
|
|
301
|
+
label: resolved_link.label,
|
|
302
|
+
reference: resolved_link.reference,
|
|
303
|
+
target: createResolvedLinkTarget(
|
|
304
|
+
resolved_link.target,
|
|
305
|
+
command_options.repo_config?.fields ?? {},
|
|
306
|
+
target_graph_node
|
|
307
|
+
? (command_options.derived_summary_evaluator?.evaluate(
|
|
308
|
+
target_graph_node,
|
|
309
|
+
) ?? null)
|
|
310
|
+
: null,
|
|
311
|
+
target_graph_node,
|
|
312
|
+
),
|
|
313
|
+
};
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
/**
|
|
317
|
+
* @param {BuildGraphResult['nodes'] | undefined} graph_nodes
|
|
318
|
+
* @param {BuildGraphResult['document_node_ids'] | undefined} document_node_ids
|
|
319
|
+
* @param {string} document_path
|
|
320
|
+
* @returns {GraphNode | undefined}
|
|
321
|
+
*/
|
|
322
|
+
function resolveDocumentGraphNode(
|
|
323
|
+
graph_nodes,
|
|
324
|
+
document_node_ids,
|
|
325
|
+
document_path,
|
|
326
|
+
) {
|
|
327
|
+
if (!graph_nodes) {
|
|
328
|
+
return undefined;
|
|
329
|
+
}
|
|
330
|
+
|
|
331
|
+
return graph_nodes[resolveDocumentNodeId(document_node_ids, document_path)];
|
|
332
|
+
}
|
|
333
|
+
|
|
289
334
|
/**
|
|
290
335
|
* @param {GraphNode} graph_node
|
|
291
336
|
* @param {NonNullable<PatramRepoConfig['fields']>} field_definitions
|
|
@@ -46,6 +46,16 @@ const SHIKI_LANGUAGE_NAMES = new Set([
|
|
|
46
46
|
...Object.keys(bundledLanguagesAlias),
|
|
47
47
|
]);
|
|
48
48
|
|
|
49
|
+
/**
|
|
50
|
+
* @typedef {{
|
|
51
|
+
* ansi: Ansis,
|
|
52
|
+
* next_reference: number,
|
|
53
|
+
* next_top_level_list: number,
|
|
54
|
+
* resolved_links: OutputResolvedLinkItem[],
|
|
55
|
+
* top_level_list_item_gaps: boolean[][]
|
|
56
|
+
* }} RichSourceRenderState
|
|
57
|
+
*/
|
|
58
|
+
|
|
49
59
|
/**
|
|
50
60
|
* @param {ShowOutputView} output_view
|
|
51
61
|
* @param {{ color_mode: CliColorMode, color_enabled: boolean }} render_options
|
|
@@ -70,11 +80,13 @@ async function renderRichMarkdownSource(output_view, ansi) {
|
|
|
70
80
|
const markdown_tree = parseAST(output_view.source);
|
|
71
81
|
/** @type {string[]} */
|
|
72
82
|
const rendered_blocks = [];
|
|
73
|
-
/** @type {
|
|
83
|
+
/** @type {RichSourceRenderState} */
|
|
74
84
|
const render_state = {
|
|
75
85
|
ansi,
|
|
76
86
|
next_reference: 1,
|
|
87
|
+
next_top_level_list: 0,
|
|
77
88
|
resolved_links: output_view.items,
|
|
89
|
+
top_level_list_item_gaps: collectTopLevelListItemGaps(output_view.source),
|
|
78
90
|
};
|
|
79
91
|
|
|
80
92
|
for (const node of markdown_tree.nodes) {
|
|
@@ -107,7 +119,7 @@ async function renderRichSourceFile(source_path, source_text, ansi) {
|
|
|
107
119
|
|
|
108
120
|
/**
|
|
109
121
|
* @param {ComarkNode} node
|
|
110
|
-
* @param {
|
|
122
|
+
* @param {RichSourceRenderState} render_state
|
|
111
123
|
* @param {number} indent_level
|
|
112
124
|
* @returns {Promise<string>}
|
|
113
125
|
*/
|
|
@@ -131,8 +143,14 @@ async function renderBlockNode(node, render_state, indent_level) {
|
|
|
131
143
|
return renderFencedCodeBlock(node, indent_level, render_state.ansi);
|
|
132
144
|
}
|
|
133
145
|
|
|
134
|
-
if (node_tag
|
|
135
|
-
return renderListBlock(
|
|
146
|
+
if (isListTag(node_tag)) {
|
|
147
|
+
return renderListBlock(
|
|
148
|
+
/** @type {'ol' | 'ul'} */ (node_tag),
|
|
149
|
+
node_children,
|
|
150
|
+
render_state,
|
|
151
|
+
indent_level,
|
|
152
|
+
indent_level === 0 ? getNextTopLevelListItemGaps(render_state) : [],
|
|
153
|
+
);
|
|
136
154
|
}
|
|
137
155
|
|
|
138
156
|
if (node_tag === 'blockquote') {
|
|
@@ -152,7 +170,7 @@ async function renderBlockNode(node, render_state, indent_level) {
|
|
|
152
170
|
|
|
153
171
|
/**
|
|
154
172
|
* @param {ComarkNode[]} nodes
|
|
155
|
-
* @param {
|
|
173
|
+
* @param {RichSourceRenderState} render_state
|
|
156
174
|
* @returns {string}
|
|
157
175
|
*/
|
|
158
176
|
function renderInlineNodes(nodes, render_state) {
|
|
@@ -217,13 +235,23 @@ function renderInlineNodes(nodes, render_state) {
|
|
|
217
235
|
/**
|
|
218
236
|
* @param {'ol' | 'ul'} nodes_type
|
|
219
237
|
* @param {ComarkNode[]} nodes
|
|
220
|
-
* @param {
|
|
238
|
+
* @param {RichSourceRenderState} render_state
|
|
221
239
|
* @param {number} indent_level
|
|
240
|
+
* @param {boolean[]} list_item_gaps
|
|
222
241
|
* @returns {Promise<string>}
|
|
223
242
|
*/
|
|
224
|
-
async function renderListBlock(
|
|
243
|
+
async function renderListBlock(
|
|
244
|
+
nodes_type,
|
|
245
|
+
nodes,
|
|
246
|
+
render_state,
|
|
247
|
+
indent_level,
|
|
248
|
+
list_item_gaps,
|
|
249
|
+
) {
|
|
225
250
|
/** @type {string[]} */
|
|
226
251
|
const rendered_items = [];
|
|
252
|
+
/** @type {ComarkElement | null} */
|
|
253
|
+
let previous_item = null;
|
|
254
|
+
let rendered_item_count = 0;
|
|
227
255
|
|
|
228
256
|
for (let item_index = 0; item_index < nodes.length; item_index += 1) {
|
|
229
257
|
const node = nodes[item_index];
|
|
@@ -232,6 +260,17 @@ async function renderListBlock(nodes_type, nodes, render_state, indent_level) {
|
|
|
232
260
|
continue;
|
|
233
261
|
}
|
|
234
262
|
|
|
263
|
+
if (
|
|
264
|
+
previous_item &&
|
|
265
|
+
shouldRenderListItemGap(
|
|
266
|
+
previous_item,
|
|
267
|
+
node,
|
|
268
|
+
list_item_gaps[rendered_item_count - 1] ?? false,
|
|
269
|
+
)
|
|
270
|
+
) {
|
|
271
|
+
rendered_items.push('');
|
|
272
|
+
}
|
|
273
|
+
|
|
235
274
|
rendered_items.push(
|
|
236
275
|
await renderListItem(
|
|
237
276
|
node,
|
|
@@ -241,6 +280,8 @@ async function renderListBlock(nodes_type, nodes, render_state, indent_level) {
|
|
|
241
280
|
indent_level,
|
|
242
281
|
),
|
|
243
282
|
);
|
|
283
|
+
previous_item = node;
|
|
284
|
+
rendered_item_count += 1;
|
|
244
285
|
}
|
|
245
286
|
|
|
246
287
|
return rendered_items.join('\n');
|
|
@@ -250,7 +291,7 @@ async function renderListBlock(nodes_type, nodes, render_state, indent_level) {
|
|
|
250
291
|
* @param {ComarkElement} node
|
|
251
292
|
* @param {number} item_number
|
|
252
293
|
* @param {boolean} is_ordered
|
|
253
|
-
* @param {
|
|
294
|
+
* @param {RichSourceRenderState} render_state
|
|
254
295
|
* @param {number} indent_level
|
|
255
296
|
* @returns {Promise<string>}
|
|
256
297
|
*/
|
|
@@ -298,7 +339,7 @@ async function renderListItem(
|
|
|
298
339
|
|
|
299
340
|
/**
|
|
300
341
|
* @param {ComarkNode[]} nodes
|
|
301
|
-
* @param {
|
|
342
|
+
* @param {RichSourceRenderState} render_state
|
|
302
343
|
* @param {number} indent_level
|
|
303
344
|
* @returns {Promise<string>}
|
|
304
345
|
*/
|
|
@@ -461,7 +502,7 @@ function renderHeading(tag_name, nodes, ansi) {
|
|
|
461
502
|
|
|
462
503
|
/**
|
|
463
504
|
* @param {ComarkElement} node
|
|
464
|
-
* @param {
|
|
505
|
+
* @param {RichSourceRenderState} render_state
|
|
465
506
|
* @returns {string}
|
|
466
507
|
*/
|
|
467
508
|
function renderLinkNode(node, render_state) {
|
|
@@ -618,7 +659,11 @@ function renderCodeBlock(
|
|
|
618
659
|
content_indent = 0,
|
|
619
660
|
) {
|
|
620
661
|
const label = formatCodeBlockLabel(language_label, file_name);
|
|
621
|
-
const content_width = measureCodeBlockWidth(
|
|
662
|
+
const content_width = measureCodeBlockWidth(
|
|
663
|
+
label,
|
|
664
|
+
source_lines,
|
|
665
|
+
content_indent,
|
|
666
|
+
);
|
|
622
667
|
/** @type {string[]} */
|
|
623
668
|
const rendered_lines = [];
|
|
624
669
|
|
|
@@ -777,7 +822,7 @@ function extractTableRowCells(row_node) {
|
|
|
777
822
|
|
|
778
823
|
/**
|
|
779
824
|
* @param {ComarkElement} node
|
|
780
|
-
* @param {
|
|
825
|
+
* @param {RichSourceRenderState} render_state
|
|
781
826
|
* @returns {{ block_nodes: ComarkElement[], lead_text: string | null }}
|
|
782
827
|
*/
|
|
783
828
|
function collectListItemParts(node, render_state) {
|
|
@@ -836,6 +881,183 @@ function collectListItemParts(node, render_state) {
|
|
|
836
881
|
};
|
|
837
882
|
}
|
|
838
883
|
|
|
884
|
+
/**
|
|
885
|
+
* @param {{ next_top_level_list: number, top_level_list_item_gaps: boolean[][] }} render_state
|
|
886
|
+
* @returns {boolean[]}
|
|
887
|
+
*/
|
|
888
|
+
function getNextTopLevelListItemGaps(render_state) {
|
|
889
|
+
const list_item_gaps =
|
|
890
|
+
render_state.top_level_list_item_gaps[render_state.next_top_level_list];
|
|
891
|
+
|
|
892
|
+
render_state.next_top_level_list += 1;
|
|
893
|
+
|
|
894
|
+
return list_item_gaps ?? [];
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
/**
|
|
898
|
+
* @param {string} source_text
|
|
899
|
+
* @returns {boolean[][]}
|
|
900
|
+
*/
|
|
901
|
+
function collectTopLevelListItemGaps(source_text) {
|
|
902
|
+
const source_lines = source_text.split('\n');
|
|
903
|
+
/** @type {boolean[][]} */
|
|
904
|
+
const top_level_list_item_gaps = [];
|
|
905
|
+
/** @type {boolean[] | null} */
|
|
906
|
+
let current_list_item_gaps = null;
|
|
907
|
+
let saw_blank_line = false;
|
|
908
|
+
/** @type {{ character: '`' | '~', length: number } | null} */
|
|
909
|
+
let active_fence = null;
|
|
910
|
+
|
|
911
|
+
for (const source_line of source_lines) {
|
|
912
|
+
if (active_fence) {
|
|
913
|
+
if (isClosingTopLevelFenceLine(source_line, active_fence)) {
|
|
914
|
+
active_fence = null;
|
|
915
|
+
}
|
|
916
|
+
|
|
917
|
+
continue;
|
|
918
|
+
}
|
|
919
|
+
|
|
920
|
+
const top_level_fence = parseTopLevelFence(source_line);
|
|
921
|
+
|
|
922
|
+
if (top_level_fence) {
|
|
923
|
+
active_fence = top_level_fence;
|
|
924
|
+
current_list_item_gaps = null;
|
|
925
|
+
saw_blank_line = false;
|
|
926
|
+
continue;
|
|
927
|
+
}
|
|
928
|
+
|
|
929
|
+
if (source_line.trim().length === 0) {
|
|
930
|
+
saw_blank_line = current_list_item_gaps !== null;
|
|
931
|
+
continue;
|
|
932
|
+
}
|
|
933
|
+
|
|
934
|
+
if (isTopLevelListItemLine(source_line)) {
|
|
935
|
+
current_list_item_gaps = pushTopLevelListItemGap(
|
|
936
|
+
current_list_item_gaps,
|
|
937
|
+
top_level_list_item_gaps,
|
|
938
|
+
saw_blank_line,
|
|
939
|
+
);
|
|
940
|
+
saw_blank_line = false;
|
|
941
|
+
continue;
|
|
942
|
+
}
|
|
943
|
+
|
|
944
|
+
if (current_list_item_gaps === null) {
|
|
945
|
+
continue;
|
|
946
|
+
}
|
|
947
|
+
|
|
948
|
+
if (source_line.startsWith(' ') || source_line.startsWith('\t')) {
|
|
949
|
+
saw_blank_line = false;
|
|
950
|
+
continue;
|
|
951
|
+
}
|
|
952
|
+
|
|
953
|
+
current_list_item_gaps = null;
|
|
954
|
+
saw_blank_line = false;
|
|
955
|
+
}
|
|
956
|
+
|
|
957
|
+
return top_level_list_item_gaps;
|
|
958
|
+
}
|
|
959
|
+
|
|
960
|
+
/**
|
|
961
|
+
* @param {ComarkElement} previous_item
|
|
962
|
+
* @param {ComarkElement} next_item
|
|
963
|
+
* @param {boolean} has_blank_line_gap
|
|
964
|
+
* @returns {boolean}
|
|
965
|
+
*/
|
|
966
|
+
function shouldRenderListItemGap(previous_item, next_item, has_blank_line_gap) {
|
|
967
|
+
if (!has_blank_line_gap) {
|
|
968
|
+
return false;
|
|
969
|
+
}
|
|
970
|
+
|
|
971
|
+
return (
|
|
972
|
+
isSimpleTopLevelListItem(previous_item) &&
|
|
973
|
+
isSimpleTopLevelListItem(next_item)
|
|
974
|
+
);
|
|
975
|
+
}
|
|
976
|
+
|
|
977
|
+
/**
|
|
978
|
+
* @param {ComarkElement} item_node
|
|
979
|
+
* @returns {boolean}
|
|
980
|
+
*/
|
|
981
|
+
function isSimpleTopLevelListItem(item_node) {
|
|
982
|
+
const item_children = getElementChildren(item_node);
|
|
983
|
+
|
|
984
|
+
if (item_children.length !== 1) {
|
|
985
|
+
return false;
|
|
986
|
+
}
|
|
987
|
+
|
|
988
|
+
const paragraph_node = item_children[0];
|
|
989
|
+
|
|
990
|
+
if (
|
|
991
|
+
typeof paragraph_node === 'string' ||
|
|
992
|
+
getElementTag(paragraph_node) !== 'p'
|
|
993
|
+
) {
|
|
994
|
+
return false;
|
|
995
|
+
}
|
|
996
|
+
|
|
997
|
+
return !extractInlineText(getElementChildren(paragraph_node)).includes('\n');
|
|
998
|
+
}
|
|
999
|
+
|
|
1000
|
+
/**
|
|
1001
|
+
* @param {string} source_line
|
|
1002
|
+
* @returns {boolean}
|
|
1003
|
+
*/
|
|
1004
|
+
function isTopLevelListItemLine(source_line) {
|
|
1005
|
+
return /^([*+-]|\d+[.)])(?:\s+|$)/du.test(source_line);
|
|
1006
|
+
}
|
|
1007
|
+
|
|
1008
|
+
/**
|
|
1009
|
+
* @param {string} source_line
|
|
1010
|
+
* @returns {{ character: '`' | '~', length: number } | null}
|
|
1011
|
+
*/
|
|
1012
|
+
function parseTopLevelFence(source_line) {
|
|
1013
|
+
const fence_match = /^(?<character>`|~)\k<character>{2,}/du.exec(source_line);
|
|
1014
|
+
|
|
1015
|
+
if (!fence_match?.groups?.character) {
|
|
1016
|
+
return null;
|
|
1017
|
+
}
|
|
1018
|
+
|
|
1019
|
+
return {
|
|
1020
|
+
character: /** @type {'`' | '~'} */ (fence_match.groups.character),
|
|
1021
|
+
length: fence_match[0].length,
|
|
1022
|
+
};
|
|
1023
|
+
}
|
|
1024
|
+
|
|
1025
|
+
/**
|
|
1026
|
+
* @param {string} source_line
|
|
1027
|
+
* @param {{ character: '`' | '~', length: number }} active_fence
|
|
1028
|
+
* @returns {boolean}
|
|
1029
|
+
*/
|
|
1030
|
+
function isClosingTopLevelFenceLine(source_line, active_fence) {
|
|
1031
|
+
const closing_pattern = new RegExp(
|
|
1032
|
+
`^${active_fence.character}{${active_fence.length},}\\s*$`,
|
|
1033
|
+
'u',
|
|
1034
|
+
);
|
|
1035
|
+
|
|
1036
|
+
return closing_pattern.test(source_line);
|
|
1037
|
+
}
|
|
1038
|
+
|
|
1039
|
+
/**
|
|
1040
|
+
* @param {boolean[] | null} current_list_item_gaps
|
|
1041
|
+
* @param {boolean[][]} top_level_list_item_gaps
|
|
1042
|
+
* @param {boolean} saw_blank_line
|
|
1043
|
+
* @returns {boolean[]}
|
|
1044
|
+
*/
|
|
1045
|
+
function pushTopLevelListItemGap(
|
|
1046
|
+
current_list_item_gaps,
|
|
1047
|
+
top_level_list_item_gaps,
|
|
1048
|
+
saw_blank_line,
|
|
1049
|
+
) {
|
|
1050
|
+
if (current_list_item_gaps === null) {
|
|
1051
|
+
current_list_item_gaps = [];
|
|
1052
|
+
top_level_list_item_gaps.push(current_list_item_gaps);
|
|
1053
|
+
return current_list_item_gaps;
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
current_list_item_gaps.push(saw_blank_line);
|
|
1057
|
+
|
|
1058
|
+
return current_list_item_gaps;
|
|
1059
|
+
}
|
|
1060
|
+
|
|
839
1061
|
/**
|
|
840
1062
|
* @param {string} text
|
|
841
1063
|
* @param {string} item_prefix
|
|
@@ -1001,6 +1223,14 @@ function isHeadingTag(tag_name) {
|
|
|
1001
1223
|
return /^h[1-6]$/du.test(tag_name);
|
|
1002
1224
|
}
|
|
1003
1225
|
|
|
1226
|
+
/**
|
|
1227
|
+
* @param {string} tag_name
|
|
1228
|
+
* @returns {boolean}
|
|
1229
|
+
*/
|
|
1230
|
+
function isListTag(tag_name) {
|
|
1231
|
+
return tag_name === 'ol' || tag_name === 'ul';
|
|
1232
|
+
}
|
|
1233
|
+
|
|
1004
1234
|
/**
|
|
1005
1235
|
* @param {string} tag_name
|
|
1006
1236
|
* @returns {number}
|
|
@@ -1136,13 +1366,14 @@ function renderCodeBlockLine(
|
|
|
1136
1366
|
/**
|
|
1137
1367
|
* @param {string} label
|
|
1138
1368
|
* @param {string[]} source_lines
|
|
1369
|
+
* @param {number} content_indent
|
|
1139
1370
|
* @returns {number}
|
|
1140
1371
|
*/
|
|
1141
|
-
function measureCodeBlockWidth(label, source_lines) {
|
|
1372
|
+
function measureCodeBlockWidth(label, source_lines, content_indent = 0) {
|
|
1142
1373
|
return Math.max(
|
|
1143
1374
|
BLOCK_WIDTH - 2,
|
|
1144
1375
|
stringWidth(label),
|
|
1145
|
-
measureMaxLineWidth(source_lines),
|
|
1376
|
+
measureMaxLineWidth(source_lines) + content_indent,
|
|
1146
1377
|
);
|
|
1147
1378
|
}
|
|
1148
1379
|
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
+
* @import { ClassDefinition } from './patram-config.js';
|
|
2
3
|
* @import { PatramRepoConfig } from './load-patram-config.types.ts';
|
|
3
4
|
* @import { PatramConfig } from './patram-config.types.ts';
|
|
4
5
|
*/
|
|
@@ -74,7 +75,7 @@ export function resolvePatramGraphConfig(repo_config) {
|
|
|
74
75
|
const graph_config = parsePatramConfig({
|
|
75
76
|
classes: {
|
|
76
77
|
...BUILT_IN_PATRAM_CONFIG.classes,
|
|
77
|
-
...repo_config.classes,
|
|
78
|
+
...collectGraphClassDefinitions(repo_config.classes),
|
|
78
79
|
},
|
|
79
80
|
mappings: {
|
|
80
81
|
...BUILT_IN_PATRAM_CONFIG.mappings,
|
|
@@ -88,7 +89,44 @@ export function resolvePatramGraphConfig(repo_config) {
|
|
|
88
89
|
|
|
89
90
|
return {
|
|
90
91
|
...graph_config,
|
|
91
|
-
|
|
92
|
+
classes: mergeResolvedClasses(graph_config.classes, repo_config.classes),
|
|
92
93
|
fields: repo_config.fields,
|
|
93
94
|
};
|
|
94
95
|
}
|
|
96
|
+
|
|
97
|
+
/**
|
|
98
|
+
* @param {PatramRepoConfig['classes']} classes
|
|
99
|
+
* @returns {Record<string, ClassDefinition>}
|
|
100
|
+
*/
|
|
101
|
+
function collectGraphClassDefinitions(classes) {
|
|
102
|
+
/** @type {Record<string, ClassDefinition>} */
|
|
103
|
+
const graph_class_definitions = {};
|
|
104
|
+
|
|
105
|
+
for (const [class_name, class_definition] of Object.entries(classes ?? {})) {
|
|
106
|
+
graph_class_definitions[class_name] = {
|
|
107
|
+
builtin: class_definition.builtin,
|
|
108
|
+
label: class_definition.label,
|
|
109
|
+
};
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
return graph_class_definitions;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
/**
|
|
116
|
+
* @param {Record<string, ClassDefinition>} graph_classes
|
|
117
|
+
* @param {PatramRepoConfig['classes']} repo_classes
|
|
118
|
+
* @returns {PatramConfig['classes']}
|
|
119
|
+
*/
|
|
120
|
+
function mergeResolvedClasses(graph_classes, repo_classes) {
|
|
121
|
+
/** @type {PatramConfig['classes']} */
|
|
122
|
+
const resolved_classes = {};
|
|
123
|
+
|
|
124
|
+
for (const [class_name, class_definition] of Object.entries(graph_classes)) {
|
|
125
|
+
resolved_classes[class_name] = {
|
|
126
|
+
...class_definition,
|
|
127
|
+
schema: repo_classes?.[class_name]?.schema,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
return resolved_classes;
|
|
132
|
+
}
|