patram 0.0.2 → 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/bin/patram.js +25 -147
- package/lib/build-graph-identity.js +238 -0
- package/lib/build-graph.js +143 -77
- package/lib/check-graph.js +23 -7
- package/lib/claim-helpers.js +55 -0
- package/lib/command-output.js +83 -0
- package/lib/layout-stored-queries.js +213 -0
- package/lib/list-queries.js +18 -0
- package/lib/list-source-files.js +50 -15
- package/lib/load-patram-config.js +106 -18
- package/lib/load-patram-config.types.ts +9 -0
- package/lib/load-project-graph.js +124 -0
- package/lib/output-view.types.ts +73 -0
- package/lib/parse-claims.js +38 -158
- package/lib/parse-claims.types.ts +7 -0
- package/lib/parse-cli-arguments-helpers.js +273 -0
- package/lib/parse-cli-arguments.js +114 -0
- package/lib/parse-cli-arguments.types.ts +24 -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 +312 -0
- package/lib/patram-cli.js +337 -0
- package/lib/patram-config.js +3 -1
- package/lib/patram-config.types.ts +2 -1
- package/lib/query-graph.js +256 -0
- package/lib/render-check-output.js +315 -0
- package/lib/render-json-output.js +108 -0
- package/lib/render-output-view.js +193 -0
- package/lib/render-plain-output.js +237 -0
- package/lib/render-rich-output.js +293 -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 +51 -0
- package/lib/show-document.js +311 -0
- package/lib/source-file-defaults.js +28 -0
- package/lib/write-paged-output.js +87 -0
- package/package.json +21 -10
- 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
|
@@ -1,10 +1,19 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
KindDefinition,
|
|
3
|
+
MappingDefinition,
|
|
4
|
+
RelationDefinition,
|
|
5
|
+
} from './patram-config.types.ts';
|
|
6
|
+
|
|
1
7
|
export interface StoredQueryConfig {
|
|
2
8
|
where: string;
|
|
3
9
|
}
|
|
4
10
|
|
|
5
11
|
export interface PatramRepoConfig {
|
|
6
12
|
include: string[];
|
|
13
|
+
kinds?: Record<string, KindDefinition>;
|
|
14
|
+
mappings?: Record<string, MappingDefinition>;
|
|
7
15
|
queries: Record<string, StoredQueryConfig>;
|
|
16
|
+
relations?: Record<string, RelationDefinition>;
|
|
8
17
|
}
|
|
9
18
|
|
|
10
19
|
export interface PatramDiagnostic {
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { BuildGraphResult } from './build-graph.types.ts';
|
|
3
|
+
* @import { PatramClaim } from './parse-claims.types.ts';
|
|
4
|
+
* @import { PatramDiagnostic, PatramRepoConfig } from './load-patram-config.types.ts';
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { readFile } from 'node:fs/promises';
|
|
8
|
+
import { resolve } from 'node:path';
|
|
9
|
+
|
|
10
|
+
import { buildGraph } from './build-graph.js';
|
|
11
|
+
import { listSourceFiles } from './list-source-files.js';
|
|
12
|
+
import { loadPatramConfig } from './load-patram-config.js';
|
|
13
|
+
import { parseSourceFile } from './parse-claims.js';
|
|
14
|
+
import { resolvePatramGraphConfig } from './resolve-patram-graph-config.js';
|
|
15
|
+
|
|
16
|
+
/**
|
|
17
|
+
* Project graph loading pipeline.
|
|
18
|
+
*
|
|
19
|
+
* Loads config, scans source files, parses claims, and materializes the graph
|
|
20
|
+
* used by CLI commands.
|
|
21
|
+
*
|
|
22
|
+
* Kind: graph
|
|
23
|
+
* Status: active
|
|
24
|
+
* Uses Term: ../docs/reference/terms/claim.md
|
|
25
|
+
* Uses Term: ../docs/reference/terms/graph.md
|
|
26
|
+
* Uses Term: ../docs/reference/terms/mapping.md
|
|
27
|
+
* Tracked in: ../docs/plans/v0/source-anchor-dogfooding.md
|
|
28
|
+
* Decided by: ../docs/decisions/dogfood-query-graph-v0.md
|
|
29
|
+
* @patram
|
|
30
|
+
* @see {@link ./parse-claims.js}
|
|
31
|
+
* @see {@link ./build-graph.js}
|
|
32
|
+
*/
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* Load config, source files, claims, and the materialized graph for one
|
|
36
|
+
* project directory.
|
|
37
|
+
*
|
|
38
|
+
* @param {string} project_directory
|
|
39
|
+
* @returns {Promise<{ config: PatramRepoConfig, diagnostics: PatramDiagnostic[], graph: BuildGraphResult, source_file_paths: string[] }>}
|
|
40
|
+
*/
|
|
41
|
+
export async function loadProjectGraph(project_directory) {
|
|
42
|
+
const load_result = await loadPatramConfig(project_directory);
|
|
43
|
+
|
|
44
|
+
if (load_result.diagnostics.length > 0) {
|
|
45
|
+
return {
|
|
46
|
+
config: {
|
|
47
|
+
include: [],
|
|
48
|
+
queries: {},
|
|
49
|
+
},
|
|
50
|
+
diagnostics: load_result.diagnostics,
|
|
51
|
+
graph: {
|
|
52
|
+
edges: [],
|
|
53
|
+
nodes: {},
|
|
54
|
+
},
|
|
55
|
+
source_file_paths: [],
|
|
56
|
+
};
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
const repo_config = load_result.config;
|
|
60
|
+
|
|
61
|
+
if (!repo_config) {
|
|
62
|
+
throw new Error('Expected a valid Patram repo config.');
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
const source_file_paths = await listSourceFiles(
|
|
66
|
+
repo_config.include,
|
|
67
|
+
project_directory,
|
|
68
|
+
);
|
|
69
|
+
const collect_result = await collectClaims(
|
|
70
|
+
source_file_paths,
|
|
71
|
+
project_directory,
|
|
72
|
+
);
|
|
73
|
+
const graph_config = resolvePatramGraphConfig(repo_config);
|
|
74
|
+
|
|
75
|
+
if (collect_result.diagnostics.length > 0) {
|
|
76
|
+
return {
|
|
77
|
+
config: repo_config,
|
|
78
|
+
diagnostics: collect_result.diagnostics,
|
|
79
|
+
graph: {
|
|
80
|
+
edges: [],
|
|
81
|
+
nodes: {},
|
|
82
|
+
},
|
|
83
|
+
source_file_paths,
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
return {
|
|
88
|
+
config: repo_config,
|
|
89
|
+
diagnostics: [],
|
|
90
|
+
graph: buildGraph(graph_config, collect_result.claims),
|
|
91
|
+
source_file_paths,
|
|
92
|
+
};
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* @param {string[]} source_file_paths
|
|
97
|
+
* @param {string} project_directory
|
|
98
|
+
* @returns {Promise<{ claims: PatramClaim[], diagnostics: PatramDiagnostic[] }>}
|
|
99
|
+
*/
|
|
100
|
+
async function collectClaims(source_file_paths, project_directory) {
|
|
101
|
+
/** @type {PatramClaim[]} */
|
|
102
|
+
const claims = [];
|
|
103
|
+
/** @type {PatramDiagnostic[]} */
|
|
104
|
+
const diagnostics = [];
|
|
105
|
+
|
|
106
|
+
for (const source_file_path of source_file_paths) {
|
|
107
|
+
const source_text = await readFile(
|
|
108
|
+
resolve(project_directory, source_file_path),
|
|
109
|
+
'utf8',
|
|
110
|
+
);
|
|
111
|
+
const parse_result = parseSourceFile({
|
|
112
|
+
path: source_file_path,
|
|
113
|
+
source: source_text,
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
claims.push(...parse_result.claims);
|
|
117
|
+
diagnostics.push(...parse_result.diagnostics);
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
return {
|
|
121
|
+
claims,
|
|
122
|
+
diagnostics,
|
|
123
|
+
};
|
|
124
|
+
}
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import type { GraphNode } from './build-graph.types.ts';
|
|
2
|
+
|
|
3
|
+
export interface OutputViewSummary {
|
|
4
|
+
count: number;
|
|
5
|
+
kind: 'resolved_link_list' | 'result_list' | 'stored_query_list';
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
export interface QueryOutputViewSummary extends OutputViewSummary {
|
|
9
|
+
kind: 'result_list';
|
|
10
|
+
limit: number;
|
|
11
|
+
offset: number;
|
|
12
|
+
total_count: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export interface OutputNodeItem {
|
|
16
|
+
id: string;
|
|
17
|
+
kind: 'node';
|
|
18
|
+
node_kind: GraphNode['kind'];
|
|
19
|
+
path: string;
|
|
20
|
+
status?: string;
|
|
21
|
+
title: string;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
export interface OutputStoredQueryItem {
|
|
25
|
+
kind: 'stored_query';
|
|
26
|
+
name: string;
|
|
27
|
+
where: string;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
export interface OutputResolvedLinkTarget {
|
|
31
|
+
kind?: string;
|
|
32
|
+
path: string;
|
|
33
|
+
status?: string;
|
|
34
|
+
title: string;
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export interface OutputResolvedLinkItem {
|
|
38
|
+
kind: 'resolved_link';
|
|
39
|
+
label: string;
|
|
40
|
+
reference: number;
|
|
41
|
+
target: OutputResolvedLinkTarget;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export interface QueryOutputView {
|
|
45
|
+
command: 'query';
|
|
46
|
+
hints: string[];
|
|
47
|
+
items: OutputNodeItem[];
|
|
48
|
+
summary: QueryOutputViewSummary;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
export interface QueriesOutputView {
|
|
52
|
+
command: 'queries';
|
|
53
|
+
hints: string[];
|
|
54
|
+
items: OutputStoredQueryItem[];
|
|
55
|
+
summary: OutputViewSummary;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
export interface ShowOutputView {
|
|
59
|
+
command: 'show';
|
|
60
|
+
hints: string[];
|
|
61
|
+
items: OutputResolvedLinkItem[];
|
|
62
|
+
path: string;
|
|
63
|
+
rendered_source: string;
|
|
64
|
+
source: string;
|
|
65
|
+
summary: OutputViewSummary;
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
export type OutputView = QueryOutputView | QueriesOutputView | ShowOutputView;
|
|
69
|
+
|
|
70
|
+
export interface ResolvedOutputMode {
|
|
71
|
+
color_enabled: boolean;
|
|
72
|
+
renderer_name: 'json' | 'plain' | 'rich';
|
|
73
|
+
}
|
package/lib/parse-claims.js
CHANGED
|
@@ -1,178 +1,58 @@
|
|
|
1
|
+
/** @import * as $k$$l$parse$j$claims$k$types$k$ts from './parse-claims.types.ts'; */
|
|
1
2
|
/**
|
|
2
|
-
* @import {
|
|
3
|
+
* @import { ParseClaimsInput, ParseSourceFileResult } from './parse-claims.types.ts';
|
|
3
4
|
*/
|
|
4
5
|
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
6
|
+
import { getFileExtension } from './claim-helpers.js';
|
|
7
|
+
import { parseJsdocClaims } from './parse-jsdoc-claims.js';
|
|
8
|
+
import { parseMarkdownClaims } from './parse-markdown-claims.js';
|
|
9
|
+
import { MARKDOWN_SOURCE_FILE_EXTENSIONS } from './source-file-defaults.js';
|
|
9
10
|
|
|
10
11
|
/**
|
|
11
|
-
*
|
|
12
|
+
* Source claim dispatch.
|
|
12
13
|
*
|
|
13
|
-
*
|
|
14
|
-
*
|
|
14
|
+
* Routes each source file to markdown or JSDoc claim parsing and keeps claim
|
|
15
|
+
* extraction on one entrypoint.
|
|
16
|
+
*
|
|
17
|
+
* Kind: parse
|
|
18
|
+
* Status: active
|
|
19
|
+
* Uses Term: ../docs/reference/terms/claim.md
|
|
20
|
+
* Uses Term: ../docs/reference/terms/document.md
|
|
21
|
+
* Tracked in: ../docs/plans/v0/source-anchor-dogfooding.md
|
|
22
|
+
* Decided by: ../docs/decisions/jsdoc-metadata-directive-syntax.md
|
|
23
|
+
* Implements: ../docs/tasks/v0/parse-claims.md
|
|
24
|
+
* @patram
|
|
25
|
+
* @see {@link ./parse-markdown-claims.js}
|
|
26
|
+
* @see {@link ./parse-jsdoc-claims.js}
|
|
15
27
|
*/
|
|
16
|
-
export function parseClaims(parse_input) {
|
|
17
|
-
const file_extension = getFileExtension(parse_input.path);
|
|
18
|
-
|
|
19
|
-
if (MARKDOWN_EXTENSIONS.has(file_extension)) {
|
|
20
|
-
return parseMarkdownClaims(parse_input);
|
|
21
|
-
}
|
|
22
28
|
|
|
23
|
-
|
|
24
|
-
}
|
|
29
|
+
const MARKDOWN_EXTENSIONS = new Set(MARKDOWN_SOURCE_FILE_EXTENSIONS);
|
|
25
30
|
|
|
26
31
|
/**
|
|
32
|
+
* Parse one source file into claims and diagnostics.
|
|
33
|
+
*
|
|
27
34
|
* @param {ParseClaimsInput} parse_input
|
|
28
|
-
* @returns {
|
|
29
|
-
*/
|
|
30
|
-
function parseMarkdownClaims(parse_input) {
|
|
31
|
-
const lines = parse_input.source.split('\n');
|
|
32
|
-
|
|
33
|
-
/** @type {PatramClaim[]} */
|
|
34
|
-
const claims = [];
|
|
35
|
-
const title_value = getMarkdownTitle(lines);
|
|
36
|
-
|
|
37
|
-
if (title_value) {
|
|
38
|
-
claims.push(
|
|
39
|
-
createClaim(parse_input.path, claims.length + 1, 'document.title', {
|
|
40
|
-
value: title_value,
|
|
41
|
-
}),
|
|
42
|
-
);
|
|
43
|
-
}
|
|
44
|
-
|
|
45
|
-
for (const [line_index, line] of lines.entries()) {
|
|
46
|
-
const line_number = line_index + 1;
|
|
47
|
-
|
|
48
|
-
collectMarkdownLinkClaims(parse_input.path, line, line_number, claims);
|
|
49
|
-
collectDirectiveClaims(parse_input.path, line, line_number, claims);
|
|
50
|
-
}
|
|
51
|
-
|
|
52
|
-
return claims;
|
|
53
|
-
}
|
|
54
|
-
|
|
55
|
-
/**
|
|
56
|
-
* @param {string} file_path
|
|
57
|
-
* @param {string} line
|
|
58
|
-
* @param {number} line_number
|
|
59
|
-
* @param {PatramClaim[]} claims
|
|
60
|
-
*/
|
|
61
|
-
function collectMarkdownLinkClaims(file_path, line, line_number, claims) {
|
|
62
|
-
for (const link_match of line.matchAll(MARKDOWN_LINK_PATTERN)) {
|
|
63
|
-
const link_text = link_match[1];
|
|
64
|
-
const target_value = link_match[2];
|
|
65
|
-
const column_number =
|
|
66
|
-
link_match.index === undefined ? 1 : link_match.index + 1;
|
|
67
|
-
|
|
68
|
-
claims.push(
|
|
69
|
-
createClaim(file_path, claims.length + 1, 'markdown.link', {
|
|
70
|
-
origin: {
|
|
71
|
-
column: column_number,
|
|
72
|
-
line: line_number,
|
|
73
|
-
path: file_path,
|
|
74
|
-
},
|
|
75
|
-
value: {
|
|
76
|
-
target: target_value,
|
|
77
|
-
text: link_text,
|
|
78
|
-
},
|
|
79
|
-
}),
|
|
80
|
-
);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
|
|
84
|
-
/**
|
|
85
|
-
* @param {string} file_path
|
|
86
|
-
* @param {string} line
|
|
87
|
-
* @param {number} line_number
|
|
88
|
-
* @param {PatramClaim[]} claims
|
|
89
|
-
*/
|
|
90
|
-
function collectDirectiveClaims(file_path, line, line_number, claims) {
|
|
91
|
-
const directive_match = line.match(DIRECTIVE_PATTERN);
|
|
92
|
-
|
|
93
|
-
if (!directive_match) {
|
|
94
|
-
return;
|
|
95
|
-
}
|
|
96
|
-
|
|
97
|
-
const directive_name = normalizeDirectiveName(directive_match[1]);
|
|
98
|
-
const directive_value = directive_match[2].trim();
|
|
99
|
-
|
|
100
|
-
claims.push(
|
|
101
|
-
createClaim(file_path, claims.length + 1, 'directive', {
|
|
102
|
-
name: directive_name,
|
|
103
|
-
origin: {
|
|
104
|
-
column: 1,
|
|
105
|
-
line: line_number,
|
|
106
|
-
path: file_path,
|
|
107
|
-
},
|
|
108
|
-
parser: 'markdown',
|
|
109
|
-
value: directive_value,
|
|
110
|
-
}),
|
|
111
|
-
);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* @param {string[]} lines
|
|
116
|
-
* @returns {string | null}
|
|
35
|
+
* @returns {ParseSourceFileResult}
|
|
117
36
|
*/
|
|
118
|
-
function
|
|
119
|
-
const
|
|
120
|
-
|
|
121
|
-
if (first_line.length === 0) {
|
|
122
|
-
return null;
|
|
123
|
-
}
|
|
124
|
-
|
|
125
|
-
const heading_match = first_line.match(HEADING_PATTERN);
|
|
37
|
+
export function parseSourceFile(parse_input) {
|
|
38
|
+
const file_extension = getFileExtension(parse_input.path);
|
|
126
39
|
|
|
127
|
-
if (
|
|
128
|
-
return
|
|
40
|
+
if (MARKDOWN_EXTENSIONS.has(file_extension)) {
|
|
41
|
+
return {
|
|
42
|
+
claims: parseMarkdownClaims(parse_input),
|
|
43
|
+
diagnostics: [],
|
|
44
|
+
};
|
|
129
45
|
}
|
|
130
46
|
|
|
131
|
-
return
|
|
132
|
-
}
|
|
133
|
-
|
|
134
|
-
/**
|
|
135
|
-
* @param {string} file_path
|
|
136
|
-
* @param {number} claim_number
|
|
137
|
-
* @param {string} claim_type
|
|
138
|
-
* @param {PatramClaimFields} claim_fields
|
|
139
|
-
* @returns {PatramClaim}
|
|
140
|
-
*/
|
|
141
|
-
function createClaim(file_path, claim_number, claim_type, claim_fields) {
|
|
142
|
-
const document_id = `doc:${file_path}`;
|
|
143
|
-
const origin = claim_fields.origin ?? {
|
|
144
|
-
column: 1,
|
|
145
|
-
line: 1,
|
|
146
|
-
path: file_path,
|
|
147
|
-
};
|
|
148
|
-
|
|
149
|
-
return {
|
|
150
|
-
...claim_fields,
|
|
151
|
-
document_id,
|
|
152
|
-
id: `claim:${document_id}:${claim_number}`,
|
|
153
|
-
origin,
|
|
154
|
-
type: claim_type,
|
|
155
|
-
};
|
|
156
|
-
}
|
|
157
|
-
|
|
158
|
-
/**
|
|
159
|
-
* @param {string} directive_label
|
|
160
|
-
* @returns {string}
|
|
161
|
-
*/
|
|
162
|
-
function normalizeDirectiveName(directive_label) {
|
|
163
|
-
return directive_label.trim().toLowerCase().replaceAll(/\s+/dgu, '_');
|
|
47
|
+
return parseJsdocClaims(parse_input);
|
|
164
48
|
}
|
|
165
49
|
|
|
166
50
|
/**
|
|
167
|
-
*
|
|
168
|
-
*
|
|
51
|
+
* Parse a file into neutral Patram claims.
|
|
52
|
+
*
|
|
53
|
+
* @param {ParseClaimsInput} parse_input
|
|
54
|
+
* @returns {$k$$l$parse$j$claims$k$types$k$ts.PatramClaim[]}
|
|
169
55
|
*/
|
|
170
|
-
function
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
if (last_dot_index < 0) {
|
|
174
|
-
return '';
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
return file_path.slice(last_dot_index);
|
|
56
|
+
export function parseClaims(parse_input) {
|
|
57
|
+
return parseSourceFile(parse_input).claims;
|
|
178
58
|
}
|
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { PatramDiagnostic } from './load-patram-config.types.ts';
|
|
2
|
+
|
|
1
3
|
export interface ParseClaimsInput {
|
|
2
4
|
path: string;
|
|
3
5
|
source: string;
|
|
@@ -25,3 +27,8 @@ export type PatramClaimFields = Omit<
|
|
|
25
27
|
> & {
|
|
26
28
|
origin?: ClaimOrigin;
|
|
27
29
|
};
|
|
30
|
+
|
|
31
|
+
export interface ParseSourceFileResult {
|
|
32
|
+
claims: PatramClaim[];
|
|
33
|
+
diagnostics: PatramDiagnostic[];
|
|
34
|
+
}
|