@flowscripter/dynamic-cli-framework 1.0.11 → 1.1.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/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
  > A framework for developing CLI applications which supports dynamic discovery
10
10
  > and installation of new commands
11
11
 
12
- [//]: # (TODO: Remove this when plugin support and compiled binaries available.)
12
+ [//]: # (TODO: Remove this when plugin support.)
13
13
 
14
14
  NOTE: The dynamic aspect is still in development as it relies upon:
15
15
 
@@ -45,8 +45,8 @@ So it isn't really dynamic at the moment! 😜
45
45
  - Support (optional) for persisted configuration and environment variables to
46
46
  specify command argument defaults.
47
47
  - Core (but optional) commands for help, logging level and version management.
48
- - Core (but optional) services for color output to stdout/stderr and
49
- configuration management.
48
+ - Core (but optional) services for color output, syntax highlighting and pretty
49
+ printing to stdout/stderr and configuration management.
50
50
  - Core (but optional) support for dynamic discovery and installation of commands
51
51
  and services using
52
52
  [dynamic-plugin-framework](https://github.com/flowscripter/dynamic-plugin-framework)
@@ -62,13 +62,12 @@ The following example projects are available:
62
62
 
63
63
  - [example-cli](https://github.com/flowscripter/example-cli) is an example CLI
64
64
  application based on this framework.
65
+ - [mpeg-sdl-tool](https://github.com/flowscripter/mpeg-sdl-tool) is a real world
66
+ use case CLI application based on this framework.
65
67
 
66
68
  [//]: # (TODO: Add this when implemented.)
67
69
  [//]: # (- [example-cli-plugin](https://github.com/flowscripter/example-cli-plugin) is an)
68
70
  [//]: # ( example command and service plugin based on this framework.)
69
- [//]: # (TODO: Add this when implemented.)
70
- [//]: # (- [sdl-tool](https://github.com/flowscripter/sdl-tool) is a real world use case CLI)
71
- [//]: # ( application based on this framework.)
72
71
 
73
72
  ## Key Concepts
74
73
 
@@ -1240,6 +1239,14 @@ Provides:
1240
1239
  - `LogLevelCommand` which allows the log level to be set via the argument
1241
1240
  `--log-level` or the env var `LOG_LEVEL`.
1242
1241
 
1242
+ #### `PrettyPrinterServiceProvider`
1243
+
1244
+ Provides:
1245
+
1246
+ - `PrettyPrinterService` allowing pretty printing of structured data and source
1247
+ code. JSON pretty printing is provided by default and other data or language
1248
+ formats can be added on demand by commands.
1249
+
1243
1250
  #### `SyntaxHighlighterServiceProvider`
1244
1251
 
1245
1252
  Provides:
package/bun.lock CHANGED
@@ -8,6 +8,7 @@
8
8
  "fastest-levenshtein": "^1.0.16",
9
9
  "figlet": "^1.8.0",
10
10
  "highlight.js": "^11.11.1",
11
+ "prettier": "^3.5.3",
11
12
  "supports-color": "^10.0.0",
12
13
  "ts-log": "^2.2.7",
13
14
  },
@@ -48,6 +49,8 @@
48
49
 
49
50
  "lowlight": ["lowlight@3.1.0", "", { "dependencies": { "@types/hast": "^3.0.0", "devlop": "^1.0.0", "highlight.js": "~11.9.0" } }, "sha512-CEbNVoSikAxwDMDPjXlqlFYiZLkDJHwyGu/MfOsJnF3d7f3tds5J3z8s/l9TMXhzfsJCCJEAsD78842mwmg0PQ=="],
50
51
 
52
+ "prettier": ["prettier@3.5.3", "", { "bin": { "prettier": "bin/prettier.cjs" } }, "sha512-QQtaxnoDJeAkDvDKWCLiwIXkTgRhwYDEQCghU9Z6q03iyek/rxRh/2lC3HB7P8sWT2xC/y5JDctPLBIGzHKbhw=="],
53
+
51
54
  "supports-color": ["supports-color@10.0.0", "", {}, "sha512-HRVVSbCCMbj7/kdWF9Q+bbckjBHLtHMEoJWlkmYzzdwhYMkjkOwubLM6t7NbWKjgKamGDrWL1++KrjUO1t9oAQ=="],
52
55
 
53
56
  "ts-log": ["ts-log@2.2.7", "", {}, "sha512-320x5Ggei84AxzlXp91QkIGSw5wgaLT6GeAH0KsqDmRZdVWW2OiSeVvElVoatk3f7nicwXlElXsoFkARiGE2yg=="],
package/package.json CHANGED
@@ -18,7 +18,7 @@
18
18
  ],
19
19
  "module": "index.ts",
20
20
  "type": "module",
21
- "version": "1.0.11",
21
+ "version": "1.1.0",
22
22
  "publishConfig": {
23
23
  "access": "public"
24
24
  },
@@ -33,6 +33,7 @@
33
33
  "fastest-levenshtein": "^1.0.16",
34
34
  "figlet": "^1.8.0",
35
35
  "highlight.js": "^11.11.1",
36
+ "prettier": "^3.5.3",
36
37
  "supports-color": "^10.0.0",
37
38
  "ts-log": "^2.2.7"
38
39
  }
@@ -0,0 +1,42 @@
1
+ export const PRETTY_PRINTER_SERVICE_ID =
2
+ "@flowscripter/dynamic-cli-framework/pretty-printer-service";
3
+
4
+ import type { Plugin as PrettierSyntax } from "prettier";
5
+
6
+ /**
7
+ * Service providing syntax based pretty printing of text for the CLI using [prettier](https://github.com/prettier/prettier).
8
+ */
9
+ export default interface PrettyPrinterService {
10
+ /**
11
+ * Register a new syntax.
12
+ *
13
+ * The recommended way to define a new language syntax plugin is to follow the instructions here:
14
+ *
15
+ * https://prettier.io/docs/plugins#developing-plugins
16
+ *
17
+ * There is a list of already defined languages available for import here:
18
+ *
19
+ * * https://prettier.io/docs/
20
+ * * https://prettier.io/docs/plugins
21
+ *
22
+ * @param syntaxName the name used to refer to the syntax.
23
+ * @param syntaxDefinition the definition for the syntax conforming to the Prettier plugin syntax.
24
+ */
25
+ registerSyntax(
26
+ syntaxName: string,
27
+ syntaxDefinition: PrettierSyntax<unknown>,
28
+ ): Promise<void>;
29
+
30
+ /**
31
+ * Return the names of the currently registered syntaxes.
32
+ */
33
+ getRegisteredSyntaxes(): Promise<ReadonlyArray<string>>;
34
+
35
+ /**
36
+ * Return a pretty printed version of the provided text using the specified syntax.
37
+ *
38
+ * @param text the text to prettify.
39
+ * @param syntaxName the syntax to use.
40
+ */
41
+ prettify(text: string, syntaxName: string): Promise<string>;
42
+ }
@@ -1,16 +1,16 @@
1
1
  import type { LanguageFn as HighlightSyntax } from "highlight.js";
2
2
 
3
3
  export const SYNTAX_HIGHLIGHTER_SERVICE_ID =
4
- "@flowscripter/dynamic-cli-framework/highlighter-service";
4
+ "@flowscripter/dynamic-cli-framework/syntax-highlighter-service";
5
5
 
6
6
  /**
7
- * Service providing syntax based color highlighting of text for the CLI using [highlight.js](https://highlightjs.org).
7
+ * Service providing syntax based color highlighting of text for the CLI using [highlight.js](https://github.com/highlightjs/highlight.js).
8
8
  */
9
9
  export default interface SyntaxHighlighterService {
10
10
  /**
11
11
  * Register a new syntax.
12
12
  *
13
- * The recommended way to define a new language syntax is follow the instructions here:
13
+ * The recommended way to define a new language syntax is to follow the instructions here:
14
14
  *
15
15
  * * https://highlightjs.readthedocs.io/en/latest/language-contribution.html
16
16
  * * https://highlightjs.readthedocs.io/en/latest/building-testing.html
@@ -22,7 +22,7 @@ export default interface SyntaxHighlighterService {
22
22
  *
23
23
  * @param syntaxName the name used to refer to the syntax.
24
24
  * @param syntaxDefinition the definition for the syntax conforming to the Highlight JS
25
- * [language definition syntax]().
25
+ * language definition syntax.
26
26
  */
27
27
  registerSyntax(syntaxName: string, syntaxDefinition: HighlightSyntax): void;
28
28
 
@@ -33,7 +33,7 @@ export default interface SyntaxHighlighterService {
33
33
 
34
34
  /**
35
35
  * Return a syntactically highlighted version of the provided text using the specified syntax.
36
- * The returned text will include appropriate ASCII color codes.
36
+ * The returned text will include appropriate terminal color codes.
37
37
  *
38
38
  * @param text the text to highlight.
39
39
  * @param syntaxName the syntax to use.
@@ -0,0 +1,67 @@
1
+ import type PrettyPrinterService from "../../api/service/core/PrettyPrinterService.ts";
2
+ import * as prettier from "prettier";
3
+ import type { Plugin as PrettierSyntax } from "prettier";
4
+
5
+ /**
6
+ * Default implementation of {@link PrettyPrinterService} which already has syntax definitions
7
+ * for those built into prettier.
8
+ */
9
+ export default class DefaultPrettyPrinterService
10
+ implements PrettyPrinterService {
11
+ #registeredSyntaxes = new Array<string>();
12
+ #syntaxNameToDefinitionMap = new Map<string, PrettierSyntax>();
13
+
14
+ async #populateBuiltInSyntaxes(): Promise<void> {
15
+ const languages = (await prettier.getSupportInfo()).languages;
16
+ this.#registeredSyntaxes = languages.map((language) =>
17
+ language.name.toLowerCase()
18
+ );
19
+ }
20
+
21
+ async getRegisteredSyntaxes(): Promise<ReadonlyArray<string>> {
22
+ if (this.#registeredSyntaxes.length === 0) {
23
+ await this.#populateBuiltInSyntaxes();
24
+ }
25
+
26
+ return this.#registeredSyntaxes;
27
+ }
28
+
29
+ async prettify(text: string, syntaxName: string): Promise<string> {
30
+ if (this.#registeredSyntaxes.length === 0) {
31
+ await this.#populateBuiltInSyntaxes();
32
+ }
33
+
34
+ const name = syntaxName.toLowerCase();
35
+ if (!this.#registeredSyntaxes.includes(name)) {
36
+ throw new Error(`Syntax name is not registered: ${name}`);
37
+ }
38
+
39
+ const options: prettier.Options = { parser: syntaxName };
40
+
41
+ const syntaxDefinition = this.#syntaxNameToDefinitionMap.get(
42
+ name,
43
+ );
44
+ if (syntaxDefinition) {
45
+ options.plugins = [syntaxDefinition];
46
+ }
47
+
48
+ return prettier.format(text, options);
49
+ }
50
+
51
+ async registerSyntax(
52
+ syntaxName: string,
53
+ syntaxDefinition: PrettierSyntax,
54
+ ): Promise<void> {
55
+ if (this.#registeredSyntaxes.length === 0) {
56
+ await this.#populateBuiltInSyntaxes();
57
+ }
58
+
59
+ const name = syntaxName.toLowerCase();
60
+ if (this.#registeredSyntaxes.includes(name)) {
61
+ throw new Error(`Syntax name already registered: ${name}`);
62
+ }
63
+
64
+ this.#registeredSyntaxes.push(name);
65
+ this.#syntaxNameToDefinitionMap.set(name, syntaxDefinition);
66
+ }
67
+ }
@@ -0,0 +1,40 @@
1
+ import type {
2
+ ServiceInfo,
3
+ ServiceProvider,
4
+ } from "../../api/service/ServiceProvider.ts";
5
+ import DefaultPrettyPrinterService from "./DefaultPrettyPrinterService.ts";
6
+ import {
7
+ PRETTY_PRINTER_SERVICE_ID,
8
+ } from "../../api/service/core/PrettyPrinterService.ts";
9
+ import type Context from "../../api/Context.ts";
10
+ import type CLIConfig from "../../api/CLIConfig.ts";
11
+
12
+ /**
13
+ * Provides a {@link PrettyPrinterService}.
14
+ */
15
+ export default class PrettyPrinterServiceProvider implements ServiceProvider {
16
+ readonly serviceId: string = PRETTY_PRINTER_SERVICE_ID;
17
+ readonly #defaultPrettyPrinterService: DefaultPrettyPrinterService;
18
+
19
+ /**
20
+ * Create an instance of the service provider with the specified details.
21
+ *
22
+ * @param servicePriority the priority of the service.
23
+ */
24
+ public constructor(
25
+ readonly servicePriority: number,
26
+ ) {
27
+ this.#defaultPrettyPrinterService = new DefaultPrettyPrinterService();
28
+ }
29
+
30
+ public provide(_cliConfig: CLIConfig): Promise<ServiceInfo> {
31
+ return Promise.resolve({
32
+ service: this.#defaultPrettyPrinterService,
33
+ commands: [],
34
+ });
35
+ }
36
+
37
+ initService(_context: Context): Promise<void> {
38
+ return Promise.resolve(undefined);
39
+ }
40
+ }
@@ -0,0 +1,27 @@
1
+ const languages = [
2
+ {
3
+ name: "foo",
4
+ parsers: ["foo"],
5
+ },
6
+ ];
7
+
8
+ const parsers = {
9
+ foo: {
10
+ astFormat: "foo",
11
+ parse: () => "",
12
+ locStart: () => 0,
13
+ locEnd: () => 0,
14
+ },
15
+ };
16
+
17
+ const printers = {
18
+ "foo": {
19
+ print: () => "foo",
20
+ },
21
+ };
22
+
23
+ export default {
24
+ languages,
25
+ parsers,
26
+ printers,
27
+ };
@@ -1224,7 +1224,7 @@ describe("runner tests", () => {
1224
1224
  expect(hasRun).toBeTrue();
1225
1225
  });
1226
1226
 
1227
- test("Group command as default command is invalid", async () => {
1227
+ test("Group command as default command is invalid", () => {
1228
1228
  const streamString = new StreamString();
1229
1229
  const groupCommand = getGroupCommand();
1230
1230
 
@@ -1232,7 +1232,7 @@ describe("runner tests", () => {
1232
1232
  return Promise.resolve();
1233
1233
  };
1234
1234
 
1235
- await expect(
1235
+ expect(
1236
1236
  run(
1237
1237
  [],
1238
1238
  new DefaultCommandRegistry([]),
@@ -34,11 +34,11 @@ describe("DefaultAsciiBannerGeneratorService tests", () => {
34
34
  ]);
35
35
  });
36
36
 
37
- test("Cannot generate with unknown font", async () => {
37
+ test("Cannot generate with unknown font", () => {
38
38
  const asciiBannerGeneratorService =
39
39
  new DefaultAsciiBannerGeneratorService();
40
40
 
41
- await expect(asciiBannerGeneratorService.generate("foo", "small")).rejects
41
+ expect(asciiBannerGeneratorService.generate("foo", "small")).rejects
42
42
  .toThrow();
43
43
  });
44
44
 
@@ -0,0 +1,53 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import DefaultPrettyPrinterService from "../../../src/service/prettyPrinter/DefaultPrettyPrinterService";
3
+ import FooSyntax from "../../fixtures/prettierPluginFoo";
4
+
5
+ describe("DefaultPrettyPrinterService tests", () => {
6
+ test("JSON registered by default", async () => {
7
+ const prettyPrinterService = new DefaultPrettyPrinterService();
8
+
9
+ expect(await prettyPrinterService.getRegisteredSyntaxes()).toContain(
10
+ "json",
11
+ );
12
+ });
13
+
14
+ test("Cannot register a syntax if already registered", () => {
15
+ const prettyPrinterService = new DefaultPrettyPrinterService();
16
+
17
+ expect(prettyPrinterService.registerSyntax("jSon", FooSyntax)).rejects
18
+ .toThrow();
19
+ });
20
+
21
+ test("Can register new syntax", async () => {
22
+ const prettyPrinterService = new DefaultPrettyPrinterService();
23
+
24
+ prettyPrinterService.registerSyntax("foo", FooSyntax);
25
+ expect(await prettyPrinterService.getRegisteredSyntaxes()).toContain("foo");
26
+ });
27
+
28
+ test("Cannot pretty print with unknown syntax", () => {
29
+ const prettyPrinterService = new DefaultPrettyPrinterService();
30
+
31
+ expect(() => prettyPrinterService.prettify("foo: 1", "bar"))
32
+ .toThrow();
33
+ });
34
+
35
+ test("Can pretty print with JSON syntax", async () => {
36
+ const prettyPrinterService = new DefaultPrettyPrinterService();
37
+
38
+ const prettified = await prettyPrinterService.prettify(
39
+ "{foo:1}",
40
+ "json",
41
+ );
42
+ expect(prettified).toEqual('{ "foo": 1 }\n');
43
+ });
44
+
45
+ test("Can pretty print with newly registered syntax", async () => {
46
+ const prettyPrinterService = new DefaultPrettyPrinterService();
47
+
48
+ prettyPrinterService.registerSyntax("foo", FooSyntax);
49
+
50
+ const prettified = await prettyPrinterService.prettify("foo: 1", "foo");
51
+ expect(prettified).toEqual("foo");
52
+ });
53
+ });
@@ -0,0 +1,40 @@
1
+ import { describe, expect, test } from "bun:test";
2
+ import DefaultContext from "../../../src/runtime/DefaultContext.ts";
3
+ import { getCLIConfig } from "../../fixtures/CLIConfig.ts";
4
+ import PrettyPrinterServiceProvider from "../../../src/service/prettyPrinter/PrettyPrinterServiceProvider.ts";
5
+ import { PRINTER_SERVICE_ID } from "../../../src/api/service/core/PrinterService.ts";
6
+ import DefaultPrinterService from "../../../src/service/printer/DefaultPrinterService.ts";
7
+ import StreamString from "../../fixtures/StreamString.ts";
8
+ import TtyTerminal from "../../../src/service/printer/terminal/TtyTerminal.ts";
9
+ import TtyStyler from "../../../src/service/printer/terminal/TtyStyler.ts";
10
+
11
+ describe("PrettyPrinterServiceProvider tests", () => {
12
+ test("PrettyPrinterServiceProvider provide and initService works", async () => {
13
+ const dummyStdout = new StreamString();
14
+ const dummyStderr = new StreamString();
15
+ const prettyPrinterServiceProvider = new PrettyPrinterServiceProvider(
16
+ 100,
17
+ );
18
+ const cliConfig = getCLIConfig();
19
+ const context = new DefaultContext(cliConfig);
20
+
21
+ context.addServiceInstance(
22
+ PRINTER_SERVICE_ID,
23
+ new DefaultPrinterService(
24
+ dummyStdout.writableStream,
25
+ dummyStderr.writableStream,
26
+ true,
27
+ true,
28
+ new TtyTerminal(dummyStderr.writeStream),
29
+ new TtyStyler(3),
30
+ ),
31
+ );
32
+
33
+ const serviceInfo = await prettyPrinterServiceProvider.provide(
34
+ cliConfig,
35
+ );
36
+ expect(serviceInfo.commands.length).toEqual(0);
37
+
38
+ await prettyPrinterServiceProvider.initService(context);
39
+ });
40
+ });