patram 0.11.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/commands/fields.js +0 -4
- package/lib/cli/commands/queries.js +10 -20
- 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 +71 -106
- package/lib/cli/main.js +10 -10
- package/lib/cli/parse-arguments-helpers.js +165 -59
- package/lib/cli/parse-arguments.js +4 -4
- package/lib/cli/render-help.js +2 -2
- package/lib/config/defaults.js +33 -25
- package/lib/config/load-patram-config.d.ts +8 -33
- package/lib/config/load-patram-config.js +9 -33
- package/lib/config/load-patram-config.types.d.ts +3 -40
- package/lib/config/manage-stored-queries-helpers.d.ts +4 -4
- package/lib/config/manage-stored-queries-helpers.js +91 -33
- package/lib/config/manage-stored-queries.d.ts +4 -4
- package/lib/config/manage-stored-queries.js +11 -5
- 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.js +6 -31
- 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.js +77 -23
- 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/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 +3 -5
- 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 +1 -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
|
@@ -17,28 +17,10 @@ import { findCloseMatch } from '../../find-close-match.js';
|
|
|
17
17
|
* @returns {{ success: true, value: { query_source: QuerySource, where_clause: string } } | { error: CliParseError, success: false }}
|
|
18
18
|
*/
|
|
19
19
|
export function resolveWhereClause(repo_config, command_arguments) {
|
|
20
|
-
|
|
21
|
-
const where_clause = command_arguments.slice(1).join(' ').trim();
|
|
22
|
-
|
|
23
|
-
if (where_clause.length === 0) {
|
|
24
|
-
return {
|
|
25
|
-
error: {
|
|
26
|
-
code: 'message',
|
|
27
|
-
message: 'Query requires a where clause.',
|
|
28
|
-
},
|
|
29
|
-
success: false,
|
|
30
|
-
};
|
|
31
|
-
}
|
|
20
|
+
const ad_hoc_result = resolveAdHocWhereClause(command_arguments);
|
|
32
21
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
value: {
|
|
36
|
-
query_source: {
|
|
37
|
-
kind: 'ad_hoc',
|
|
38
|
-
},
|
|
39
|
-
where_clause,
|
|
40
|
-
},
|
|
41
|
-
};
|
|
22
|
+
if (ad_hoc_result) {
|
|
23
|
+
return ad_hoc_result;
|
|
42
24
|
}
|
|
43
25
|
|
|
44
26
|
const stored_query_name = command_arguments[0];
|
|
@@ -47,7 +29,7 @@ export function resolveWhereClause(repo_config, command_arguments) {
|
|
|
47
29
|
return {
|
|
48
30
|
error: {
|
|
49
31
|
code: 'message',
|
|
50
|
-
message: 'Query requires "--
|
|
32
|
+
message: 'Query requires "--cypher" or a stored query name.',
|
|
51
33
|
},
|
|
52
34
|
success: false,
|
|
53
35
|
};
|
|
@@ -65,6 +47,18 @@ export function resolveWhereClause(repo_config, command_arguments) {
|
|
|
65
47
|
};
|
|
66
48
|
}
|
|
67
49
|
|
|
50
|
+
const query_text = stored_query.cypher;
|
|
51
|
+
|
|
52
|
+
if (!query_text) {
|
|
53
|
+
return {
|
|
54
|
+
error: {
|
|
55
|
+
code: 'message',
|
|
56
|
+
message: `Stored query "${stored_query_name}" is missing "cypher" query text.`,
|
|
57
|
+
},
|
|
58
|
+
success: false,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
|
|
68
62
|
return {
|
|
69
63
|
success: true,
|
|
70
64
|
value: {
|
|
@@ -72,7 +66,67 @@ export function resolveWhereClause(repo_config, command_arguments) {
|
|
|
72
66
|
kind: 'stored_query',
|
|
73
67
|
name: stored_query_name,
|
|
74
68
|
},
|
|
75
|
-
where_clause:
|
|
69
|
+
where_clause: query_text,
|
|
70
|
+
},
|
|
71
|
+
};
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
/**
|
|
75
|
+
* @param {string[]} command_arguments
|
|
76
|
+
* @returns {{ success: true, value: { query_source: QuerySource, where_clause: string } } | { error: CliParseError, success: false } | null}
|
|
77
|
+
*/
|
|
78
|
+
function resolveAdHocWhereClause(command_arguments) {
|
|
79
|
+
if (command_arguments[0] === '--cypher') {
|
|
80
|
+
return createAdHocWhereClauseResult(
|
|
81
|
+
command_arguments,
|
|
82
|
+
'--cypher',
|
|
83
|
+
'Query requires a Cypher statement.',
|
|
84
|
+
);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if (command_arguments[0] === '--where') {
|
|
88
|
+
return {
|
|
89
|
+
error: {
|
|
90
|
+
code: 'message',
|
|
91
|
+
message: 'Query requires a Cypher statement.',
|
|
92
|
+
},
|
|
93
|
+
success: false,
|
|
94
|
+
};
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
/**
|
|
101
|
+
* @param {string[]} command_arguments
|
|
102
|
+
* @param {'--cypher'} option_name
|
|
103
|
+
* @param {string} empty_message
|
|
104
|
+
* @returns {{ success: true, value: { query_source: QuerySource, where_clause: string } } | { error: CliParseError, success: false }}
|
|
105
|
+
*/
|
|
106
|
+
function createAdHocWhereClauseResult(
|
|
107
|
+
command_arguments,
|
|
108
|
+
option_name,
|
|
109
|
+
empty_message,
|
|
110
|
+
) {
|
|
111
|
+
const query_text = command_arguments.slice(1).join(' ').trim();
|
|
112
|
+
|
|
113
|
+
if (query_text.length === 0) {
|
|
114
|
+
return {
|
|
115
|
+
error: {
|
|
116
|
+
code: 'message',
|
|
117
|
+
message: empty_message,
|
|
118
|
+
},
|
|
119
|
+
success: false,
|
|
120
|
+
};
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
return {
|
|
124
|
+
success: true,
|
|
125
|
+
value: {
|
|
126
|
+
query_source: {
|
|
127
|
+
kind: 'ad_hoc',
|
|
128
|
+
},
|
|
129
|
+
where_clause: query_text,
|
|
76
130
|
},
|
|
77
131
|
};
|
|
78
132
|
}
|
|
@@ -15,17 +15,17 @@ import { writePagedOutput } from './write-paged-output.js';
|
|
|
15
15
|
* Resolves the final output mode and switches between direct stdout writes and
|
|
16
16
|
* interactive pager output.
|
|
17
17
|
*
|
|
18
|
-
*
|
|
19
|
-
*
|
|
20
|
-
*
|
|
21
|
-
*
|
|
18
|
+
* kind: output
|
|
19
|
+
* status: active
|
|
20
|
+
* tracked_in: ../../docs/plans/v0/source-anchor-dogfooding.md
|
|
21
|
+
* decided_by: ../../docs/decisions/tty-pager-output.md
|
|
22
22
|
* @patram
|
|
23
23
|
* @see {@link ./render-output-view.js}
|
|
24
24
|
* @see {@link ../../docs/decisions/tty-pager-output.md}
|
|
25
25
|
*/
|
|
26
26
|
|
|
27
27
|
/**
|
|
28
|
-
* @param {{ stdout: { isTTY?: boolean, write(chunk: string): boolean }, write_paged_output?: (output_text: string) => Promise<void> }} io_context
|
|
28
|
+
* @param {{ stdout: { columns?: number, isTTY?: boolean, write(chunk: string): boolean }, write_paged_output?: (output_text: string) => Promise<void> }} io_context
|
|
29
29
|
* @param {ParsedCliArguments} parsed_command
|
|
30
30
|
* @param {OutputView} output_view
|
|
31
31
|
* @returns {Promise<void>}
|
|
@@ -43,6 +43,13 @@ export async function writeCommandOutput(
|
|
|
43
43
|
term: process.env.TERM,
|
|
44
44
|
}),
|
|
45
45
|
parsed_command,
|
|
46
|
+
{
|
|
47
|
+
is_tty: io_context.stdout.isTTY === true,
|
|
48
|
+
terminal_width:
|
|
49
|
+
io_context.stdout.isTTY === true
|
|
50
|
+
? io_context.stdout.columns
|
|
51
|
+
: undefined,
|
|
52
|
+
},
|
|
46
53
|
);
|
|
47
54
|
|
|
48
55
|
await writeRenderedCommandOutput(io_context, parsed_command, rendered_output);
|
|
@@ -0,0 +1,221 @@
|
|
|
1
|
+
import wrapAnsi from 'wrap-ansi';
|
|
2
|
+
|
|
3
|
+
const BODY_INDENT = ' ';
|
|
4
|
+
const TITLE_COLUMN_GAP = ' ';
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* @param {{ body_indent?: string, is_tty?: boolean, terminal_width?: number }} layout_options
|
|
8
|
+
* @returns {number}
|
|
9
|
+
*/
|
|
10
|
+
function getCompactBodyWidth(layout_options = {}) {
|
|
11
|
+
const body_indent = layout_options.body_indent ?? BODY_INDENT;
|
|
12
|
+
|
|
13
|
+
if (!hasTtyWidth(layout_options)) {
|
|
14
|
+
return Number.POSITIVE_INFINITY;
|
|
15
|
+
}
|
|
16
|
+
|
|
17
|
+
const terminal_width = /** @type {number} */ (layout_options.terminal_width);
|
|
18
|
+
|
|
19
|
+
return Math.max(1, terminal_width - body_indent.length);
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
/**
|
|
23
|
+
* @param {string[]} left_titles
|
|
24
|
+
* @returns {number}
|
|
25
|
+
*/
|
|
26
|
+
export function getCompactLeftTitleWidth(left_titles) {
|
|
27
|
+
if (left_titles.length === 0) {
|
|
28
|
+
return 0;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return Math.max(...left_titles.map((left_title) => left_title.length));
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
/**
|
|
35
|
+
* @param {string[]} metadata_rows
|
|
36
|
+
* @returns {string | undefined}
|
|
37
|
+
*/
|
|
38
|
+
export function formatCompactMetadataLabel(metadata_rows) {
|
|
39
|
+
if (metadata_rows.length === 0) {
|
|
40
|
+
return undefined;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
return `(${formatCompactMetadataFields(metadata_rows).join(', ')})`;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* @param {{
|
|
48
|
+
* format_left?: (text: string) => string,
|
|
49
|
+
* format_right?: (text: string) => string,
|
|
50
|
+
* is_tty?: boolean,
|
|
51
|
+
* left_title: string,
|
|
52
|
+
* left_title_width: number,
|
|
53
|
+
* right_title?: string,
|
|
54
|
+
* terminal_width?: number,
|
|
55
|
+
* }} options
|
|
56
|
+
* @returns {string}
|
|
57
|
+
*/
|
|
58
|
+
export function formatCompactTitleRow(options) {
|
|
59
|
+
const formatLeft = options.format_left ?? identity;
|
|
60
|
+
const formatRight = options.format_right ?? identity;
|
|
61
|
+
|
|
62
|
+
if (!options.right_title) {
|
|
63
|
+
return formatLeft(options.left_title);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (!hasTtyWidth(options)) {
|
|
67
|
+
return `${formatLeft(options.left_title)}${TITLE_COLUMN_GAP}${formatRight(options.right_title)}`;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
const terminal_width = /** @type {number} */ (options.terminal_width);
|
|
71
|
+
const available_width =
|
|
72
|
+
terminal_width - options.left_title.length - TITLE_COLUMN_GAP.length;
|
|
73
|
+
|
|
74
|
+
if (available_width <= 0) {
|
|
75
|
+
return formatLeft(options.left_title);
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
return `${formatLeft(options.left_title)}${TITLE_COLUMN_GAP}${formatRight(truncateText(options.right_title, available_width))}`;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* @param {string} text
|
|
83
|
+
* @param {{ body_indent?: string, is_tty?: boolean, terminal_width?: number }} layout_options
|
|
84
|
+
* @returns {string[]}
|
|
85
|
+
*/
|
|
86
|
+
export function wrapCompactBodyText(text, layout_options = {}) {
|
|
87
|
+
if (!hasTtyWidth(layout_options)) {
|
|
88
|
+
return text.split('\n');
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
const body_width = getCompactBodyWidth(layout_options);
|
|
92
|
+
/** @type {string[]} */
|
|
93
|
+
const lines = [];
|
|
94
|
+
|
|
95
|
+
for (const [line_index, source_line] of text.split('\n').entries()) {
|
|
96
|
+
if (line_index > 0 && source_line.length === 0) {
|
|
97
|
+
lines.push('');
|
|
98
|
+
continue;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
const wrapped_line = wrapAnsi(source_line, body_width, {
|
|
102
|
+
hard: false,
|
|
103
|
+
trim: false,
|
|
104
|
+
wordWrap: true,
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
lines.push(...wrapped_line.split('\n'));
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
return lines;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/**
|
|
114
|
+
* @param {{ is_tty?: boolean, terminal_width?: number }} layout_options
|
|
115
|
+
* @returns {boolean}
|
|
116
|
+
*/
|
|
117
|
+
function hasTtyWidth(layout_options) {
|
|
118
|
+
return (
|
|
119
|
+
layout_options.is_tty === true &&
|
|
120
|
+
typeof layout_options.terminal_width === 'number' &&
|
|
121
|
+
Number.isFinite(layout_options.terminal_width) &&
|
|
122
|
+
layout_options.terminal_width > 0
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
/**
|
|
127
|
+
* @param {string} value
|
|
128
|
+
* @param {number} max_width
|
|
129
|
+
* @returns {string}
|
|
130
|
+
*/
|
|
131
|
+
function truncateText(value, max_width) {
|
|
132
|
+
if (value.length <= max_width) {
|
|
133
|
+
return value;
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
if (max_width <= 1) {
|
|
137
|
+
return '…';
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
if (hasEnclosingPunctuation(value, '(', ')')) {
|
|
141
|
+
return truncateEnclosedText(value, max_width, '(', ')');
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
if (hasEnclosingPunctuation(value, '[', ']')) {
|
|
145
|
+
return truncateEnclosedText(value, max_width, '[', ']');
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
return `${value.slice(0, max_width - 1)}…`;
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
/**
|
|
152
|
+
* @param {string} value
|
|
153
|
+
* @param {number} max_width
|
|
154
|
+
* @param {string} open_character
|
|
155
|
+
* @param {string} close_character
|
|
156
|
+
* @returns {string}
|
|
157
|
+
*/
|
|
158
|
+
function truncateEnclosedText(
|
|
159
|
+
value,
|
|
160
|
+
max_width,
|
|
161
|
+
open_character,
|
|
162
|
+
close_character,
|
|
163
|
+
) {
|
|
164
|
+
if (max_width === 2) {
|
|
165
|
+
return `…${close_character}`;
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (max_width === 3) {
|
|
169
|
+
return `${open_character}…${close_character}`;
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
const inner_value = value.slice(1, -1);
|
|
173
|
+
const prefix_width = Math.max(0, max_width - 3);
|
|
174
|
+
|
|
175
|
+
return `${open_character}${inner_value.slice(0, prefix_width)}…${close_character}`;
|
|
176
|
+
}
|
|
177
|
+
|
|
178
|
+
/**
|
|
179
|
+
* @param {string[]} metadata_rows
|
|
180
|
+
* @returns {string[]}
|
|
181
|
+
*/
|
|
182
|
+
function formatCompactMetadataFields(metadata_rows) {
|
|
183
|
+
return metadata_rows
|
|
184
|
+
.flatMap((metadata_row) => metadata_row.split(' '))
|
|
185
|
+
.map(formatCompactMetadataField);
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
/**
|
|
189
|
+
* @param {string} metadata_field
|
|
190
|
+
* @returns {string}
|
|
191
|
+
*/
|
|
192
|
+
function formatCompactMetadataField(metadata_field) {
|
|
193
|
+
const separator_index = metadata_field.indexOf(': ');
|
|
194
|
+
|
|
195
|
+
if (separator_index === -1) {
|
|
196
|
+
return metadata_field;
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
const field_name = metadata_field.slice(0, separator_index);
|
|
200
|
+
const field_value = metadata_field.slice(separator_index + 2);
|
|
201
|
+
|
|
202
|
+
return `${field_name}=${field_value}`;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
/**
|
|
206
|
+
* @param {string} value
|
|
207
|
+
* @param {string} open_character
|
|
208
|
+
* @param {string} close_character
|
|
209
|
+
* @returns {boolean}
|
|
210
|
+
*/
|
|
211
|
+
function hasEnclosingPunctuation(value, open_character, close_character) {
|
|
212
|
+
return value.startsWith(open_character) && value.endsWith(close_character);
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
/**
|
|
216
|
+
* @param {string} value
|
|
217
|
+
* @returns {string}
|
|
218
|
+
*/
|
|
219
|
+
function identity(value) {
|
|
220
|
+
return value;
|
|
221
|
+
}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* @param {{ header: string, metadata_rows: string[], metadata_indent?: string, title: string, title_indent?: string }} options
|
|
2
|
+
* @param {{ description?: string, header: string, metadata_rows: string[], metadata_indent?: string, title: string, title_indent?: string }} options
|
|
3
3
|
* @returns {string}
|
|
4
4
|
*/
|
|
5
5
|
export function formatOutputItemBlock(options) {
|
|
@@ -18,5 +18,35 @@ export function formatOutputItemBlock(options) {
|
|
|
18
18
|
|
|
19
19
|
lines.push('', `${title_indent}${options.title}`);
|
|
20
20
|
|
|
21
|
+
if (options.description) {
|
|
22
|
+
lines.push(
|
|
23
|
+
'',
|
|
24
|
+
...formatIndentedDescription(options.description, title_indent),
|
|
25
|
+
);
|
|
26
|
+
}
|
|
27
|
+
|
|
21
28
|
return lines.join('\n');
|
|
22
29
|
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* @param {string} description
|
|
33
|
+
* @param {string} indent
|
|
34
|
+
* @returns {string[]}
|
|
35
|
+
*/
|
|
36
|
+
function formatIndentedDescription(description, indent) {
|
|
37
|
+
/** @type {string[]} */
|
|
38
|
+
const lines = [];
|
|
39
|
+
const paragraphs = description.split('\n\n');
|
|
40
|
+
|
|
41
|
+
for (const [paragraph_index, paragraph] of paragraphs.entries()) {
|
|
42
|
+
if (paragraph_index > 0) {
|
|
43
|
+
lines.push('');
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
for (const paragraph_line of paragraph.split('\n')) {
|
|
47
|
+
lines.push(`${indent}${paragraph_line}`);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
return lines;
|
|
52
|
+
}
|
|
@@ -1,44 +1,31 @@
|
|
|
1
|
-
import { formatDerivedSummaryRow } from './format-derived-summary-row.js';
|
|
2
|
-
|
|
3
1
|
/**
|
|
4
2
|
* @param {import('./output-view.types.ts').OutputNodeItem} output_item
|
|
5
|
-
* @returns {string
|
|
3
|
+
* @returns {string | undefined}
|
|
6
4
|
*/
|
|
7
|
-
export function
|
|
8
|
-
|
|
9
|
-
const metadata_rows = [];
|
|
10
|
-
const stored_metadata_fields =
|
|
11
|
-
output_item.visible_fields.map(formatMetadataField);
|
|
12
|
-
|
|
13
|
-
if (stored_metadata_fields.length > 0) {
|
|
14
|
-
metadata_rows.push(stored_metadata_fields.join(' '));
|
|
15
|
-
}
|
|
16
|
-
|
|
17
|
-
if (output_item.derived_summary) {
|
|
18
|
-
metadata_rows.push(formatDerivedSummaryRow(output_item.derived_summary));
|
|
19
|
-
}
|
|
20
|
-
|
|
21
|
-
return metadata_rows;
|
|
5
|
+
export function formatOutputNodeStoredMetadataRow(output_item) {
|
|
6
|
+
return formatStoredMetadataRow(output_item.visible_fields);
|
|
22
7
|
}
|
|
23
8
|
|
|
24
9
|
/**
|
|
25
10
|
* @param {import('./output-view.types.ts').OutputResolvedLinkTarget} target
|
|
26
|
-
* @returns {string
|
|
11
|
+
* @returns {string | undefined}
|
|
27
12
|
*/
|
|
28
|
-
export function
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
const stored_metadata_fields = target.visible_fields.map(formatMetadataField);
|
|
13
|
+
export function formatResolvedLinkStoredMetadataRow(target) {
|
|
14
|
+
return formatStoredMetadataRow(target.visible_fields);
|
|
15
|
+
}
|
|
32
16
|
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
17
|
+
/**
|
|
18
|
+
* @param {import('./output-view.types.ts').OutputMetadataField[]} visible_fields
|
|
19
|
+
* @returns {string | undefined}
|
|
20
|
+
*/
|
|
21
|
+
function formatStoredMetadataRow(visible_fields) {
|
|
22
|
+
const stored_metadata_fields = visible_fields.map(formatMetadataField);
|
|
36
23
|
|
|
37
|
-
if (
|
|
38
|
-
|
|
24
|
+
if (stored_metadata_fields.length === 0) {
|
|
25
|
+
return undefined;
|
|
39
26
|
}
|
|
40
27
|
|
|
41
|
-
return
|
|
28
|
+
return stored_metadata_fields.join(' ');
|
|
42
29
|
}
|
|
43
30
|
|
|
44
31
|
/**
|
|
@@ -0,0 +1,95 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @import { OutputStoredQueryItem } from './output-view.types.ts';
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
import { wrapCompactBodyText } from './compact-layout.js';
|
|
6
|
+
import { layoutStoredQueryRow } from './layout-stored-queries.js';
|
|
7
|
+
|
|
8
|
+
/**
|
|
9
|
+
* @typedef {{
|
|
10
|
+
* kind: 'description' | 'field_name' | 'keyword' | 'literal' | 'name' | 'operator' | 'plain',
|
|
11
|
+
* text: string,
|
|
12
|
+
* }} StoredQueryLineSegment
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
/**
|
|
16
|
+
* @param {OutputStoredQueryItem} output_item
|
|
17
|
+
* @param {{ is_tty?: boolean, terminal_width?: number }} render_options
|
|
18
|
+
* @param {{
|
|
19
|
+
* format_description?: (text: string) => string,
|
|
20
|
+
* format_line: (line_segments: StoredQueryLineSegment[]) => string,
|
|
21
|
+
* format_name: (text: string) => string,
|
|
22
|
+
* }} format_options
|
|
23
|
+
* @returns {string}
|
|
24
|
+
*/
|
|
25
|
+
export function formatStoredQueryBlock(
|
|
26
|
+
output_item,
|
|
27
|
+
render_options,
|
|
28
|
+
format_options,
|
|
29
|
+
) {
|
|
30
|
+
const row_layout = getStoredQueryRowLayout(output_item.name, render_options);
|
|
31
|
+
const formatDescription = format_options.format_description ?? identity;
|
|
32
|
+
const output_lines = layoutStoredQueryRow(
|
|
33
|
+
output_item.where,
|
|
34
|
+
row_layout.first_line_width,
|
|
35
|
+
row_layout.continuation_width,
|
|
36
|
+
).map((line_segments, line_index) =>
|
|
37
|
+
line_index === 0
|
|
38
|
+
? `${format_options.format_name(output_item.name)} ${format_options.format_line(line_segments)}`
|
|
39
|
+
: `${row_layout.continuation_prefix}${format_options.format_line(line_segments)}`,
|
|
40
|
+
);
|
|
41
|
+
|
|
42
|
+
if (output_item.description) {
|
|
43
|
+
for (const description_line of wrapCompactBodyText(
|
|
44
|
+
output_item.description,
|
|
45
|
+
render_options,
|
|
46
|
+
)) {
|
|
47
|
+
output_lines.push(
|
|
48
|
+
description_line.length > 0
|
|
49
|
+
? ` ${formatDescription(description_line)}`
|
|
50
|
+
: '',
|
|
51
|
+
);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
return output_lines.join('\n');
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* @param {string} query_name
|
|
60
|
+
* @param {{ is_tty?: boolean, terminal_width?: number }} render_options
|
|
61
|
+
* @returns {{ continuation_prefix: string, continuation_width: number, first_line_width: number }}
|
|
62
|
+
*/
|
|
63
|
+
function getStoredQueryRowLayout(query_name, render_options) {
|
|
64
|
+
const continuation_prefix = ' '.repeat(query_name.length + 2);
|
|
65
|
+
|
|
66
|
+
if (
|
|
67
|
+
render_options.is_tty !== true ||
|
|
68
|
+
typeof render_options.terminal_width !== 'number'
|
|
69
|
+
) {
|
|
70
|
+
return {
|
|
71
|
+
continuation_prefix,
|
|
72
|
+
continuation_width: Number.POSITIVE_INFINITY,
|
|
73
|
+
first_line_width: Number.POSITIVE_INFINITY,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
const available_width = Math.max(
|
|
78
|
+
1,
|
|
79
|
+
render_options.terminal_width - continuation_prefix.length,
|
|
80
|
+
);
|
|
81
|
+
|
|
82
|
+
return {
|
|
83
|
+
continuation_prefix,
|
|
84
|
+
continuation_width: available_width,
|
|
85
|
+
first_line_width: available_width,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
/**
|
|
90
|
+
* @param {string} value
|
|
91
|
+
* @returns {string}
|
|
92
|
+
*/
|
|
93
|
+
function identity(value) {
|
|
94
|
+
return value;
|
|
95
|
+
}
|