patram 0.10.0 → 0.12.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 +4 -4
- package/lib/cli/arguments.types.d.ts +1 -0
- package/lib/cli/commands/check.js +27 -15
- package/lib/cli/commands/fields.js +0 -4
- package/lib/cli/commands/queries.js +179 -1
- package/lib/cli/commands/query.js +1 -8
- package/lib/cli/commands/refs.js +3 -10
- package/lib/cli/commands/show.js +1 -8
- package/lib/cli/help-metadata.js +106 -111
- package/lib/cli/main.js +10 -10
- package/lib/cli/parse-arguments-helpers.js +416 -66
- package/lib/cli/parse-arguments.js +4 -4
- package/lib/cli/render-help.js +10 -4
- package/lib/config/defaults.js +33 -25
- package/lib/config/load-patram-config.d.ts +19 -33
- package/lib/config/load-patram-config.js +18 -121
- package/lib/config/load-patram-config.types.d.ts +3 -40
- package/lib/config/manage-stored-queries-helpers.d.ts +69 -0
- package/lib/config/manage-stored-queries-helpers.js +320 -0
- package/lib/config/manage-stored-queries-jsonc.d.ts +31 -0
- package/lib/config/manage-stored-queries-jsonc.js +95 -0
- package/lib/config/manage-stored-queries.d.ts +77 -0
- package/lib/config/manage-stored-queries.js +300 -0
- package/lib/config/patram-config.d.ts +34 -34
- package/lib/config/patram-config.js +3 -3
- package/lib/config/patram-config.types.d.ts +5 -11
- package/lib/config/resolve-patram-graph-config.d.ts +5 -1
- package/lib/config/resolve-patram-graph-config.js +3 -119
- package/lib/config/schema.d.ts +158 -269
- package/lib/config/schema.js +72 -210
- package/lib/config/validate-patram-config-value.d.ts +13 -0
- package/lib/config/validate-patram-config-value.js +94 -0
- package/lib/config/validation.d.ts +2 -12
- package/lib/config/validation.js +125 -483
- package/lib/find-close-match.d.ts +4 -1
- package/lib/graph/build-graph-identity.d.ts +1 -32
- package/lib/graph/build-graph-identity.js +5 -269
- package/lib/graph/build-graph.d.ts +13 -4
- package/lib/graph/build-graph.js +347 -488
- package/lib/graph/build-graph.types.d.ts +8 -9
- package/lib/graph/check-directive-metadata-helpers.d.ts +30 -0
- package/lib/graph/check-directive-metadata-helpers.js +126 -0
- package/lib/graph/check-directive-metadata.d.ts +8 -9
- package/lib/graph/check-directive-metadata.js +70 -561
- package/lib/graph/check-directive-path-target.d.ts +6 -13
- package/lib/graph/check-directive-path-target.js +26 -57
- package/lib/graph/check-directive-value.d.ts +1 -5
- package/lib/graph/check-directive-value.js +40 -180
- package/lib/graph/check-graph.d.ts +5 -5
- package/lib/graph/check-graph.js +8 -6
- package/lib/graph/document-node-identity.d.ts +23 -7
- package/lib/graph/document-node-identity.js +417 -160
- package/lib/graph/graph-node.d.ts +42 -0
- package/lib/graph/graph-node.js +83 -0
- package/lib/graph/inspect-reverse-references.js +16 -11
- package/lib/graph/load-project-graph.d.ts +7 -7
- package/lib/graph/load-project-graph.js +7 -7
- package/lib/graph/parse-where-clause.types.d.ts +3 -2
- package/lib/graph/query/cypher-reader.d.ts +59 -0
- package/lib/graph/query/cypher-reader.js +151 -0
- package/lib/graph/query/cypher-support.d.ts +79 -0
- package/lib/graph/query/cypher-support.js +213 -0
- package/lib/graph/query/cypher-tokenize.d.ts +13 -0
- package/lib/graph/query/cypher-tokenize.js +225 -0
- package/lib/graph/query/cypher.types.d.ts +43 -0
- package/lib/graph/query/execute.d.ts +7 -7
- package/lib/graph/query/execute.js +71 -33
- package/lib/graph/query/inspect.js +58 -24
- package/lib/graph/query/parse-cypher-patterns.d.ts +27 -0
- package/lib/graph/query/parse-cypher-patterns.js +382 -0
- package/lib/graph/query/parse-cypher.d.ts +7 -0
- package/lib/graph/query/parse-cypher.js +580 -0
- package/lib/graph/query/parse-query.d.ts +13 -0
- package/lib/graph/query/parse-query.js +97 -0
- package/lib/graph/query/resolve.d.ts +6 -0
- package/lib/graph/query/resolve.js +81 -24
- package/lib/output/command-output.js +12 -5
- package/lib/output/compact-layout.js +221 -0
- package/lib/output/format-output-item-block.js +31 -1
- package/lib/output/format-output-metadata.js +16 -29
- package/lib/output/format-stored-query-block.js +95 -0
- package/lib/output/layout-incoming-references.js +101 -19
- package/lib/output/layout-stored-queries.js +23 -330
- package/lib/output/list-queries.js +1 -1
- package/lib/output/render-field-discovery.js +11 -2
- package/lib/output/render-output-view.js +9 -5
- package/lib/output/renderers/json.js +5 -26
- package/lib/output/renderers/plain.js +155 -35
- package/lib/output/renderers/rich.js +250 -36
- package/lib/output/resolve-check-target.js +120 -11
- package/lib/output/resolved-link-layout.js +43 -0
- package/lib/output/rich-source/render.js +193 -35
- package/lib/output/show-document.js +25 -18
- package/lib/output/view-model/index.js +124 -103
- package/lib/parse/jsdoc/parse-jsdoc-blocks.js +1 -1
- package/lib/parse/jsdoc/parse-jsdoc-claims.js +12 -6
- package/lib/parse/markdown/parse-markdown-claims.js +99 -62
- package/lib/parse/markdown/parse-markdown-directives.d.ts +10 -6
- package/lib/parse/markdown/parse-markdown-directives.js +104 -18
- package/lib/parse/markdown/parse-markdown-prose.d.ts +27 -0
- package/lib/parse/markdown/parse-markdown-prose.js +243 -0
- package/lib/parse/parse-claims.d.ts +2 -6
- package/lib/parse/parse-claims.js +11 -53
- package/lib/parse/tagged-fenced/tagged-fenced-blocks.d.ts +4 -4
- package/lib/parse/tagged-fenced/tagged-fenced-blocks.js +4 -4
- package/lib/parse/yaml/parse-yaml-claims.js +4 -4
- package/lib/patram.d.ts +9 -3
- package/lib/patram.js +1 -1
- package/lib/scan/discover-fields.js +194 -55
- package/lib/scan/list-source-files.d.ts +4 -4
- package/lib/scan/list-source-files.js +4 -4
- package/package.json +2 -1
- package/lib/directive-validation-test-helpers.js +0 -87
- package/lib/graph/query/parse.d.ts +0 -75
- package/lib/graph/query/parse.js +0 -1064
- package/lib/output/derived-summary.js +0 -280
- package/lib/output/format-derived-summary-row.js +0 -9
|
@@ -1,22 +1,26 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @import { OutputNodeItem } from './output-view.types.ts';
|
|
2
|
+
* @import { RefsOutputView, OutputNodeItem } from './output-view.types.ts';
|
|
3
3
|
*/
|
|
4
4
|
|
|
5
|
-
import {
|
|
5
|
+
import { formatCompactMetadataLabel } from './compact-layout.js';
|
|
6
6
|
import { formatNodeHeader } from './format-node-header.js';
|
|
7
|
-
import {
|
|
7
|
+
import { formatOutputNodeStoredMetadataRow } from './format-output-metadata.js';
|
|
8
|
+
|
|
9
|
+
const INCOMING_TREE_PREFIX_WIDTH = 5;
|
|
8
10
|
|
|
9
11
|
/**
|
|
10
12
|
* Layout grouped incoming references as plain text lines.
|
|
11
13
|
*
|
|
12
14
|
* @param {Record<string, OutputNodeItem[]>} incoming
|
|
13
15
|
* @param {{
|
|
16
|
+
* format_node_block?: (output_item: OutputNodeItem) => string,
|
|
14
17
|
* format_node_header?: (output_item: OutputNodeItem) => string,
|
|
15
18
|
* format_relation_header?: (relation_name: string, relation_count: number) => string,
|
|
16
19
|
* }} layout_options
|
|
17
20
|
* @returns {string[]}
|
|
18
21
|
*/
|
|
19
22
|
export function layoutIncomingReferenceLines(incoming, layout_options = {}) {
|
|
23
|
+
const formatNodeBlock = layout_options.format_node_block;
|
|
20
24
|
const formatNodeHeader =
|
|
21
25
|
layout_options.format_node_header ?? defaultNodeHeaderFormatter;
|
|
22
26
|
const formatRelationHeader =
|
|
@@ -35,13 +39,51 @@ export function layoutIncomingReferenceLines(incoming, layout_options = {}) {
|
|
|
35
39
|
formatRelationHeader(relation_name, relation_sources.length),
|
|
36
40
|
);
|
|
37
41
|
output_lines.push(
|
|
38
|
-
...layoutIncomingRelationSources(
|
|
42
|
+
...layoutIncomingRelationSources(
|
|
43
|
+
relation_sources,
|
|
44
|
+
formatNodeBlock ?? createDefaultNodeBlockFormatter(formatNodeHeader),
|
|
45
|
+
),
|
|
39
46
|
);
|
|
40
47
|
}
|
|
41
48
|
|
|
42
49
|
return output_lines;
|
|
43
50
|
}
|
|
44
51
|
|
|
52
|
+
/**
|
|
53
|
+
* @param {RefsOutputView} output_view
|
|
54
|
+
* @returns {string[]}
|
|
55
|
+
*/
|
|
56
|
+
export function collectIncomingRefHeaders(output_view) {
|
|
57
|
+
/** @type {string[]} */
|
|
58
|
+
const headers = [formatNodeHeader(output_view.node)];
|
|
59
|
+
|
|
60
|
+
for (const incoming_items of Object.values(output_view.incoming)) {
|
|
61
|
+
for (const output_item of incoming_items) {
|
|
62
|
+
headers.push(formatNodeHeader(output_item));
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
return headers;
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
/**
|
|
70
|
+
* @param {{ is_tty?: boolean, terminal_width?: number }} render_options
|
|
71
|
+
* @returns {{ is_tty?: boolean, terminal_width?: number }}
|
|
72
|
+
*/
|
|
73
|
+
export function createIncomingTreeRenderOptions(render_options) {
|
|
74
|
+
if (typeof render_options.terminal_width !== 'number') {
|
|
75
|
+
return render_options;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return {
|
|
79
|
+
...render_options,
|
|
80
|
+
terminal_width: Math.max(
|
|
81
|
+
1,
|
|
82
|
+
render_options.terminal_width - INCOMING_TREE_PREFIX_WIDTH,
|
|
83
|
+
),
|
|
84
|
+
};
|
|
85
|
+
}
|
|
86
|
+
|
|
45
87
|
/**
|
|
46
88
|
* @param {OutputNodeItem} output_item
|
|
47
89
|
* @returns {string}
|
|
@@ -61,21 +103,18 @@ function defaultRelationHeaderFormatter(relation_name, relation_count) {
|
|
|
61
103
|
|
|
62
104
|
/**
|
|
63
105
|
* @param {OutputNodeItem[]} relation_sources
|
|
64
|
-
* @param {(output_item: OutputNodeItem) => string}
|
|
106
|
+
* @param {(output_item: OutputNodeItem) => string} format_node_block
|
|
65
107
|
* @returns {string[]}
|
|
66
108
|
*/
|
|
67
|
-
function layoutIncomingRelationSources(relation_sources,
|
|
109
|
+
function layoutIncomingRelationSources(relation_sources, format_node_block) {
|
|
68
110
|
/** @type {string[]} */
|
|
69
111
|
const output_lines = [];
|
|
70
112
|
|
|
71
113
|
for (const [item_index, output_item] of relation_sources.entries()) {
|
|
72
|
-
if (item_index > 0) {
|
|
73
|
-
output_lines.push('');
|
|
74
|
-
}
|
|
75
|
-
|
|
76
114
|
output_lines.push(
|
|
77
|
-
...
|
|
78
|
-
|
|
115
|
+
...layoutIncomingNodeBlock(
|
|
116
|
+
format_node_block(output_item),
|
|
117
|
+
item_index === relation_sources.length - 1,
|
|
79
118
|
),
|
|
80
119
|
);
|
|
81
120
|
}
|
|
@@ -89,17 +128,60 @@ function layoutIncomingRelationSources(relation_sources, format_node_header) {
|
|
|
89
128
|
* @returns {string}
|
|
90
129
|
*/
|
|
91
130
|
function formatIncomingNodeBlock(output_item, format_node_header) {
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
131
|
+
const stored_metadata_row = formatOutputNodeStoredMetadataRow(output_item);
|
|
132
|
+
const metadata_label = stored_metadata_row
|
|
133
|
+
? formatCompactMetadataLabel([stored_metadata_row])
|
|
134
|
+
: undefined;
|
|
135
|
+
const header = metadata_label
|
|
136
|
+
? `${format_node_header(output_item)} ${metadata_label}`
|
|
137
|
+
: format_node_header(output_item);
|
|
138
|
+
/** @type {string[]} */
|
|
139
|
+
const output_lines = [header];
|
|
140
|
+
|
|
141
|
+
output_lines.push(output_item.title);
|
|
142
|
+
|
|
143
|
+
if (output_item.description) {
|
|
144
|
+
output_lines.push(...output_item.description.split('\n'));
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
return output_lines.join('\n');
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
/**
|
|
151
|
+
* @param {(output_item: OutputNodeItem) => string} format_node_header
|
|
152
|
+
* @returns {(output_item: OutputNodeItem) => string}
|
|
153
|
+
*/
|
|
154
|
+
function createDefaultNodeBlockFormatter(format_node_header) {
|
|
155
|
+
return (output_item) =>
|
|
156
|
+
formatIncomingNodeBlock(output_item, format_node_header);
|
|
97
157
|
}
|
|
98
158
|
|
|
99
159
|
/**
|
|
100
160
|
* @param {string} block
|
|
161
|
+
* @param {boolean} is_last_item
|
|
101
162
|
* @returns {string[]}
|
|
102
163
|
*/
|
|
103
|
-
function
|
|
104
|
-
|
|
164
|
+
function layoutIncomingNodeBlock(block, is_last_item) {
|
|
165
|
+
const header_prefix = is_last_item ? ' └─ ' : ' ├─ ';
|
|
166
|
+
const continuation_prefix = is_last_item ? ' ' : ' │ ';
|
|
167
|
+
|
|
168
|
+
return block.split('\n').map((line, line_index) => {
|
|
169
|
+
if (line_index === 0) {
|
|
170
|
+
return `${header_prefix}${line}`;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
if (line.length === 0) {
|
|
174
|
+
return '';
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return `${continuation_prefix}${trimIncomingContinuationIndent(line)}`;
|
|
178
|
+
});
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
/**
|
|
182
|
+
* @param {string} line
|
|
183
|
+
* @returns {string}
|
|
184
|
+
*/
|
|
185
|
+
function trimIncomingContinuationIndent(line) {
|
|
186
|
+
return line.startsWith(' ') ? line.slice(2) : line;
|
|
105
187
|
}
|
|
@@ -1,14 +1,7 @@
|
|
|
1
|
-
/* eslint-disable max-lines */
|
|
2
1
|
/**
|
|
3
2
|
* @import { OutputStoredQueryItem } from './output-view.types.ts';
|
|
4
3
|
*/
|
|
5
4
|
|
|
6
|
-
import { parseWhereClause } from '../graph/query/parse.js';
|
|
7
|
-
|
|
8
|
-
const MAX_STORED_QUERY_WIDTH = 100;
|
|
9
|
-
const MIN_TERM_COLUMN_WIDTH = 20;
|
|
10
|
-
const STORED_QUERY_COLUMN_GAP = 2;
|
|
11
|
-
|
|
12
5
|
/**
|
|
13
6
|
* @typedef {'description' | 'field_name' | 'keyword' | 'literal' | 'name' | 'operator' | 'plain'} StoredQuerySegmentKind
|
|
14
7
|
*/
|
|
@@ -18,336 +11,29 @@ const STORED_QUERY_COLUMN_GAP = 2;
|
|
|
18
11
|
*/
|
|
19
12
|
|
|
20
13
|
/**
|
|
21
|
-
*
|
|
22
|
-
*
|
|
23
|
-
* @param {
|
|
14
|
+
* @param {string} where_clause
|
|
15
|
+
* @param {number} first_line_width
|
|
16
|
+
* @param {number} continuation_width
|
|
24
17
|
* @returns {StoredQuerySegment[][]}
|
|
25
18
|
*/
|
|
26
|
-
export function
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
MIN_TERM_COLUMN_WIDTH,
|
|
36
|
-
MAX_STORED_QUERY_WIDTH - name_column_width - STORED_QUERY_COLUMN_GAP,
|
|
37
|
-
);
|
|
38
|
-
|
|
39
|
-
return output_items.flatMap((output_item) =>
|
|
40
|
-
layoutStoredQuery(output_item, name_column_width, term_column_width),
|
|
19
|
+
export function layoutStoredQueryRow(
|
|
20
|
+
where_clause,
|
|
21
|
+
first_line_width,
|
|
22
|
+
continuation_width,
|
|
23
|
+
) {
|
|
24
|
+
return wrapPhrasesWithLineWidths(
|
|
25
|
+
createStoredQueryPhrases(where_clause),
|
|
26
|
+
first_line_width,
|
|
27
|
+
continuation_width,
|
|
41
28
|
);
|
|
42
29
|
}
|
|
43
30
|
|
|
44
|
-
/**
|
|
45
|
-
* @param {OutputStoredQueryItem} output_item
|
|
46
|
-
* @param {number} name_column_width
|
|
47
|
-
* @param {number} term_column_width
|
|
48
|
-
* @returns {StoredQuerySegment[][]}
|
|
49
|
-
*/
|
|
50
|
-
function layoutStoredQuery(output_item, name_column_width, term_column_width) {
|
|
51
|
-
const term_lines = wrapPhrases(
|
|
52
|
-
createStoredQueryPhrases(output_item.where),
|
|
53
|
-
term_column_width,
|
|
54
|
-
);
|
|
55
|
-
const continuation_prefix = ' '.repeat(
|
|
56
|
-
name_column_width + STORED_QUERY_COLUMN_GAP,
|
|
57
|
-
);
|
|
58
|
-
|
|
59
|
-
/** @type {StoredQuerySegment[][]} */
|
|
60
|
-
const output_lines = term_lines.map((line_segments, line_index) => {
|
|
61
|
-
if (line_index === 0) {
|
|
62
|
-
return [
|
|
63
|
-
{
|
|
64
|
-
kind: 'name',
|
|
65
|
-
text: output_item.name.padEnd(name_column_width, ' '),
|
|
66
|
-
},
|
|
67
|
-
{
|
|
68
|
-
kind: 'plain',
|
|
69
|
-
text: ' '.repeat(STORED_QUERY_COLUMN_GAP),
|
|
70
|
-
},
|
|
71
|
-
...line_segments,
|
|
72
|
-
];
|
|
73
|
-
}
|
|
74
|
-
|
|
75
|
-
return [
|
|
76
|
-
{
|
|
77
|
-
kind: 'plain',
|
|
78
|
-
text: continuation_prefix,
|
|
79
|
-
},
|
|
80
|
-
...line_segments,
|
|
81
|
-
];
|
|
82
|
-
});
|
|
83
|
-
|
|
84
|
-
if (output_item.description) {
|
|
85
|
-
for (const description_line of output_item.description.split('\n')) {
|
|
86
|
-
output_lines.push([
|
|
87
|
-
{
|
|
88
|
-
kind: 'description',
|
|
89
|
-
text: `${' '.repeat(name_column_width + STORED_QUERY_COLUMN_GAP)}${description_line}`,
|
|
90
|
-
},
|
|
91
|
-
]);
|
|
92
|
-
}
|
|
93
|
-
}
|
|
94
|
-
|
|
95
|
-
output_lines.push([]);
|
|
96
|
-
|
|
97
|
-
return output_lines;
|
|
98
|
-
}
|
|
99
|
-
|
|
100
31
|
/**
|
|
101
32
|
* @param {string} where_clause
|
|
102
33
|
* @returns {StoredQuerySegment[][]}
|
|
103
34
|
*/
|
|
104
35
|
function createStoredQueryPhrases(where_clause) {
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
if (!parse_result.success) {
|
|
108
|
-
return createFallbackPhrases(where_clause);
|
|
109
|
-
}
|
|
110
|
-
|
|
111
|
-
return createExpressionPhrases(parse_result.expression);
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
/**
|
|
115
|
-
* @param {import('../graph/parse-where-clause.types.ts').ParsedExpression} expression
|
|
116
|
-
* @returns {StoredQuerySegment[][]}
|
|
117
|
-
*/
|
|
118
|
-
function createExpressionPhrases(expression) {
|
|
119
|
-
if (expression.kind !== 'and' && expression.kind !== 'or') {
|
|
120
|
-
return [[...createExpressionSegments(expression, 0)]];
|
|
121
|
-
}
|
|
122
|
-
|
|
123
|
-
return expression.expressions.map((subexpression, expression_index) =>
|
|
124
|
-
createExpressionPhrase(
|
|
125
|
-
subexpression,
|
|
126
|
-
expression.kind,
|
|
127
|
-
expression_index > 0,
|
|
128
|
-
),
|
|
129
|
-
);
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
* @param {import('../graph/parse-where-clause.types.ts').ParsedExpression} expression
|
|
134
|
-
* @param {'and' | 'or'} operator
|
|
135
|
-
* @param {boolean} should_prefix_operator
|
|
136
|
-
* @returns {StoredQuerySegment[]}
|
|
137
|
-
*/
|
|
138
|
-
function createExpressionPhrase(expression, operator, should_prefix_operator) {
|
|
139
|
-
/** @type {StoredQuerySegment[]} */
|
|
140
|
-
const phrase = [];
|
|
141
|
-
|
|
142
|
-
if (should_prefix_operator) {
|
|
143
|
-
phrase.push({ kind: 'keyword', text: operator });
|
|
144
|
-
phrase.push({ kind: 'plain', text: ' ' });
|
|
145
|
-
}
|
|
146
|
-
|
|
147
|
-
phrase.push(
|
|
148
|
-
...createExpressionSegments(
|
|
149
|
-
expression,
|
|
150
|
-
getBooleanExpressionPrecedence(operator),
|
|
151
|
-
),
|
|
152
|
-
);
|
|
153
|
-
|
|
154
|
-
return phrase;
|
|
155
|
-
}
|
|
156
|
-
|
|
157
|
-
/**
|
|
158
|
-
* @param {import('../graph/parse-where-clause.types.ts').ParsedExpression} expression
|
|
159
|
-
* @param {number} parent_precedence
|
|
160
|
-
* @returns {StoredQuerySegment[]}
|
|
161
|
-
*/
|
|
162
|
-
function createExpressionSegments(expression, parent_precedence) {
|
|
163
|
-
const expression_precedence = getExpressionPrecedence(expression);
|
|
164
|
-
const expression_segments = createRawExpressionSegments(expression);
|
|
165
|
-
|
|
166
|
-
if (expression_precedence >= parent_precedence) {
|
|
167
|
-
return expression_segments;
|
|
168
|
-
}
|
|
169
|
-
|
|
170
|
-
return [
|
|
171
|
-
{ kind: 'operator', text: '(' },
|
|
172
|
-
...expression_segments,
|
|
173
|
-
{ kind: 'operator', text: ')' },
|
|
174
|
-
];
|
|
175
|
-
}
|
|
176
|
-
|
|
177
|
-
/**
|
|
178
|
-
* @param {import('../graph/parse-where-clause.types.ts').ParsedExpression} expression
|
|
179
|
-
* @returns {StoredQuerySegment[]}
|
|
180
|
-
*/
|
|
181
|
-
function createRawExpressionSegments(expression) {
|
|
182
|
-
if (expression.kind === 'and' || expression.kind === 'or') {
|
|
183
|
-
return expression.expressions.flatMap((subexpression, expression_index) => {
|
|
184
|
-
const subexpression_segments = createExpressionSegments(
|
|
185
|
-
subexpression,
|
|
186
|
-
getExpressionPrecedence(expression),
|
|
187
|
-
);
|
|
188
|
-
|
|
189
|
-
if (expression_index === 0) {
|
|
190
|
-
return subexpression_segments;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
return [
|
|
194
|
-
{ kind: 'plain', text: ' ' },
|
|
195
|
-
{ kind: 'keyword', text: expression.kind },
|
|
196
|
-
{ kind: 'plain', text: ' ' },
|
|
197
|
-
...subexpression_segments,
|
|
198
|
-
];
|
|
199
|
-
});
|
|
200
|
-
}
|
|
201
|
-
|
|
202
|
-
if (expression.kind === 'not') {
|
|
203
|
-
return [
|
|
204
|
-
{ kind: 'keyword', text: 'not' },
|
|
205
|
-
{ kind: 'plain', text: ' ' },
|
|
206
|
-
...createExpressionSegments(
|
|
207
|
-
expression.expression,
|
|
208
|
-
getExpressionPrecedence(expression),
|
|
209
|
-
),
|
|
210
|
-
];
|
|
211
|
-
}
|
|
212
|
-
|
|
213
|
-
if (expression.kind === 'term') {
|
|
214
|
-
return createTermSegments(expression.term);
|
|
215
|
-
}
|
|
216
|
-
|
|
217
|
-
throw new Error('Unsupported stored-query expression.');
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
/**
|
|
221
|
-
* @param {import('../graph/parse-where-clause.types.ts').ParsedExpression} expression
|
|
222
|
-
* @returns {number}
|
|
223
|
-
*/
|
|
224
|
-
function getExpressionPrecedence(expression) {
|
|
225
|
-
if (expression.kind === 'or') {
|
|
226
|
-
return 1;
|
|
227
|
-
}
|
|
228
|
-
|
|
229
|
-
if (expression.kind === 'and') {
|
|
230
|
-
return 2;
|
|
231
|
-
}
|
|
232
|
-
|
|
233
|
-
if (expression.kind === 'not') {
|
|
234
|
-
return 3;
|
|
235
|
-
}
|
|
236
|
-
|
|
237
|
-
return 4;
|
|
238
|
-
}
|
|
239
|
-
|
|
240
|
-
/**
|
|
241
|
-
* @param {'and' | 'or'} operator
|
|
242
|
-
* @returns {number}
|
|
243
|
-
*/
|
|
244
|
-
function getBooleanExpressionPrecedence(operator) {
|
|
245
|
-
return operator === 'or' ? 1 : 2;
|
|
246
|
-
}
|
|
247
|
-
|
|
248
|
-
/**
|
|
249
|
-
* @param {import('../graph/parse-where-clause.types.ts').ParsedTerm} term
|
|
250
|
-
* @returns {StoredQuerySegment[]}
|
|
251
|
-
*/
|
|
252
|
-
function createTermSegments(term) {
|
|
253
|
-
if (term.kind === 'aggregate') {
|
|
254
|
-
return createAggregateSegments(term);
|
|
255
|
-
}
|
|
256
|
-
|
|
257
|
-
if (term.kind === 'field') {
|
|
258
|
-
return [
|
|
259
|
-
{ kind: 'field_name', text: term.field_name },
|
|
260
|
-
{ kind: 'operator', text: term.operator },
|
|
261
|
-
{ kind: 'literal', text: term.value },
|
|
262
|
-
];
|
|
263
|
-
}
|
|
264
|
-
|
|
265
|
-
if (term.kind === 'field_set') {
|
|
266
|
-
return createFieldSetSegments(term);
|
|
267
|
-
}
|
|
268
|
-
|
|
269
|
-
if (term.kind === 'relation_target') {
|
|
270
|
-
return [
|
|
271
|
-
{ kind: 'field_name', text: term.relation_name },
|
|
272
|
-
{ kind: 'operator', text: '=' },
|
|
273
|
-
{ kind: 'literal', text: term.target_id },
|
|
274
|
-
];
|
|
275
|
-
}
|
|
276
|
-
|
|
277
|
-
return [
|
|
278
|
-
{ kind: 'field_name', text: term.relation_name },
|
|
279
|
-
{ kind: 'operator', text: ':*' },
|
|
280
|
-
];
|
|
281
|
-
}
|
|
282
|
-
|
|
283
|
-
/**
|
|
284
|
-
* @param {import('../graph/parse-where-clause.types.ts').ParsedFieldSetTerm} term
|
|
285
|
-
* @returns {StoredQuerySegment[]}
|
|
286
|
-
*/
|
|
287
|
-
function createFieldSetSegments(term) {
|
|
288
|
-
return [
|
|
289
|
-
{ kind: 'field_name', text: term.field_name },
|
|
290
|
-
{ kind: 'plain', text: ' ' },
|
|
291
|
-
{ kind: 'operator', text: term.operator },
|
|
292
|
-
{ kind: 'plain', text: ' ' },
|
|
293
|
-
{ kind: 'operator', text: '[' },
|
|
294
|
-
...createListSegments(term.values),
|
|
295
|
-
{ kind: 'operator', text: ']' },
|
|
296
|
-
];
|
|
297
|
-
}
|
|
298
|
-
|
|
299
|
-
/**
|
|
300
|
-
* @param {import('../graph/parse-where-clause.types.ts').ParsedAggregateTerm} term
|
|
301
|
-
* @returns {StoredQuerySegment[]}
|
|
302
|
-
*/
|
|
303
|
-
function createAggregateSegments(term) {
|
|
304
|
-
/** @type {StoredQuerySegment[]} */
|
|
305
|
-
const segments = [
|
|
306
|
-
{ kind: 'field_name', text: term.aggregate_name },
|
|
307
|
-
{ kind: 'operator', text: '(' },
|
|
308
|
-
...createTraversalSegments(term.traversal),
|
|
309
|
-
{ kind: 'operator', text: ', ' },
|
|
310
|
-
...createExpressionSegments(term.expression, 0),
|
|
311
|
-
{ kind: 'operator', text: ')' },
|
|
312
|
-
];
|
|
313
|
-
|
|
314
|
-
if (term.aggregate_name === 'count') {
|
|
315
|
-
segments.push({ kind: 'plain', text: ' ' });
|
|
316
|
-
segments.push({ kind: 'operator', text: term.comparison ?? '=' });
|
|
317
|
-
segments.push({ kind: 'plain', text: ' ' });
|
|
318
|
-
segments.push({ kind: 'literal', text: String(term.value ?? 0) });
|
|
319
|
-
}
|
|
320
|
-
|
|
321
|
-
return segments;
|
|
322
|
-
}
|
|
323
|
-
|
|
324
|
-
/**
|
|
325
|
-
* @param {{ direction: 'in' | 'out', relation_name: string }} traversal
|
|
326
|
-
* @returns {StoredQuerySegment[]}
|
|
327
|
-
*/
|
|
328
|
-
function createTraversalSegments(traversal) {
|
|
329
|
-
return [
|
|
330
|
-
{ kind: 'field_name', text: traversal.direction },
|
|
331
|
-
{ kind: 'operator', text: ':' },
|
|
332
|
-
{ kind: 'field_name', text: traversal.relation_name },
|
|
333
|
-
];
|
|
334
|
-
}
|
|
335
|
-
|
|
336
|
-
/**
|
|
337
|
-
* @param {string[]} values
|
|
338
|
-
* @returns {StoredQuerySegment[]}
|
|
339
|
-
*/
|
|
340
|
-
function createListSegments(values) {
|
|
341
|
-
return values.flatMap((value, value_index) => {
|
|
342
|
-
if (value_index === 0) {
|
|
343
|
-
return [{ kind: 'literal', text: value }];
|
|
344
|
-
}
|
|
345
|
-
|
|
346
|
-
return [
|
|
347
|
-
{ kind: 'operator', text: ', ' },
|
|
348
|
-
{ kind: 'literal', text: value },
|
|
349
|
-
];
|
|
350
|
-
});
|
|
36
|
+
return createFallbackPhrases(where_clause);
|
|
351
37
|
}
|
|
352
38
|
|
|
353
39
|
/**
|
|
@@ -366,15 +52,21 @@ function createFallbackPhrases(where_clause) {
|
|
|
366
52
|
|
|
367
53
|
/**
|
|
368
54
|
* @param {StoredQuerySegment[][]} phrases
|
|
369
|
-
* @param {number}
|
|
55
|
+
* @param {number} first_line_width
|
|
56
|
+
* @param {number} continuation_width
|
|
370
57
|
* @returns {StoredQuerySegment[][]}
|
|
371
58
|
*/
|
|
372
|
-
function
|
|
59
|
+
function wrapPhrasesWithLineWidths(
|
|
60
|
+
phrases,
|
|
61
|
+
first_line_width,
|
|
62
|
+
continuation_width,
|
|
63
|
+
) {
|
|
373
64
|
/** @type {StoredQuerySegment[][]} */
|
|
374
65
|
const lines = [];
|
|
375
66
|
/** @type {StoredQuerySegment[]} */
|
|
376
67
|
let current_line = [];
|
|
377
68
|
let current_width = 0;
|
|
69
|
+
let current_line_width = first_line_width;
|
|
378
70
|
|
|
379
71
|
for (const phrase of phrases) {
|
|
380
72
|
const phrase_width = measureSegments(phrase);
|
|
@@ -385,10 +77,11 @@ function wrapPhrases(phrases, term_column_width) {
|
|
|
385
77
|
continue;
|
|
386
78
|
}
|
|
387
79
|
|
|
388
|
-
if (current_width + 1 + phrase_width >
|
|
80
|
+
if (current_width + 1 + phrase_width > current_line_width) {
|
|
389
81
|
lines.push(current_line);
|
|
390
82
|
current_line = [...phrase];
|
|
391
83
|
current_width = phrase_width;
|
|
84
|
+
current_line_width = continuation_width;
|
|
392
85
|
continue;
|
|
393
86
|
}
|
|
394
87
|
|
|
@@ -42,8 +42,9 @@ function formatJsonFieldSuggestion(field_suggestion) {
|
|
|
42
42
|
confidence: field_suggestion.confidence,
|
|
43
43
|
conflicting_evidence: field_suggestion.conflicting_evidence,
|
|
44
44
|
evidence_references: field_suggestion.evidence_references,
|
|
45
|
-
likely_class_usage: field_suggestion.likely_class_usage,
|
|
46
45
|
likely_multiplicity: field_suggestion.likely_multiplicity,
|
|
46
|
+
likely_on: field_suggestion.likely_on,
|
|
47
|
+
likely_to: field_suggestion.likely_to,
|
|
47
48
|
likely_type: field_suggestion.likely_type,
|
|
48
49
|
name: field_suggestion.name,
|
|
49
50
|
};
|
|
@@ -106,10 +107,18 @@ function formatTextFieldSuggestion(field_suggestion, render_options) {
|
|
|
106
107
|
lines.push(
|
|
107
108
|
`${render_options.label(' likely type:')} ${field_suggestion.likely_type.name}`,
|
|
108
109
|
`${render_options.label(' likely multiplicity:')} ${field_suggestion.likely_multiplicity.name}`,
|
|
109
|
-
`${render_options.label(' likely
|
|
110
|
+
`${render_options.label(' likely on:')} ${field_suggestion.likely_on.types.join(', ')}`,
|
|
110
111
|
`${render_options.label(' confidence:')} ${formatConfidence(field_suggestion.confidence)}`,
|
|
111
112
|
);
|
|
112
113
|
|
|
114
|
+
if (field_suggestion.likely_to) {
|
|
115
|
+
lines.splice(
|
|
116
|
+
3,
|
|
117
|
+
0,
|
|
118
|
+
`${render_options.label(' likely to:')} ${field_suggestion.likely_to.type}`,
|
|
119
|
+
);
|
|
120
|
+
}
|
|
121
|
+
|
|
113
122
|
if (field_suggestion.evidence_references.length > 0) {
|
|
114
123
|
lines.push(
|
|
115
124
|
...formatTextEvidenceSection(
|
|
@@ -13,10 +13,10 @@ import { renderRichOutput } from './renderers/rich.js';
|
|
|
13
13
|
* Normalizes `query`, `queries`, and `show` results into renderer-specific
|
|
14
14
|
* output models.
|
|
15
15
|
*
|
|
16
|
-
*
|
|
17
|
-
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
16
|
+
* kind: output
|
|
17
|
+
* status: active
|
|
18
|
+
* tracked_in: ../../docs/plans/v0/source-anchor-dogfooding.md
|
|
19
|
+
* decided_by: ../../docs/decisions/cli-output-architecture.md
|
|
20
20
|
* @patram
|
|
21
21
|
* @see {@link ./show-document.js}
|
|
22
22
|
* @see {@link ../../docs/decisions/cli-output-architecture.md}
|
|
@@ -34,23 +34,27 @@ export {
|
|
|
34
34
|
* @param {OutputView} output_view
|
|
35
35
|
* @param {ResolvedOutputMode} output_mode
|
|
36
36
|
* @param {ParsedCliArguments} parsed_arguments
|
|
37
|
+
* @param {{ is_tty?: boolean, terminal_width?: number }=} render_options
|
|
37
38
|
* @returns {Promise<string>}
|
|
38
39
|
*/
|
|
39
40
|
export async function renderOutputView(
|
|
40
41
|
output_view,
|
|
41
42
|
output_mode,
|
|
42
43
|
parsed_arguments,
|
|
44
|
+
render_options = {},
|
|
43
45
|
) {
|
|
44
46
|
if (output_mode.renderer_name === 'json') {
|
|
45
47
|
return renderJsonOutput(output_view);
|
|
46
48
|
}
|
|
47
49
|
|
|
48
50
|
if (output_mode.renderer_name === 'plain') {
|
|
49
|
-
return renderPlainOutput(output_view);
|
|
51
|
+
return renderPlainOutput(output_view, render_options);
|
|
50
52
|
}
|
|
51
53
|
|
|
52
54
|
return renderRichOutput(output_view, {
|
|
53
55
|
color_enabled: output_mode.color_enabled,
|
|
54
56
|
color_mode: parsed_arguments.color_mode,
|
|
57
|
+
is_tty: render_options.is_tty,
|
|
58
|
+
terminal_width: render_options.terminal_width,
|
|
55
59
|
});
|
|
56
60
|
}
|