@p-buddy/parkdown 0.0.2 → 0.0.3
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/dist/cli.js +1 -1
- package/package.json +9 -1
- package/.assets/api-note.md +0 -3
- package/.assets/api.md +0 -34
- package/.assets/authoring.md +0 -69
- package/.assets/code/depopulate.ts +0 -6
- package/.assets/code/inclusions.ts +0 -6
- package/.assets/depopulated.md +0 -25
- package/.assets/invocation.md +0 -16
- package/.assets/populated/block.md +0 -9
- package/.assets/populated/inline.multi.md +0 -5
- package/.assets/populated/inline.single.md +0 -3
- package/.assets/query.md +0 -73
- package/.assets/remap-imports.md +0 -0
- package/.assets/unpopulated/block.md +0 -5
- package/.assets/unpopulated/inline.multi.md +0 -3
- package/.assets/unpopulated/inline.single.md +0 -1
- package/.devcontainer/Dockerfile +0 -16
- package/.devcontainer/devcontainer.json +0 -35
- package/src/api/index.test.ts +0 -32
- package/src/api/index.ts +0 -8
- package/src/api/types.ts +0 -78
- package/src/api/utils.test.ts +0 -132
- package/src/api/utils.ts +0 -161
- package/src/cli.ts +0 -31
- package/src/include.test.ts +0 -369
- package/src/include.ts +0 -252
- package/src/index.ts +0 -35
- package/src/region.test.ts +0 -145
- package/src/region.ts +0 -138
- package/src/remap.test.ts +0 -37
- package/src/remap.ts +0 -72
- package/src/utils.test.ts +0 -238
- package/src/utils.ts +0 -184
- package/src/wrap.ts +0 -61
- package/tsconfig.json +0 -5
- package/vite.cli.config.ts +0 -23
- package/vite.config.ts +0 -20
package/dist/cli.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import { Command as t } from "@commander-js/extra-typings";
|
|
3
3
|
import { populateMarkdownInclusions as r, depopulateMarkdownInclusions as l } from "@p-buddy/parkdown";
|
|
4
|
-
const p = "0.0.
|
|
4
|
+
const p = "0.0.3", f = new t().version(p).option("--nw, --no-write", "Do NOT write result to file (defaults to false)", !1).option("--ni, --no-inclusions", "Do NOT process file inclusions (defaults to false)", !1).option("-d, --depopulate", "Remove populated inclusions from the file", !1).option("-f, --file <flag>", "The file(s) to process", (e, o) => (o.push(e), o), new Array()).option("-r, --remap-imports", "Remap import specifiers in code blocks from one destination to another").parse(), { inclusions: a, depopulate: c, file: n, write: i } = f.opts();
|
|
5
5
|
n.length === 0 && n.push("README.md");
|
|
6
6
|
const u = [
|
|
7
7
|
[r, !a],
|
package/package.json
CHANGED
|
@@ -2,9 +2,17 @@
|
|
|
2
2
|
"name": "@p-buddy/parkdown",
|
|
3
3
|
"type": "module",
|
|
4
4
|
"private": false,
|
|
5
|
-
"version": "0.0.
|
|
5
|
+
"version": "0.0.3",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": "dist/cli.js",
|
|
8
|
+
"files": [
|
|
9
|
+
"dist",
|
|
10
|
+
"README.md",
|
|
11
|
+
"LICENSE"
|
|
12
|
+
],
|
|
13
|
+
"publishConfig": {
|
|
14
|
+
"access": "public"
|
|
15
|
+
},
|
|
8
16
|
"devDependencies": {
|
|
9
17
|
"@types/node": "^22.13.10",
|
|
10
18
|
"@types/unist": "^3.0.3",
|
package/.assets/api-note.md
DELETED
|
@@ -1,3 +0,0 @@
|
|
|
1
|
-
**_NOTE ON API USAGE:_** As you can see from the included examples, each _invocation_ of an API method looks like a less strict (more quirky) version of a typical javascript function invocation.
|
|
2
|
-
|
|
3
|
-
Please see the [full explanation](#query-parameters-with-function-like-apis) to learn more and/or if the below is confusing.
|
package/.assets/api.md
DELETED
|
@@ -1,34 +0,0 @@
|
|
|
1
|
-
## Query Parameters with Function-like APIs
|
|
2
|
-
|
|
3
|
-
Some query parameters have more complex APIs, which are defined by a collection of typescript function singatures (limited to only `string`, `boolean`, and `number` arguments), like:
|
|
4
|
-
|
|
5
|
-
```ts
|
|
6
|
-
const definitions = [
|
|
7
|
-
"method(arg1: string, arg2: boolean, arg3?: number)",
|
|
8
|
-
"otherMethod(arg1: string, arg2?: boolean, arg3?: number)"
|
|
9
|
-
]
|
|
10
|
-
```
|
|
11
|
-
|
|
12
|
-
These APIs are utilized when setting the query parameter with a _function-like_ invocation syntax, such as:
|
|
13
|
-
|
|
14
|
-
```md
|
|
15
|
-
[](<url>?example=method(hello-world,true))
|
|
16
|
-
```
|
|
17
|
-
|
|
18
|
-
As you can maybe tell from the example above, we're relaxing some constraints on typical function invocations (like the need to wrap string arguments in quotes), while also imposing some additional constraints (like not using spaces) to ensure the links are valid markdown and the URLs are [safe](https://support.exactonline.com/community/s/knowledge-base?language=en_GB#All-All-DNO-Content-urlcharacters).
|
|
19
|
-
|
|
20
|
-
The goal is to make it as painless as possible to author links that are valid markdown, valid URLs, and easy to read and write.
|
|
21
|
-
|
|
22
|
-
Please note the following:
|
|
23
|
-
|
|
24
|
-
- Methods that take no arguments can be invoked without parentheses (e.g. `[](<url>?example=method)`).
|
|
25
|
-
- String arguments do not need to be wrapped in quotes (e.g. `[](<url>?example=method(some-string))`), and they should **NOT** be wrapped in double quotes (see more below).
|
|
26
|
-
- You cannot use spaces within a string argument or anywhere else in the query (as this would violate the markdown link syntax). For arguments that reasonably could include spaces, there should be an optional `space` argument that defaults to `-`, so that any usage of the space character will be converted to a space (e.g. `hello-world` becomes `hello world`).
|
|
27
|
-
- If a method takes a string argument, and you want to include a comma within that argument, you must wrap it in one or more single quotes (`'`).
|
|
28
|
-
- String arguments wrapped in single set of single quotes will automatically have their single quotes removed when the query is parsed (e.g. the argument included in `[](<url>?example=method('hello,world'))` will parse to `hello,world`).
|
|
29
|
-
- If you want single quotes preserved in the parsed output, use two single quotes in a row (e.g. `[](<url>?example=method(''single-quoted''))`).
|
|
30
|
-
- You cannot use double quotes within a string argument (as they are not a [URL safe character](https://support.exactonline.com/community/s/knowledge-base#All-All-DNO-Content-urlcharacters)). To include a double-quote in the parsed output, use three single quotes in a row (e.g. `[](<url>?example=method'''double-quoted'''))`).
|
|
31
|
-
- Optional arguments can be completely ommitted (for example if a `method` took 3 optional arguments, and you only wanted to provide the third, you could do the following: `[](<url>?example=method(,,your-third-argument))`).
|
|
32
|
-
- Overall, text meant to be displayed to a human will be _sanitized_ in the following manner (unless otherwise noted):
|
|
33
|
-
|
|
34
|
-
[](../src/utils.ts?region=extract(sanitize))
|
package/.assets/authoring.md
DELETED
|
@@ -1,69 +0,0 @@
|
|
|
1
|
-
# Authoring
|
|
2
|
-
|
|
3
|
-
You author inclusions in your markdown files using a link with no text i.e. `[](<url>)`, where `<url>` points to some local or remote text resource (e.g.`./other.md`, `https://example.com/remote.md`).
|
|
4
|
-
|
|
5
|
-
These links can be rendered either [inline](#inline) or [block](#block), depending on how you author them.
|
|
6
|
-
|
|
7
|
-
## Inline
|
|
8
|
-
|
|
9
|
-
Inline inclusions occur when your _text-less_ link has 1 or more siblings (meaning it's **not** the only node in a [paragraph](https://www.markdownguide.org/basic-syntax/#paragraphs-1)).
|
|
10
|
-
|
|
11
|
-
There are two equivalent ways to author inline inclusions, [single-line](#single-line) or [multi-line](#multi-line), and which you choose depends solely on how you want your raw markdown to look (it will **not** affect the rendered output).
|
|
12
|
-
|
|
13
|
-
### Single-line
|
|
14
|
-
|
|
15
|
-
What you write:
|
|
16
|
-
|
|
17
|
-
[](./unpopulated/inline.single.md?wrap=code)
|
|
18
|
-
|
|
19
|
-
What is rendered (**_before_** processing, same as [Option B](#option-b-multi-line)):
|
|
20
|
-
|
|
21
|
-
[](./unpopulated/inline.single.md?wrap=quote&inline)
|
|
22
|
-
|
|
23
|
-
What your markdown file contains (**_after_** processing):
|
|
24
|
-
|
|
25
|
-
[](./populated/inline.single.md?wrap=code)
|
|
26
|
-
|
|
27
|
-
What is rendered (**_after_** processing, same as [Option B](#option-b-multi-line)):
|
|
28
|
-
|
|
29
|
-
[](./populated/inline.single.md?wrap=quote&inline)
|
|
30
|
-
|
|
31
|
-
### Multi-line
|
|
32
|
-
|
|
33
|
-
What you write:
|
|
34
|
-
|
|
35
|
-
[](./unpopulated/inline.multi.md?wrap=code)
|
|
36
|
-
|
|
37
|
-
What is rendered (**_before_** processing, same as [Option A](#option-a-single-line)):
|
|
38
|
-
|
|
39
|
-
[](./unpopulated/inline.multi.md?wrap=quote&inline)
|
|
40
|
-
|
|
41
|
-
What your markdown file contains (**_after_** processing):
|
|
42
|
-
|
|
43
|
-
[](./populated/inline.multi.md?wrap=code)
|
|
44
|
-
|
|
45
|
-
What is rendered (**_after_** processing, same as [Option A](#option-a-single-line)):
|
|
46
|
-
|
|
47
|
-
[](./populated/inline.multi.md?wrap=quote&inline)
|
|
48
|
-
|
|
49
|
-
## Block
|
|
50
|
-
|
|
51
|
-
Block inclusions occur when your "empty" link is the **only** node in a [paragraph](https://www.markdownguide.org/basic-syntax/#paragraphs-1) (at least before being populated). This is likely the most common way to author inclusions.
|
|
52
|
-
|
|
53
|
-
What you write:
|
|
54
|
-
|
|
55
|
-
[](./unpopulated/block.md?wrap=code)
|
|
56
|
-
|
|
57
|
-
What is rendered (**_before_** processing):
|
|
58
|
-
|
|
59
|
-
[](./unpopulated/block.md?wrap=quote)
|
|
60
|
-
|
|
61
|
-
What your markdown file contains (**_after_** processing):
|
|
62
|
-
|
|
63
|
-
[](./populated/block.md?wrap=code)
|
|
64
|
-
|
|
65
|
-
What is rendered (**_after_** processing):
|
|
66
|
-
|
|
67
|
-
[](./populated/block.md?wrap=quote)
|
|
68
|
-
|
|
69
|
-
[](./query.md?heading=-1)
|
package/.assets/depopulated.md
DELETED
|
@@ -1,25 +0,0 @@
|
|
|
1
|
-
# Removing populated inclusions
|
|
2
|
-
|
|
3
|
-
Sometimes you may want to remove populated inclusions from your markdown file, since they can make things more difficult to read during authoring. You can do this either using the [cli](#cli-removing-populated-inclusions) or via the `removePopulatedInclusions` [export](#depopulateMarkdownIncludes-export):
|
|
4
|
-
|
|
5
|
-
## CLI (removing populated inclusions)
|
|
6
|
-
|
|
7
|
-
The following commands are all equivalent:
|
|
8
|
-
|
|
9
|
-
```bash
|
|
10
|
-
npx parkdown --file ./README.md --depopulate --no-inclusions
|
|
11
|
-
npx parkdown -f README.md -d --ni # Notice the double-dash (--) on 'ni'
|
|
12
|
-
npx parkdown -d --ni # defaults to processing the 'README.md' file of the current working directory
|
|
13
|
-
```
|
|
14
|
-
|
|
15
|
-
The following commands will lead to the same result, but since `--no-inclusions` (`--ni`) is not specified, there will be some wasted work as inclusions will be processed and then removed.
|
|
16
|
-
|
|
17
|
-
```bash
|
|
18
|
-
npx parkdown --file ./README.md --depopulate
|
|
19
|
-
npx parkdown -f README.md -d
|
|
20
|
-
npx parkdown -d # defaults to processing the 'README.md' file of the current working directory
|
|
21
|
-
```
|
|
22
|
-
|
|
23
|
-
## `depopulateMarkdownIncludes` export
|
|
24
|
-
|
|
25
|
-
[](./code/depopulate.ts?region=replace(pkg,'''parkdown'''))
|
package/.assets/invocation.md
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# Invocation
|
|
2
|
-
|
|
3
|
-
Invoke [parkdown's]() functionality with either the [cli](#cli-inclusions) or via the `processMarkdownIncludes` [export](#populateMarkdownIncludes-export):
|
|
4
|
-
|
|
5
|
-
## CLI
|
|
6
|
-
|
|
7
|
-
The following commands are all equivalent:
|
|
8
|
-
```bash
|
|
9
|
-
npx parkdown --file ./README.md
|
|
10
|
-
npx parkdown -f README.md
|
|
11
|
-
npx parkdown # defaults to processing inclusions in the 'README.md' file of the current working directory
|
|
12
|
-
```
|
|
13
|
-
|
|
14
|
-
## `populateMarkdownIncludes` export
|
|
15
|
-
|
|
16
|
-
[](./code/inclusions.ts?region=replace(pkg,'''parkdown'''))
|
package/.assets/query.md
DELETED
|
@@ -1,73 +0,0 @@
|
|
|
1
|
-
# Query parameters
|
|
2
|
-
|
|
3
|
-
You can pass query parameters to your inclusion links to control how their content is processed and included within your markdown.
|
|
4
|
-
|
|
5
|
-
## Processing Order
|
|
6
|
-
|
|
7
|
-
[](../src/include.ts?®ion=extract(query))
|
|
8
|
-
|
|
9
|
-
## `region`
|
|
10
|
-
|
|
11
|
-
Either extract, remove, or replace content from the included file based on the provided specifier(s).
|
|
12
|
-
|
|
13
|
-
Specifiers will be searched for within the file's comments, and are expected to come in pairs / bookend the desired region, like so:
|
|
14
|
-
|
|
15
|
-
```ts
|
|
16
|
-
/** some-specifier */
|
|
17
|
-
... code to find ...
|
|
18
|
-
/** some-specifier */
|
|
19
|
-
```
|
|
20
|
-
|
|
21
|
-
```md
|
|
22
|
-
[](...?region=extract(some-specifier))
|
|
23
|
-
```
|
|
24
|
-
|
|
25
|
-
Below is the currently supported API for the `region` query parameter, where each defined method signature can be _invoked_ as a value for the `region` parameter (e.g. `[](<url>?region=extract(some-specifier))`, `[](<url>?region=remove(some-specifier))`, `[](<url>?region=replace(some-specifier))`).
|
|
26
|
-
|
|
27
|
-
[](./api-note.md?wrap=quote)
|
|
28
|
-
|
|
29
|
-
[](../src/region.ts?region=extract(definition))
|
|
30
|
-
|
|
31
|
-
## `skip`
|
|
32
|
-
|
|
33
|
-
Skip the default processing behavior for the given type of file.
|
|
34
|
-
|
|
35
|
-
[](../src/include.ts?wrap=dropdown(See-default-processing-behavior.)®ion=extract(Default-Behavior),replace(...))
|
|
36
|
-
|
|
37
|
-
```md
|
|
38
|
-
[](<url>?skip)
|
|
39
|
-
```
|
|
40
|
-
|
|
41
|
-
## `heading`
|
|
42
|
-
|
|
43
|
-
Modify the heading depth applied to included content. By default, the headings of the included content are adjusted to be one-level below their parent heading.
|
|
44
|
-
|
|
45
|
-
In the following example, the headings within the included content of `<url>` will be adjusted to one-level below the parent heading (which is an `h2` / `##`), so any `#` headings will be converted to `###` headings, and `##` headings will be converted to `####` headings, and so on.
|
|
46
|
-
|
|
47
|
-
```md
|
|
48
|
-
## Heading
|
|
49
|
-
|
|
50
|
-
[](<url>)
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
The following would then ensure that the headings of the included content are at the same level as the parent heading.
|
|
54
|
-
|
|
55
|
-
```md
|
|
56
|
-
## Heading
|
|
57
|
-
|
|
58
|
-
[](<url>?heading=-1)
|
|
59
|
-
```
|
|
60
|
-
|
|
61
|
-
A value of `-2` would result in the headings of the included content being at their original level (since the content is being included underneath an `h2` / `##` heading).
|
|
62
|
-
|
|
63
|
-
## `inline` (Advanced)
|
|
64
|
-
|
|
65
|
-
## `wrap`
|
|
66
|
-
|
|
67
|
-
Wrap the content of the included file in a specific kind of element.
|
|
68
|
-
|
|
69
|
-
Below is the currently supported API for the `wrap` query parameter, where each defined method signature can be _invoked_ as a value for the `wrap` parameter (e.g. `[](<url>?wrap=code)`, `[](<url>?wrap=dropdown(hello-world))`).
|
|
70
|
-
|
|
71
|
-
[](./api-note.md?wrap=quote)
|
|
72
|
-
|
|
73
|
-
[](../src/wrap.ts?region=extract(definition))
|
package/.assets/remap-imports.md
DELETED
|
File without changes
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
Before... [](<url>) ...After
|
package/.devcontainer/Dockerfile
DELETED
|
@@ -1,16 +0,0 @@
|
|
|
1
|
-
# See here for image contents: https://github.com/microsoft/vscode-dev-containers/tree/v0.187.0/containers/typescript-node/.devcontainer/base.Dockerfile
|
|
2
|
-
|
|
3
|
-
# [Choice] Node.js version: 16, 18, 20
|
|
4
|
-
ARG VARIANT="20-buster"
|
|
5
|
-
FROM mcr.microsoft.com/vscode/devcontainers/typescript-node:0-${VARIANT}
|
|
6
|
-
|
|
7
|
-
# [Optional] Uncomment this section to install additional OS packages.
|
|
8
|
-
# RUN apt-get update && export DEBIAN_FRONTEND=noninteractive \
|
|
9
|
-
# && apt-get -y install --no-install-recommends <your-package-list-here>
|
|
10
|
-
|
|
11
|
-
# [Optional] Uncomment if you want to install an additional version of node using nvm
|
|
12
|
-
# ARG EXTRA_NODE_VERSION=10
|
|
13
|
-
# RUN su node -c "source /usr/local/share/nvm/nvm.sh && nvm install ${EXTRA_NODE_VERSION}"
|
|
14
|
-
|
|
15
|
-
# To install more global node packages
|
|
16
|
-
RUN su node -c "npm install -g pnpm@10"
|
|
@@ -1,35 +0,0 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "parkdown-dev",
|
|
3
|
-
"dockerFile": "Dockerfile",
|
|
4
|
-
"customizations": {
|
|
5
|
-
"vscode": {
|
|
6
|
-
"extensions": [
|
|
7
|
-
"ms-vscode.vscode-typescript-next",
|
|
8
|
-
"svelte.svelte-vscode",
|
|
9
|
-
"ms-azuretools.vscode-docker",
|
|
10
|
-
"bradlc.vscode-tailwindcss",
|
|
11
|
-
"YoavBls.pretty-ts-errors"
|
|
12
|
-
]
|
|
13
|
-
}
|
|
14
|
-
},
|
|
15
|
-
"postStartCommand": [
|
|
16
|
-
"bash",
|
|
17
|
-
"-c",
|
|
18
|
-
"git config --global user.name \"${GIT_USER_NAME}\"",
|
|
19
|
-
"bash",
|
|
20
|
-
"-c",
|
|
21
|
-
"git config --global user.email \"${GIT_USER_EMAIL}\"",
|
|
22
|
-
"bash",
|
|
23
|
-
"-c",
|
|
24
|
-
"pnpm install"
|
|
25
|
-
],
|
|
26
|
-
"mounts": [
|
|
27
|
-
"source=${localEnv:HOME}/.ssh,target=/home/node/.ssh,type=bind,readonly"
|
|
28
|
-
],
|
|
29
|
-
"containerEnv": {
|
|
30
|
-
"NPM_TOKEN": "${localEnv:PARKDOWN_NPM_TOKEN}",
|
|
31
|
-
"HOST_WORKSPACE_PATH": "${localWorkspaceFolder}",
|
|
32
|
-
"GIT_USER_NAME": "${localEnv:GIT_USER_NAME}",
|
|
33
|
-
"GIT_USER_EMAIL": "${localEnv:GIT_USER_EMAIL}"
|
|
34
|
-
}
|
|
35
|
-
}
|
package/src/api/index.test.ts
DELETED
|
@@ -1,32 +0,0 @@
|
|
|
1
|
-
import { describe, expect, test } from "vitest";
|
|
2
|
-
import { MethodDefinition } from "./types";
|
|
3
|
-
import { numberedParameters, createParser } from "./index";
|
|
4
|
-
|
|
5
|
-
describe(numberedParameters.name, () => {
|
|
6
|
-
test("simple", () => {
|
|
7
|
-
const definitions = [
|
|
8
|
-
"example(0: string, 1?: string)" satisfies MethodDefinition,
|
|
9
|
-
] as const;
|
|
10
|
-
|
|
11
|
-
const result = createParser(definitions)("example(z,a)");
|
|
12
|
-
expect(numberedParameters(result)).toEqual(["z", "a"]);
|
|
13
|
-
});
|
|
14
|
-
|
|
15
|
-
test("mixed types", () => {
|
|
16
|
-
const definitions = [
|
|
17
|
-
"example(0: string, 1?: string, 2: boolean)" satisfies MethodDefinition,
|
|
18
|
-
] as const;
|
|
19
|
-
|
|
20
|
-
const result = createParser(definitions)("example(z,a,true)");
|
|
21
|
-
expect(numberedParameters(result)).toEqual(["z", "a", true]);
|
|
22
|
-
});
|
|
23
|
-
|
|
24
|
-
test("mixed positions", () => {
|
|
25
|
-
const definitions = [
|
|
26
|
-
"example(0: string, hi: string, 1: boolean)" satisfies MethodDefinition,
|
|
27
|
-
] as const;
|
|
28
|
-
|
|
29
|
-
const result = createParser(definitions)("example(z,hi,true)");
|
|
30
|
-
expect(numberedParameters(result)).toEqual(["z", true]);
|
|
31
|
-
});
|
|
32
|
-
});
|
package/src/api/index.ts
DELETED
|
@@ -1,8 +0,0 @@
|
|
|
1
|
-
export type { MethodDefinition } from "./types";
|
|
2
|
-
import type { Parser } from "./types";
|
|
3
|
-
export { createParser } from "./utils";
|
|
4
|
-
|
|
5
|
-
export const numberedParameters = (result: ReturnType<Parser<any>>) =>
|
|
6
|
-
Object.entries(result)
|
|
7
|
-
.filter(([key]) => key !== "name" && !isNaN(Number(key)))
|
|
8
|
-
.map(([_, value]) => value);
|
package/src/api/types.ts
DELETED
|
@@ -1,78 +0,0 @@
|
|
|
1
|
-
export type TypeRecord = {
|
|
2
|
-
"string": string,
|
|
3
|
-
"number": number,
|
|
4
|
-
"boolean": boolean,
|
|
5
|
-
}
|
|
6
|
-
|
|
7
|
-
type ExpandRecursively<T> =
|
|
8
|
-
T extends (...args: infer A) => infer R
|
|
9
|
-
/**/ ? (...args: ExpandRecursively<A>) => ExpandRecursively<R>
|
|
10
|
-
/**/ : T extends object
|
|
11
|
-
/**/ ? T extends infer O
|
|
12
|
-
/**/ ? { [K in keyof O]: ExpandRecursively<O[K]> }
|
|
13
|
-
/**/ : never
|
|
14
|
-
/**/ : T;
|
|
15
|
-
|
|
16
|
-
type WithoutLeadingWhitespace<T extends string> = T extends ` ${infer Without}` ? Without : T;
|
|
17
|
-
|
|
18
|
-
type SplitOn<T extends string, Delimeter extends string> =
|
|
19
|
-
T extends `${infer Left}${Delimeter}${infer Right}` ? [Left, ...SplitOn<Right, Delimeter>] : [T];
|
|
20
|
-
|
|
21
|
-
type RepeatInTuple<Element, Count extends number, Acc extends unknown[] = []> = {
|
|
22
|
-
[k in Count]: Acc['length'] extends k
|
|
23
|
-
? Acc
|
|
24
|
-
: RepeatInTuple<Element, k, [...Acc, Element]>;
|
|
25
|
-
}[Count];
|
|
26
|
-
|
|
27
|
-
type Join<T extends string[], D extends string> =
|
|
28
|
-
T extends []
|
|
29
|
-
/**/ ? ''
|
|
30
|
-
/**/ : T extends [infer F extends string]
|
|
31
|
-
/**/ ? F
|
|
32
|
-
/**/ : T extends [infer F extends string, ...infer R extends string[]]
|
|
33
|
-
/**/ ? `${F}${D}${Join<R, D>}`
|
|
34
|
-
/**/ : string;
|
|
35
|
-
|
|
36
|
-
type Capture<T extends string, Start extends string, End extends string> = {
|
|
37
|
-
[k in T]: k extends `${infer Before}${Start}${infer Captured}${End}${infer After}`
|
|
38
|
-
/**/ ? { before: Before, captured: Captured, after: After }
|
|
39
|
-
/**/ : { before: T, captured: "", after: "" };
|
|
40
|
-
}[T]
|
|
41
|
-
|
|
42
|
-
type SplitOnComma<T extends string> = { [k in SplitOn<T, ",">[number]]: k }[SplitOn<T, ",">[number]];
|
|
43
|
-
|
|
44
|
-
type Parameter<Name extends string, Optional extends boolean, Type extends TypeRecord[keyof TypeRecord]> = { name: Name, optional: Optional, type: Type };
|
|
45
|
-
|
|
46
|
-
type ExtractParameter<T extends string> = T extends `${infer Name}?: ${infer Type extends keyof TypeRecord}`
|
|
47
|
-
/**/ ? Parameter<Name, true, Type>
|
|
48
|
-
/**/ : T extends `${infer Name}: ${infer Type extends keyof TypeRecord}`
|
|
49
|
-
/**/ ? Parameter<Name, false, Type>
|
|
50
|
-
/**/ : never;
|
|
51
|
-
|
|
52
|
-
type FunctionName<T extends string> = Capture<T, "(", ")">["before"];
|
|
53
|
-
|
|
54
|
-
type ParameterRecord<T extends string> = {
|
|
55
|
-
[k in WithoutLeadingWhitespace<SplitOnComma<Capture<T, "(", ")">["captured"]>> as ExtractParameter<k>["name"]]: ExtractParameter<k>["optional"] extends true ? TypeRecord[ExtractParameter<k>["type"]] | undefined : TypeRecord[ExtractParameter<k>["type"]]
|
|
56
|
-
}
|
|
57
|
-
|
|
58
|
-
type UndefinedToOptional<T> = {
|
|
59
|
-
[K in keyof T as undefined extends T[K] ? K : never]?: Exclude<T[K], undefined>;
|
|
60
|
-
} & {
|
|
61
|
-
[K in keyof T as undefined extends T[K] ? never : K]: T[K];
|
|
62
|
-
};
|
|
63
|
-
|
|
64
|
-
type ParseMethodDefinition<T extends string> = { [t in T as FunctionName<t>]: UndefinedToOptional<ParameterRecord<t>> };
|
|
65
|
-
|
|
66
|
-
type ParameterDefinition = `${string}${"?" | ""}: ${keyof TypeRecord}`;
|
|
67
|
-
/** Can add more parameters by adding more numbers to the count union, but it adds a lot of complexity to the type */
|
|
68
|
-
type ZeroOrMoreParameters = Join<RepeatInTuple<ParameterDefinition, 0 | 1 | 2>, ", ">;
|
|
69
|
-
export type MethodDefinition = `${string}(${ZeroOrMoreParameters})`;
|
|
70
|
-
|
|
71
|
-
export type MethodDefinitions = string[] | readonly string[];
|
|
72
|
-
|
|
73
|
-
export type ParsingCandidates<T extends MethodDefinitions> = ExpandRecursively<{
|
|
74
|
-
[k in keyof ParseMethodDefinition<T[number]>]: ParseMethodDefinition<T[number]>[k] & { name: k }
|
|
75
|
-
}[keyof ParseMethodDefinition<T[number]>]>;
|
|
76
|
-
|
|
77
|
-
export type Parser<T extends MethodDefinitions> = (query: string) => ParsingCandidates<T>;
|
|
78
|
-
export type CreateParser = <T extends MethodDefinitions>(definitions: T) => Parser<T>;
|
package/src/api/utils.test.ts
DELETED
|
@@ -1,132 +0,0 @@
|
|
|
1
|
-
import { describe, test, expect } from "vitest";
|
|
2
|
-
import { parseInvocation, parseDefinition, createParser, splitOnUnquotedComma } from "./utils";
|
|
3
|
-
import { MethodDefinition } from "./types";
|
|
4
|
-
import { COMMA_NOT_IN_PARENTHESIS } from "../utils";
|
|
5
|
-
|
|
6
|
-
describe(parseInvocation.name, () => {
|
|
7
|
-
const testCases = [
|
|
8
|
-
["code(ts,some-meta)", { name: "code", parameters: ["ts", "some-meta"] }],
|
|
9
|
-
["code", { name: "code", parameters: [] }],
|
|
10
|
-
["code()", { name: "code", parameters: [] }],
|
|
11
|
-
["code(ts)", { name: "code", parameters: ["ts"] }],
|
|
12
|
-
["code(,some-meta)", { name: "code", parameters: [undefined, "some-meta"] }],
|
|
13
|
-
["code(,some-meta,,,)", { name: "code", parameters: [undefined, "some-meta"] }],
|
|
14
|
-
["dropdown(hello-world,true)", { name: "dropdown", parameters: ["hello-world", "true"] }],
|
|
15
|
-
["code(,,anything)", { name: "code", parameters: [undefined, undefined, "anything"] }],
|
|
16
|
-
] as const;
|
|
17
|
-
|
|
18
|
-
for (const [input, expected] of testCases) {
|
|
19
|
-
test(input, () => {
|
|
20
|
-
expect(parseInvocation(input)).toEqual(expected);
|
|
21
|
-
});
|
|
22
|
-
}
|
|
23
|
-
});
|
|
24
|
-
|
|
25
|
-
describe(parseDefinition.name, () => {
|
|
26
|
-
const testCases = [
|
|
27
|
-
["code(lang?: string, meta?: string)", {
|
|
28
|
-
name: "code",
|
|
29
|
-
parameters: [
|
|
30
|
-
{ name: "lang", optional: true, type: "string" },
|
|
31
|
-
{ name: "meta", optional: true, type: "string" }
|
|
32
|
-
]
|
|
33
|
-
}],
|
|
34
|
-
["quote", { name: "quote" }],
|
|
35
|
-
["dropdown(summary: string, open?: boolean)", {
|
|
36
|
-
name: "dropdown",
|
|
37
|
-
parameters: [
|
|
38
|
-
{ name: "summary", optional: false, type: "string" },
|
|
39
|
-
{ name: "open", optional: true, type: "boolean" }
|
|
40
|
-
]
|
|
41
|
-
}],
|
|
42
|
-
["allTypes(a: string, b: boolean, c: number, d?: string, e?: boolean, f?: number)", {
|
|
43
|
-
name: "allTypes",
|
|
44
|
-
parameters: [
|
|
45
|
-
{ name: "a", optional: false, type: "string" },
|
|
46
|
-
{ name: "b", optional: false, type: "boolean" },
|
|
47
|
-
{ name: "c", optional: false, type: "number" },
|
|
48
|
-
{ name: "d", optional: true, type: "string" },
|
|
49
|
-
{ name: "e", optional: true, type: "boolean" },
|
|
50
|
-
{ name: "f", optional: true, type: "number" },
|
|
51
|
-
]
|
|
52
|
-
}]
|
|
53
|
-
] as const;
|
|
54
|
-
|
|
55
|
-
for (const [input, expected] of testCases) {
|
|
56
|
-
test(input, () => {
|
|
57
|
-
expect(parseDefinition(input)).toEqual(expected);
|
|
58
|
-
});
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
|
|
62
|
-
describe(createParser.name, () => {
|
|
63
|
-
const definitions = [
|
|
64
|
-
"code(lang?: string, meta?: string)" satisfies MethodDefinition,
|
|
65
|
-
"quote",
|
|
66
|
-
"dropdown(summary: string, open?: boolean)" satisfies MethodDefinition,
|
|
67
|
-
] as const;
|
|
68
|
-
|
|
69
|
-
const parse = createParser(definitions);
|
|
70
|
-
|
|
71
|
-
const invocations = [
|
|
72
|
-
["code", { name: "code" }],
|
|
73
|
-
["code()", { name: "code" }],
|
|
74
|
-
["code(ts)", { name: "code", lang: "ts" }],
|
|
75
|
-
["code(ts,some-meta)", { name: "code", lang: "ts", meta: "some-meta" }],
|
|
76
|
-
["code(,some-meta)", { name: "code", meta: "some-meta" }],
|
|
77
|
-
["code(,some-meta,)", { name: "code", meta: "some-meta" }],
|
|
78
|
-
["code(,,anything)", { error: true }],
|
|
79
|
-
["quote", { name: "quote" }],
|
|
80
|
-
["quote()", { name: "quote" }],
|
|
81
|
-
["dropdown()", { error: true }],
|
|
82
|
-
["dropdown(,true)", { error: true }],
|
|
83
|
-
["dropdown(hello-world,true)", { name: "dropdown", summary: "hello-world", open: true }],
|
|
84
|
-
["dropdown('hello world',true)", { name: "dropdown", summary: "hello world", open: true }],
|
|
85
|
-
|
|
86
|
-
] as const;
|
|
87
|
-
|
|
88
|
-
for (const [input, expected] of invocations) {
|
|
89
|
-
test(input, () => {
|
|
90
|
-
if ('error' in expected) {
|
|
91
|
-
expect(() => parse(input)).toThrow();
|
|
92
|
-
} else {
|
|
93
|
-
expect(parse(input)).toEqual(expected);
|
|
94
|
-
}
|
|
95
|
-
});
|
|
96
|
-
}
|
|
97
|
-
|
|
98
|
-
});
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
describe("split on non-parenthesized comma", () => {
|
|
102
|
-
const testCases = [
|
|
103
|
-
["hello,world", ["hello", "world"]],
|
|
104
|
-
["code(,,anything), code, quote, dropdown(hello-world,true)", ["code(,,anything)", "code", "quote", "dropdown(hello-world,true)"]],
|
|
105
|
-
] as const;
|
|
106
|
-
|
|
107
|
-
for (const [input, expected] of testCases) {
|
|
108
|
-
test(input, () => {
|
|
109
|
-
expect(input.split(COMMA_NOT_IN_PARENTHESIS)).toEqual(expected);
|
|
110
|
-
});
|
|
111
|
-
}
|
|
112
|
-
|
|
113
|
-
});
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
describe(splitOnUnquotedComma.name, () => {
|
|
117
|
-
test("simple", () => {
|
|
118
|
-
expect(splitOnUnquotedComma("hello,world")).toEqual(["hello", "world"]);
|
|
119
|
-
});
|
|
120
|
-
|
|
121
|
-
test("single quotes", () => {
|
|
122
|
-
expect(splitOnUnquotedComma("hello,'hello,world',hello")).toEqual(["hello", "hello,world", "hello"]);
|
|
123
|
-
});
|
|
124
|
-
|
|
125
|
-
test("preserve dual single quotes", () => {
|
|
126
|
-
expect(splitOnUnquotedComma("hello,''hello,world'',hello")).toEqual(["hello", "''hello,world''", "hello"]);
|
|
127
|
-
});
|
|
128
|
-
|
|
129
|
-
test("preserve triple double quotes", () => {
|
|
130
|
-
expect(splitOnUnquotedComma("hello,'''hello,world''',hello")).toEqual(["hello", "'''hello,world'''", "hello"]);
|
|
131
|
-
});
|
|
132
|
-
})
|