mirror of
https://github.com/DeusData/codebase-memory-mcp.git
synced 2026-10-29 07:13:44 +08:00
fix(ui): count a YAML list of mappings as a list in the file facts (K12)
The keys of a list's items stand at the same indent as the keys of a mapping, so the facts of django's .pre-commit-config.yaml read "repos (10 keys: rev, hooks, rev, hooks, ...)" for a list of 5 items, under "Read from the file; not generated by the model". A key that holds any item is now a list: its items are counted and their keys are named as item keys, "repos (list of 5; item keys: repo, rev, hooks)". Signed-off-by: Bernhard Jackiewicz <bj@techport.io>
This commit is contained in:
@@ -27,6 +27,24 @@ describe('facts read from a configuration or text file (K12)', () => {
|
||||
]);
|
||||
});
|
||||
|
||||
it('counts a list of mappings as a list, and the keys of its items as item keys, not as keys of the parent', () => {
|
||||
// django's .pre-commit-config.yaml: one key holding 5 items, each with repo, rev and hooks.
|
||||
const preCommit = ['repos:', ...['black', 'blacken-docs', 'isort', 'flake8', 'eslint'].flatMap(hook => [
|
||||
` - repo: https://github.com/example/${hook}`, ' rev: 1.0.0', ' hooks:', ` - id: ${hook}`])].join('\n') + '\n';
|
||||
expect(fileFactLines('.pre-commit-config.yaml', preCommit)).toEqual([
|
||||
'File kind: YAML configuration, not program code; 21 lines.',
|
||||
'Top-level keys (1): `repos` (list of 5; item keys: `repo`, `rev`, `hooks`).',
|
||||
]);
|
||||
// The items' first key on the dash line and the rest below it; mappings that differ in their keys.
|
||||
expect(fileFactLines('steps.yml', 'steps:\n - name: a\n run: x\n - name: b\n uses: y\n with:\n key: 1\n')).toContain(
|
||||
'Top-level keys (1): `steps` (list of 2; item keys: `name`, `run`, `uses`, `with`).');
|
||||
// A list written at its key's own indent, next to a mapping.
|
||||
expect(fileFactLines('x.yaml', 'repos:\n- repo: a\n rev: 1\n- repo: b\n rev: 2\nci:\n autofix: true\n')).toContain(
|
||||
'Top-level keys (2): `repos` (list of 2; item keys: `repo`, `rev`), `ci` (1 key: `autofix`).');
|
||||
// A list of plain values has no item keys.
|
||||
expect(fileFactLines('tags.yml', 'tags:\n - a\n - b\n - c\n')).toContain('Top-level keys (1): `tags` (list of 3).');
|
||||
});
|
||||
|
||||
it('reads a JSON file with JSON.parse: keys and what their values are', () => {
|
||||
expect(fileFactLines('package.json', '{"name": "demo", "private": true, "scripts": {"build": "tsc", "test": "vitest"}, "files": ["dist", "src"]}')).toEqual([
|
||||
'File kind: JSON data, not program code; 1 line.',
|
||||
|
||||
@@ -21,7 +21,8 @@ function yamlFacts(text: string): string[] {
|
||||
const outline = yamlOutline(text);
|
||||
if (!outline.length) return [];
|
||||
return [words.topKeys(outline.length, outline.slice(0, LISTED).map(item => `${quote(item.key)}${item.keys.length
|
||||
? ` (${words.children(item.keys.length, item.keys.slice(0, CHILDREN).map(quote))})` : item.items ? ` (${words.list(item.items)})` : ''}`))];
|
||||
? ` (${words.children(item.keys.length, item.keys.slice(0, CHILDREN).map(quote))})`
|
||||
: item.items ? ` (${words.list(item.items)}${item.itemKeys.length ? `; ${words.itemKeys(item.itemKeys.length, item.itemKeys.slice(0, CHILDREN).map(quote))}` : ''})` : ''}`))];
|
||||
}
|
||||
|
||||
function jsonValue(value: unknown): string {
|
||||
|
||||
@@ -254,6 +254,8 @@ export const fileWords = {
|
||||
children: (total: number, items: readonly string[]) => total ? `${counted(total, 'key', 'keys')}: ${listed(items, total)}` : 'no keys',
|
||||
keyCount: (total: number) => counted(total, 'key', 'keys'),
|
||||
list: (total: number) => `list of ${total.toLocaleString('en-US')}`,
|
||||
/** The keys the items of a list have: "item keys: `repo`, `rev`, `hooks`". */
|
||||
itemKeys: (total: number, items: readonly string[]) => `item keys: ${listed(items, total)}`,
|
||||
value: { text: 'text', number: 'number', boolean: 'true or false', empty: 'null' },
|
||||
items: (total: number) => `A list of ${counted(total, 'item', 'items')}.`,
|
||||
invalidJson: 'The text is not valid JSON, so no keys are counted.',
|
||||
|
||||
@@ -66,16 +66,21 @@ function children(lines: readonly Line[], at: number): number[] {
|
||||
}
|
||||
|
||||
/** The top-level keys of a YAML file in block layout, each with the keys right below it or
|
||||
* the length of the list it holds (K12). Keys inside block values are text, not keys. */
|
||||
export function yamlOutline(text: string): { key: string; keys: string[]; items: number }[] {
|
||||
* the length of the list it holds (K12). Keys inside block values are text, not keys.
|
||||
*
|
||||
* The keys of a list's items stand at the same indent as the keys of a mapping ("- repo:"
|
||||
* and "rev:" below it), so a key holding any item is a list: its items are counted, and
|
||||
* their keys are named as item keys, never as keys of the parent. */
|
||||
export function yamlOutline(text: string): { key: string; keys: string[]; items: number; itemKeys: string[] }[] {
|
||||
const lines = linesOf(text);
|
||||
if (!lines.length) return [];
|
||||
const level = Math.min(...lines.map(line => line.keyIndent));
|
||||
return lines.flatMap((line, index) => {
|
||||
if (line.keyIndent !== level || line.dash || !line.key) return [];
|
||||
const below = line.value ? [] : children(lines, index);
|
||||
return [{ key: line.key, keys: below.flatMap(child => !lines[child].dash && lines[child].key ? [lines[child].key!] : []),
|
||||
items: below.filter(child => lines[child].dash).length }];
|
||||
const named = below.flatMap(child => lines[child].key ? [lines[child].key!] : []);
|
||||
const items = below.filter(child => lines[child].dash).length;
|
||||
return [items ? { key: line.key, keys: [], items, itemKeys: [...new Set(named)] } : { key: line.key, keys: named, items: 0, itemKeys: [] }];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user