patram 0.0.2 → 0.2.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/bin/patram.js +25 -147
- package/lib/build-graph-identity.js +270 -0
- package/lib/build-graph.js +156 -77
- package/lib/check-graph.js +23 -7
- package/lib/claim-helpers.js +55 -0
- package/lib/cli-help-metadata.js +552 -0
- package/lib/command-output.js +83 -0
- package/lib/derived-summary.js +278 -0
- package/lib/format-derived-summary-row.js +9 -0
- package/lib/format-node-header.js +19 -0
- package/lib/format-output-item-block.js +22 -0
- package/lib/format-output-metadata.js +62 -0
- package/lib/layout-stored-queries.js +361 -0
- package/lib/list-queries.js +18 -0
- package/lib/list-source-files.js +50 -15
- package/lib/load-patram-config.js +505 -18
- package/lib/load-patram-config.types.ts +40 -0
- package/lib/load-project-graph.js +124 -0
- package/lib/output-view.types.ts +88 -0
- package/lib/parse-claims.js +38 -158
- package/lib/parse-claims.types.ts +7 -0
- package/lib/parse-cli-arguments-helpers.js +446 -0
- package/lib/parse-cli-arguments.js +266 -0
- package/lib/parse-cli-arguments.types.ts +69 -0
- package/lib/parse-cli-color-options.js +44 -0
- package/lib/parse-cli-query-pagination.js +49 -0
- package/lib/parse-jsdoc-blocks.js +184 -0
- package/lib/parse-jsdoc-claims.js +280 -0
- package/lib/parse-jsdoc-prose.js +111 -0
- package/lib/parse-markdown-claims.js +242 -0
- package/lib/parse-markdown-directives.js +136 -0
- package/lib/parse-where-clause.js +707 -0
- package/lib/parse-where-clause.types.ts +70 -0
- package/lib/patram-cli.js +464 -0
- package/lib/patram-config.js +3 -1
- package/lib/patram-config.types.ts +2 -1
- package/lib/patram.js +6 -0
- package/lib/query-graph.js +368 -0
- package/lib/query-inspection.js +523 -0
- package/lib/render-check-output.js +315 -0
- package/lib/render-cli-help.js +419 -0
- package/lib/render-json-output.js +161 -0
- package/lib/render-output-view.js +222 -0
- package/lib/render-plain-output.js +182 -0
- package/lib/render-rich-output.js +240 -0
- package/lib/render-rich-source.js +1333 -0
- package/lib/resolve-check-target.js +190 -0
- package/lib/resolve-output-mode.js +60 -0
- package/lib/resolve-patram-graph-config.js +88 -0
- package/lib/resolve-where-clause.js +66 -0
- package/lib/show-document.js +311 -0
- package/lib/source-file-defaults.js +28 -0
- package/lib/tagged-fenced-block-error.js +17 -0
- package/lib/tagged-fenced-block-markdown.js +111 -0
- package/lib/tagged-fenced-block-metadata.js +97 -0
- package/lib/tagged-fenced-block-parser.js +292 -0
- package/lib/tagged-fenced-blocks.js +100 -0
- package/lib/tagged-fenced-blocks.types.ts +38 -0
- package/lib/write-paged-output.js +87 -0
- package/package.json +28 -12
- package/bin/patram.test.js +0 -184
- package/lib/build-graph.test.js +0 -141
- package/lib/check-graph.test.js +0 -103
- package/lib/list-source-files.test.js +0 -101
- package/lib/load-patram-config.test.js +0 -211
- package/lib/parse-claims.test.js +0 -113
- package/lib/patram-config.test.js +0 -147
package/lib/build-graph.js
CHANGED
|
@@ -4,7 +4,34 @@
|
|
|
4
4
|
* @import { MappingDefinition, PatramConfig } from './patram-config.types.ts';
|
|
5
5
|
*/
|
|
6
6
|
|
|
7
|
-
import {
|
|
7
|
+
import {
|
|
8
|
+
collectDocumentEntityKeys,
|
|
9
|
+
normalizeRepoRelativePath,
|
|
10
|
+
resolveNodeKey,
|
|
11
|
+
resolveTargetReference,
|
|
12
|
+
setNonDocumentPath,
|
|
13
|
+
} from './build-graph-identity.js';
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* Claim-to-graph materialization.
|
|
17
|
+
*
|
|
18
|
+
* Maps parsed claims into document nodes and relations using the resolved
|
|
19
|
+
* Patram graph config.
|
|
20
|
+
*
|
|
21
|
+
* Kind: graph
|
|
22
|
+
* Status: active
|
|
23
|
+
* Uses Term: ../docs/reference/terms/claim.md
|
|
24
|
+
* Uses Term: ../docs/reference/terms/document.md
|
|
25
|
+
* Uses Term: ../docs/reference/terms/graph.md
|
|
26
|
+
* Uses Term: ../docs/reference/terms/mapping.md
|
|
27
|
+
* Uses Term: ../docs/reference/terms/relation.md
|
|
28
|
+
* Tracked in: ../docs/plans/v0/source-anchor-dogfooding.md
|
|
29
|
+
* Decided by: ../docs/decisions/graph-materialization.md
|
|
30
|
+
* Implements: ../docs/tasks/v0/materialize-graph.md
|
|
31
|
+
* @patram
|
|
32
|
+
* @see {@link ./load-project-graph.js}
|
|
33
|
+
* @see {@link ../docs/decisions/graph-materialization.md}
|
|
34
|
+
*/
|
|
8
35
|
|
|
9
36
|
/**
|
|
10
37
|
* Build a Patram graph from semantic config and parsed claims.
|
|
@@ -16,49 +43,29 @@ import { posix } from 'node:path';
|
|
|
16
43
|
export function buildGraph(patram_config, claims) {
|
|
17
44
|
/** @type {Map<string, GraphNode>} */
|
|
18
45
|
const graph_nodes = new Map();
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
normalizeRepoRelativePath(claim.origin.path),
|
|
28
|
-
);
|
|
29
|
-
const mapping_definition = resolveMappingDefinition(
|
|
30
|
-
patram_config.mappings,
|
|
31
|
-
claim,
|
|
32
|
-
);
|
|
33
|
-
|
|
34
|
-
if (!mapping_definition) {
|
|
35
|
-
continue;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (mapping_definition.node) {
|
|
39
|
-
applyNodeMapping(graph_nodes, mapping_definition.node, claim);
|
|
40
|
-
}
|
|
41
|
-
|
|
42
|
-
if (!mapping_definition.emit) {
|
|
43
|
-
continue;
|
|
44
|
-
}
|
|
45
|
-
|
|
46
|
-
const target_key = resolveTargetKey(mapping_definition.emit.target, claim);
|
|
47
|
-
const target_node = upsertNode(
|
|
48
|
-
graph_nodes,
|
|
49
|
-
mapping_definition.emit.target_kind,
|
|
50
|
-
target_key,
|
|
51
|
-
);
|
|
46
|
+
const document_entity_keys = collectDocumentEntityKeys(
|
|
47
|
+
patram_config.mappings,
|
|
48
|
+
claims,
|
|
49
|
+
);
|
|
50
|
+
/** @type {Set<string>} */
|
|
51
|
+
const document_paths = new Set(
|
|
52
|
+
claims.map((claim) => normalizeRepoRelativePath(claim.origin.path)),
|
|
53
|
+
);
|
|
52
54
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
55
|
+
createDocumentNodes(graph_nodes, claims);
|
|
56
|
+
applyNodeMappings(
|
|
57
|
+
graph_nodes,
|
|
58
|
+
patram_config.mappings,
|
|
59
|
+
claims,
|
|
60
|
+
document_entity_keys,
|
|
61
|
+
);
|
|
62
|
+
const graph_edges = createGraphEdges(
|
|
63
|
+
graph_nodes,
|
|
64
|
+
patram_config.mappings,
|
|
65
|
+
claims,
|
|
66
|
+
document_entity_keys,
|
|
67
|
+
document_paths,
|
|
68
|
+
);
|
|
62
69
|
|
|
63
70
|
return {
|
|
64
71
|
edges: graph_edges,
|
|
@@ -96,56 +103,124 @@ function resolveDirectiveMapping(mappings, claim) {
|
|
|
96
103
|
|
|
97
104
|
/**
|
|
98
105
|
* @param {Map<string, GraphNode>} graph_nodes
|
|
99
|
-
* @param {
|
|
100
|
-
* @param {PatramClaim} claim
|
|
106
|
+
* @param {PatramClaim[]} claims
|
|
101
107
|
*/
|
|
102
|
-
function
|
|
103
|
-
const
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
108
|
+
function createDocumentNodes(graph_nodes, claims) {
|
|
109
|
+
for (const claim of claims) {
|
|
110
|
+
upsertNode(
|
|
111
|
+
graph_nodes,
|
|
112
|
+
'document',
|
|
113
|
+
normalizeRepoRelativePath(claim.origin.path),
|
|
114
|
+
);
|
|
115
|
+
}
|
|
107
116
|
}
|
|
108
117
|
|
|
109
118
|
/**
|
|
110
|
-
* @param {
|
|
111
|
-
* @param {
|
|
112
|
-
* @
|
|
119
|
+
* @param {Map<string, GraphNode>} graph_nodes
|
|
120
|
+
* @param {Record<string, MappingDefinition>} mappings
|
|
121
|
+
* @param {PatramClaim[]} claims
|
|
122
|
+
* @param {Map<string, string>} document_entity_keys
|
|
113
123
|
*/
|
|
114
|
-
function
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
124
|
+
function applyNodeMappings(
|
|
125
|
+
graph_nodes,
|
|
126
|
+
mappings,
|
|
127
|
+
claims,
|
|
128
|
+
document_entity_keys,
|
|
129
|
+
) {
|
|
130
|
+
for (const claim of claims) {
|
|
131
|
+
const mapping_definition = resolveMappingDefinition(mappings, claim);
|
|
118
132
|
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
const raw_target = getPathTargetValue(claim);
|
|
133
|
+
if (!mapping_definition?.node) {
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
123
136
|
|
|
124
|
-
|
|
137
|
+
applyNodeMapping(
|
|
138
|
+
graph_nodes,
|
|
139
|
+
mapping_definition.node,
|
|
140
|
+
claim,
|
|
141
|
+
document_entity_keys,
|
|
142
|
+
);
|
|
143
|
+
}
|
|
125
144
|
}
|
|
126
145
|
|
|
127
146
|
/**
|
|
128
|
-
* @param {
|
|
129
|
-
* @
|
|
147
|
+
* @param {Map<string, GraphNode>} graph_nodes
|
|
148
|
+
* @param {Record<string, MappingDefinition>} mappings
|
|
149
|
+
* @param {PatramClaim[]} claims
|
|
150
|
+
* @param {Map<string, string>} document_entity_keys
|
|
151
|
+
* @param {Set<string>} document_paths
|
|
152
|
+
* @returns {GraphEdge[]}
|
|
130
153
|
*/
|
|
131
|
-
function
|
|
132
|
-
|
|
133
|
-
|
|
154
|
+
function createGraphEdges(
|
|
155
|
+
graph_nodes,
|
|
156
|
+
mappings,
|
|
157
|
+
claims,
|
|
158
|
+
document_entity_keys,
|
|
159
|
+
document_paths,
|
|
160
|
+
) {
|
|
161
|
+
/** @type {GraphEdge[]} */
|
|
162
|
+
const graph_edges = [];
|
|
163
|
+
let edge_number = 0;
|
|
164
|
+
|
|
165
|
+
for (const claim of claims) {
|
|
166
|
+
const mapping_definition = resolveMappingDefinition(mappings, claim);
|
|
167
|
+
|
|
168
|
+
if (!mapping_definition?.emit) {
|
|
169
|
+
continue;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const source_document_node = upsertNode(
|
|
173
|
+
graph_nodes,
|
|
174
|
+
'document',
|
|
175
|
+
normalizeRepoRelativePath(claim.origin.path),
|
|
176
|
+
);
|
|
177
|
+
const target_reference = resolveTargetReference(
|
|
178
|
+
mapping_definition.emit.target_kind,
|
|
179
|
+
mapping_definition.emit.target,
|
|
180
|
+
claim,
|
|
181
|
+
document_entity_keys,
|
|
182
|
+
document_paths,
|
|
183
|
+
);
|
|
184
|
+
const target_node = upsertNode(
|
|
185
|
+
graph_nodes,
|
|
186
|
+
mapping_definition.emit.target_kind,
|
|
187
|
+
target_reference.key,
|
|
188
|
+
);
|
|
189
|
+
|
|
190
|
+
setNonDocumentPath(target_node, target_reference.path);
|
|
191
|
+
|
|
192
|
+
edge_number += 1;
|
|
193
|
+
graph_edges.push({
|
|
194
|
+
from: source_document_node.id,
|
|
195
|
+
id: `edge:${edge_number}`,
|
|
196
|
+
origin: claim.origin,
|
|
197
|
+
relation: mapping_definition.emit.relation,
|
|
198
|
+
to: target_node.id,
|
|
199
|
+
});
|
|
134
200
|
}
|
|
135
201
|
|
|
136
|
-
return
|
|
202
|
+
return graph_edges;
|
|
137
203
|
}
|
|
138
204
|
|
|
139
205
|
/**
|
|
206
|
+
* @param {Map<string, GraphNode>} graph_nodes
|
|
207
|
+
* @param {{ field: string, key?: 'path' | 'value', kind: string }} node_mapping
|
|
140
208
|
* @param {PatramClaim} claim
|
|
141
|
-
* @
|
|
209
|
+
* @param {Map<string, string>} document_entity_keys
|
|
142
210
|
*/
|
|
143
|
-
function
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
211
|
+
function applyNodeMapping(
|
|
212
|
+
graph_nodes,
|
|
213
|
+
node_mapping,
|
|
214
|
+
claim,
|
|
215
|
+
document_entity_keys,
|
|
216
|
+
) {
|
|
217
|
+
const source_key = normalizeRepoRelativePath(claim.origin.path);
|
|
218
|
+
const node_key = resolveNodeKey(node_mapping, claim, document_entity_keys);
|
|
219
|
+
const graph_node = upsertNode(graph_nodes, node_mapping.kind, node_key);
|
|
147
220
|
|
|
148
|
-
|
|
221
|
+
setNonDocumentPath(graph_node, source_key);
|
|
222
|
+
|
|
223
|
+
graph_node[node_mapping.field] = getNodeFieldValue(claim);
|
|
149
224
|
}
|
|
150
225
|
|
|
151
226
|
/**
|
|
@@ -205,11 +280,15 @@ function getNodeId(kind_name, node_key) {
|
|
|
205
280
|
}
|
|
206
281
|
|
|
207
282
|
/**
|
|
208
|
-
* @param {
|
|
283
|
+
* @param {PatramClaim} claim
|
|
209
284
|
* @returns {string}
|
|
210
285
|
*/
|
|
211
|
-
function
|
|
212
|
-
|
|
286
|
+
function getNodeFieldValue(claim) {
|
|
287
|
+
if (typeof claim.value === 'string') {
|
|
288
|
+
return claim.value;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
throw new Error(`Claim "${claim.id}" does not carry a string value.`);
|
|
213
292
|
}
|
|
214
293
|
|
|
215
294
|
/**
|
package/lib/check-graph.js
CHANGED
|
@@ -3,17 +3,33 @@
|
|
|
3
3
|
* @import { PatramDiagnostic } from './load-patram-config.types.ts';
|
|
4
4
|
*/
|
|
5
5
|
|
|
6
|
+
/**
|
|
7
|
+
* Graph validation.
|
|
8
|
+
*
|
|
9
|
+
* Reports broken document links and missing edge nodes after graph
|
|
10
|
+
* materialization.
|
|
11
|
+
*
|
|
12
|
+
* Kind: graph
|
|
13
|
+
* Status: active
|
|
14
|
+
* Tracked in: ../docs/plans/v0/source-anchor-dogfooding.md
|
|
15
|
+
* Decided by: ../docs/decisions/check-link-target-existence.md
|
|
16
|
+
* Implements: ../docs/tasks/v0/check-command.md
|
|
17
|
+
* @patram
|
|
18
|
+
* @see {@link ./build-graph.js}
|
|
19
|
+
* @see {@link ../docs/decisions/check-link-target-existence.md}
|
|
20
|
+
*/
|
|
21
|
+
|
|
6
22
|
/**
|
|
7
23
|
* Check a materialized graph for broken document links and missing edge nodes.
|
|
8
24
|
*
|
|
9
25
|
* @param {BuildGraphResult} graph
|
|
10
|
-
* @param {string[]}
|
|
26
|
+
* @param {string[]} existing_file_paths
|
|
11
27
|
* @returns {PatramDiagnostic[]}
|
|
12
28
|
*/
|
|
13
|
-
export function checkGraph(graph,
|
|
29
|
+
export function checkGraph(graph, existing_file_paths) {
|
|
14
30
|
/** @type {PatramDiagnostic[]} */
|
|
15
31
|
const diagnostics = [];
|
|
16
|
-
const
|
|
32
|
+
const existing_file_path_set = new Set(existing_file_paths);
|
|
17
33
|
|
|
18
34
|
for (const graph_edge of graph.edges) {
|
|
19
35
|
const source_node = graph.nodes[graph_edge.from];
|
|
@@ -34,7 +50,7 @@ export function checkGraph(graph, source_file_paths) {
|
|
|
34
50
|
diagnostics,
|
|
35
51
|
graph_edge,
|
|
36
52
|
target_node,
|
|
37
|
-
|
|
53
|
+
existing_file_path_set,
|
|
38
54
|
);
|
|
39
55
|
}
|
|
40
56
|
|
|
@@ -78,13 +94,13 @@ function collectMissingNodeDiagnostics(
|
|
|
78
94
|
* @param {PatramDiagnostic[]} diagnostics
|
|
79
95
|
* @param {GraphEdge} graph_edge
|
|
80
96
|
* @param {GraphNode} target_node
|
|
81
|
-
* @param {Set<string>}
|
|
97
|
+
* @param {Set<string>} existing_file_path_set
|
|
82
98
|
*/
|
|
83
99
|
function collectBrokenLinkDiagnostics(
|
|
84
100
|
diagnostics,
|
|
85
101
|
graph_edge,
|
|
86
102
|
target_node,
|
|
87
|
-
|
|
103
|
+
existing_file_path_set,
|
|
88
104
|
) {
|
|
89
105
|
if (graph_edge.relation !== 'links_to') {
|
|
90
106
|
return;
|
|
@@ -94,7 +110,7 @@ function collectBrokenLinkDiagnostics(
|
|
|
94
110
|
return;
|
|
95
111
|
}
|
|
96
112
|
|
|
97
|
-
if (
|
|
113
|
+
if (existing_file_path_set.has(target_node.path)) {
|
|
98
114
|
return;
|
|
99
115
|
}
|
|
100
116
|
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { PatramClaim, PatramClaimFields } from './parse-claims.types.ts';
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const URI_SCHEME_PATTERN = /^[A-Za-z][A-Za-z0-9+.-]*:/du;
|
|
6
|
+
|
|
7
|
+
/**
|
|
8
|
+
* @param {string} target_value
|
|
9
|
+
* @returns {boolean}
|
|
10
|
+
*/
|
|
11
|
+
export function isPathLikeTarget(target_value) {
|
|
12
|
+
if (target_value.startsWith('#')) {
|
|
13
|
+
return false;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
return !URI_SCHEME_PATTERN.test(target_value);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
/**
|
|
20
|
+
* @param {string} file_path
|
|
21
|
+
* @param {number} claim_number
|
|
22
|
+
* @param {string} claim_type
|
|
23
|
+
* @param {PatramClaimFields} claim_fields
|
|
24
|
+
* @returns {PatramClaim}
|
|
25
|
+
*/
|
|
26
|
+
export function createClaim(file_path, claim_number, claim_type, claim_fields) {
|
|
27
|
+
const document_id = `doc:${file_path}`;
|
|
28
|
+
const origin = claim_fields.origin ?? {
|
|
29
|
+
column: 1,
|
|
30
|
+
line: 1,
|
|
31
|
+
path: file_path,
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
return {
|
|
35
|
+
...claim_fields,
|
|
36
|
+
document_id,
|
|
37
|
+
id: `claim:${document_id}:${claim_number}`,
|
|
38
|
+
origin,
|
|
39
|
+
type: claim_type,
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* @param {string} file_path
|
|
45
|
+
* @returns {string}
|
|
46
|
+
*/
|
|
47
|
+
export function getFileExtension(file_path) {
|
|
48
|
+
const last_dot_index = file_path.lastIndexOf('.');
|
|
49
|
+
|
|
50
|
+
if (last_dot_index < 0) {
|
|
51
|
+
return '';
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
return file_path.slice(last_dot_index);
|
|
55
|
+
}
|