@mercury-fw/formatter 0.25.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/CHANGELOG.md +7 -0
- package/README.md +27 -0
- package/dist/index.d.ts +41 -0
- package/index.ts +89 -0
- package/package.json +39 -0
package/CHANGELOG.md
ADDED
package/README.md
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# @mercury-fw/formatter
|
|
2
|
+
|
|
3
|
+
Turns the lists a [Mercury](https://github.com/lucabro81/mercury-fw) plugin hands the user into text, applying rules the app writes in its own config. It holds no format of its own: without a rule, a kind of list isn't shown (the model still gets the data, and the log says which rule is missing).
|
|
4
|
+
|
|
5
|
+
```ts
|
|
6
|
+
import { formatterPlugin, formatter } from "@mercury-fw/formatter";
|
|
7
|
+
import { jiraPlugin, type JiraDisplays } from "@mercury-fw/plugin-jira";
|
|
8
|
+
|
|
9
|
+
const jiraIssueLine = (issue: JiraDisplays["issue-list"]) =>
|
|
10
|
+
`${issue.key} ${issue.status ? `[${issue.status}] ` : ""}${issue.summary}\n${issue.url}`;
|
|
11
|
+
|
|
12
|
+
export default defineMercuryConfig({
|
|
13
|
+
plugins: [
|
|
14
|
+
formatterPlugin(
|
|
15
|
+
jiraPlugin,
|
|
16
|
+
formatter<JiraDisplays>({
|
|
17
|
+
"issue-list": { item: jiraIssueLine, empty: "No matching issues." },
|
|
18
|
+
}),
|
|
19
|
+
),
|
|
20
|
+
],
|
|
21
|
+
});
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
- `formatter<D>(rules)` takes one rule per kind of list, keyed by the kinds the plugin declares (`D`, e.g. `JiraDisplays`), so a kind it doesn't emit or a rule for the wrong item shape fails the typecheck. A rule is the line for each item, or `{ item, empty }` when an empty list needs a text of its own. Items are joined with a blank line.
|
|
25
|
+
- `formatterPlugin(plugin, handler)` wraps a plugin so every list it hands over goes through the rules; everything else the plugin contributes passes through untouched.
|
|
26
|
+
|
|
27
|
+
MIT
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The formatter: turns the structured lists a data plugin hands to the user
|
|
3
|
+
* (its `display` channel) into text, by applying a rule the instance writes in
|
|
4
|
+
* its own config. It holds no format knowledge of its own — every rule comes
|
|
5
|
+
* from the config, keyed by the display's kind — and it knows no plugin: a data
|
|
6
|
+
* plugin declares the kinds it emits as a type, which is what keeps the
|
|
7
|
+
* config's keys and item shapes checked at compile time.
|
|
8
|
+
*
|
|
9
|
+
* - `formatter(rules)` builds a `DisplayHandler` from a `kind → rule` map.
|
|
10
|
+
* - `formatterPlugin(plugin, handler)` wraps a data plugin so every display
|
|
11
|
+
* its post-processor emits goes through the handler; everything else the
|
|
12
|
+
* plugin contributes passes through untouched.
|
|
13
|
+
*/
|
|
14
|
+
import type { Plugin, ToolDisplay } from "@mercury-fw/plugin-types";
|
|
15
|
+
/** Renders one display into the single text block shown to the user, or
|
|
16
|
+
* returns `undefined` when there is nothing to show (no rule for its kind, or
|
|
17
|
+
* an empty list with no empty text). */
|
|
18
|
+
export type DisplayHandler = (display: ToolDisplay) => string | undefined;
|
|
19
|
+
/** How one kind of list is rendered: the line for each item, or that plus the
|
|
20
|
+
* text to show when the list is empty. */
|
|
21
|
+
export type FormatterRule<T> = ((item: T) => string) | {
|
|
22
|
+
item: (item: T) => string;
|
|
23
|
+
empty?: string;
|
|
24
|
+
};
|
|
25
|
+
/**
|
|
26
|
+
* Builds a handler from the instance's rules. `D` is the data plugin's map of
|
|
27
|
+
* the kinds it emits to their item shapes (e.g. `JiraDisplays`), so a key it
|
|
28
|
+
* doesn't declare, or a rule for the wrong item shape, fails the typecheck.
|
|
29
|
+
* Every rule is optional: a kind without one has nothing to show.
|
|
30
|
+
*/
|
|
31
|
+
export declare function formatter<D>(rules: {
|
|
32
|
+
[K in keyof D]?: FormatterRule<D[K]>;
|
|
33
|
+
}): DisplayHandler;
|
|
34
|
+
/**
|
|
35
|
+
* Wraps `plugin` so its post-processor's displays are rendered through
|
|
36
|
+
* `handler`. Returns a plugin identical to `plugin` except for a `build` that
|
|
37
|
+
* runs the inner `build` (passing the runtime context straight through) and
|
|
38
|
+
* wraps the post-processor it contributes; the wrapped one flows to the
|
|
39
|
+
* plugin's `sessionTools` factory, which the composition hands it to.
|
|
40
|
+
*/
|
|
41
|
+
export declare function formatterPlugin(plugin: Plugin, handler: DisplayHandler): Plugin;
|
package/index.ts
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The formatter: turns the structured lists a data plugin hands to the user
|
|
3
|
+
* (its `display` channel) into text, by applying a rule the instance writes in
|
|
4
|
+
* its own config. It holds no format knowledge of its own — every rule comes
|
|
5
|
+
* from the config, keyed by the display's kind — and it knows no plugin: a data
|
|
6
|
+
* plugin declares the kinds it emits as a type, which is what keeps the
|
|
7
|
+
* config's keys and item shapes checked at compile time.
|
|
8
|
+
*
|
|
9
|
+
* - `formatter(rules)` builds a `DisplayHandler` from a `kind → rule` map.
|
|
10
|
+
* - `formatterPlugin(plugin, handler)` wraps a data plugin so every display
|
|
11
|
+
* its post-processor emits goes through the handler; everything else the
|
|
12
|
+
* plugin contributes passes through untouched.
|
|
13
|
+
*/
|
|
14
|
+
import type { Plugin, CliPostProcessor, CliResult, ToolDisplay } from "@mercury-fw/plugin-types";
|
|
15
|
+
|
|
16
|
+
/** Renders one display into the single text block shown to the user, or
|
|
17
|
+
* returns `undefined` when there is nothing to show (no rule for its kind, or
|
|
18
|
+
* an empty list with no empty text). */
|
|
19
|
+
export type DisplayHandler = (display: ToolDisplay) => string | undefined;
|
|
20
|
+
|
|
21
|
+
/** How one kind of list is rendered: the line for each item, or that plus the
|
|
22
|
+
* text to show when the list is empty. */
|
|
23
|
+
export type FormatterRule<T> = ((item: T) => string) | { item: (item: T) => string; empty?: string };
|
|
24
|
+
|
|
25
|
+
/** Items are rendered one per block, separated by a blank line. */
|
|
26
|
+
const ITEM_SEPARATOR = "\n\n";
|
|
27
|
+
|
|
28
|
+
/**
|
|
29
|
+
* Builds a handler from the instance's rules. `D` is the data plugin's map of
|
|
30
|
+
* the kinds it emits to their item shapes (e.g. `JiraDisplays`), so a key it
|
|
31
|
+
* doesn't declare, or a rule for the wrong item shape, fails the typecheck.
|
|
32
|
+
* Every rule is optional: a kind without one has nothing to show.
|
|
33
|
+
*/
|
|
34
|
+
export function formatter<D>(rules: { [K in keyof D]?: FormatterRule<D[K]> }): DisplayHandler {
|
|
35
|
+
const byKind = rules as Record<string, FormatterRule<unknown> | undefined>;
|
|
36
|
+
return (display) => {
|
|
37
|
+
const rule = byKind[display.type];
|
|
38
|
+
if (rule === undefined) {
|
|
39
|
+
return undefined;
|
|
40
|
+
}
|
|
41
|
+
const { item, empty } = typeof rule === "function" ? { item: rule, empty: undefined } : rule;
|
|
42
|
+
if (display.items.length === 0) {
|
|
43
|
+
return empty;
|
|
44
|
+
}
|
|
45
|
+
return display.items.map((i) => item(i)).join(ITEM_SEPARATOR);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
/** Applies `handler` to an ok result's display. A rendered block replaces the
|
|
50
|
+
* structured items; an empty list with nothing to show drops the display; a
|
|
51
|
+
* non-empty list with no rule stays structured (so it isn't shown) and is
|
|
52
|
+
* reported through `log`. Non-ok results and results with no display are
|
|
53
|
+
* returned as they are, without calling the handler. */
|
|
54
|
+
function renderDisplay(result: CliResult, handler: DisplayHandler, log: (m: string) => void, pluginName: string): CliResult {
|
|
55
|
+
if (!result.ok || !result.display) {
|
|
56
|
+
return result;
|
|
57
|
+
}
|
|
58
|
+
const text = handler(result.display);
|
|
59
|
+
if (text !== undefined) {
|
|
60
|
+
return { ...result, display: { ...result.display, items: [text] } };
|
|
61
|
+
}
|
|
62
|
+
if (result.display.items.length === 0) {
|
|
63
|
+
const { display: _dropped, ...rest } = result;
|
|
64
|
+
return rest;
|
|
65
|
+
}
|
|
66
|
+
log(`[formatter] no rule for "${result.display.type}" lists from plugin "${pluginName}" — list not shown`);
|
|
67
|
+
return result;
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
/**
|
|
71
|
+
* Wraps `plugin` so its post-processor's displays are rendered through
|
|
72
|
+
* `handler`. Returns a plugin identical to `plugin` except for a `build` that
|
|
73
|
+
* runs the inner `build` (passing the runtime context straight through) and
|
|
74
|
+
* wraps the post-processor it contributes; the wrapped one flows to the
|
|
75
|
+
* plugin's `sessionTools` factory, which the composition hands it to.
|
|
76
|
+
*/
|
|
77
|
+
export function formatterPlugin(plugin: Plugin, handler: DisplayHandler): Plugin {
|
|
78
|
+
return {
|
|
79
|
+
...plugin,
|
|
80
|
+
build: (ctx) => {
|
|
81
|
+
const inner = plugin.build ? plugin.build(ctx) : {};
|
|
82
|
+
const proc = inner.postProcess;
|
|
83
|
+
const wrapped: CliPostProcessor | undefined = proc
|
|
84
|
+
? (cmd, result) => renderDisplay(proc(cmd, result), handler, ctx.log, plugin.name)
|
|
85
|
+
: undefined;
|
|
86
|
+
return { ...inner, postProcess: wrapped };
|
|
87
|
+
},
|
|
88
|
+
};
|
|
89
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@mercury-fw/formatter",
|
|
3
|
+
"version": "0.25.0",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"license": "MIT",
|
|
6
|
+
"repository": {
|
|
7
|
+
"type": "git",
|
|
8
|
+
"url": "git+https://github.com/lucabro81/mercury-fw.git",
|
|
9
|
+
"directory": "packages/formatters/formatter"
|
|
10
|
+
},
|
|
11
|
+
"files": [
|
|
12
|
+
"*.ts",
|
|
13
|
+
"dist",
|
|
14
|
+
"CHANGELOG.md",
|
|
15
|
+
"!**/*.test.ts"
|
|
16
|
+
],
|
|
17
|
+
"publishConfig": {
|
|
18
|
+
"access": "public"
|
|
19
|
+
},
|
|
20
|
+
"exports": {
|
|
21
|
+
".": {
|
|
22
|
+
"mercury-fw-source": "./index.ts",
|
|
23
|
+
"types": "./dist/index.d.ts",
|
|
24
|
+
"default": "./index.ts"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"test": "bun test",
|
|
29
|
+
"typecheck": "tsc --noEmit"
|
|
30
|
+
},
|
|
31
|
+
"dependencies": {
|
|
32
|
+
"@mercury-fw/plugin-types": "0.25.0"
|
|
33
|
+
},
|
|
34
|
+
"devDependencies": {
|
|
35
|
+
"@mercury-fw/typescript-config": "*",
|
|
36
|
+
"@types/bun": "^1.4.0",
|
|
37
|
+
"typescript": "^6.0.3"
|
|
38
|
+
}
|
|
39
|
+
}
|