Skip to content

Commit ab46f46

Browse files
feat(cli-vector): initialise cli-vector docs command
1 parent 8cbef0b commit ab46f46

File tree

3 files changed

+205
-2
lines changed

3 files changed

+205
-2
lines changed
Lines changed: 150 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,150 @@
1+
import { fsa, Url, UrlFolder } from '@basemaps/shared';
2+
import { CliInfo } from '@basemaps/shared/build/cli/info.js';
3+
import { getLogger, logArguments } from '@basemaps/shared/build/cli/log.js';
4+
import { command, option } from 'cmd-ts';
5+
import { readFileSync } from 'fs';
6+
import Mustache from 'mustache';
7+
import { z } from 'zod';
8+
9+
import { zSchema } from '../schema-loader/parser.js';
10+
import { Schema } from '../schema-loader/schema.js';
11+
12+
interface Property {
13+
name: string;
14+
type: string;
15+
description: string;
16+
}
17+
18+
interface Feature {
19+
name: string;
20+
kind: string;
21+
geometry: string;
22+
minZoom: number;
23+
maxZoom: number;
24+
}
25+
26+
interface Layer {
27+
name: string;
28+
description: string;
29+
properties: Property[];
30+
features: Feature[];
31+
}
32+
33+
function pathToURLFolder(path: string): URL {
34+
const url = fsa.toUrl(path);
35+
url.search = '';
36+
url.hash = '';
37+
if (!url.pathname.endsWith('/')) url.pathname += '/';
38+
return url;
39+
}
40+
41+
export const DocsArgs = {
42+
...logArguments,
43+
schema: option({
44+
type: UrlFolder,
45+
long: 'schema',
46+
defaultValue: () => pathToURLFolder('schema'),
47+
description: 'Path to the directory containing the schema files from which to generate markdown docs',
48+
}),
49+
template: option({
50+
type: Url,
51+
long: 'template',
52+
description: 'Template file',
53+
}),
54+
target: option({
55+
type: Url,
56+
long: 'target',
57+
description: 'Target location for the result file',
58+
}),
59+
};
60+
61+
export const DocsCommand = command({
62+
name: 'docs',
63+
version: CliInfo.version,
64+
description: 'Generate markdown docs from a directory of schema files.',
65+
args: DocsArgs,
66+
async handler(args) {
67+
const logger = getLogger(this, args, 'cli-vector');
68+
logger.info('GenerateMarkdownDocs: Start');
69+
70+
// parse schema files
71+
const schemas: Schema[] = [];
72+
const files = await fsa.toArray(fsa.list(args.schema));
73+
74+
for (const file of files) {
75+
if (file.href.endsWith('.json')) {
76+
const json = await fsa.readJson(file);
77+
// Validate the json
78+
try {
79+
const parsed = zSchema.parse(json);
80+
schemas.push(parsed);
81+
} catch (e) {
82+
if (e instanceof z.ZodError) {
83+
throw new Error(`Schema ${file.href} is invalid: ${e.message}`);
84+
}
85+
}
86+
}
87+
}
88+
89+
const layers: Layer[] = [];
90+
91+
for (const schema of schemas) {
92+
const properties = schema.metadata.attributes.map((attribute) => ({
93+
name: attribute,
94+
type: 'TBC',
95+
description: 'TBC',
96+
}));
97+
98+
const features = schema.layers.reduce(
99+
(obj, layer) => {
100+
const kind = layer.tags['kind'];
101+
if (typeof kind !== 'string') return obj;
102+
103+
// const zoom =
104+
// layer.style.minZoom === layer.style.maxZoom
105+
// ? layer.style.minZoom.toString()
106+
// : `${layer.style.minZoom}-${layer.style.maxZoom}`;
107+
108+
const entry = obj[kind];
109+
110+
if (entry == null) {
111+
return {
112+
...obj,
113+
[kind]: {
114+
name: kind,
115+
kind,
116+
geometry: 'TBC',
117+
minZoom: layer.style.minZoom,
118+
maxZoom: layer.style.maxZoom,
119+
} as Feature,
120+
};
121+
}
122+
123+
if (layer.style.minZoom < entry.minZoom) {
124+
entry.minZoom = layer.style.minZoom;
125+
}
126+
127+
if (layer.style.maxZoom > entry.maxZoom) {
128+
entry.maxZoom = layer.style.maxZoom;
129+
}
130+
131+
return obj;
132+
},
133+
{} as { [key: string]: Feature },
134+
);
135+
136+
layers.push({
137+
name: schema.name,
138+
description: 'TBC',
139+
properties,
140+
features: Object.values(features),
141+
});
142+
}
143+
144+
const template = readFileSync(args.template).toString();
145+
const output = Mustache.render(template, { layers });
146+
await fsa.write(new URL('result.md', args.target), output);
147+
148+
logger.info('GenerateMarkdownDocs: End');
149+
},
150+
});

packages/cli-vector/src/index.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,8 @@
1-
// eslint-disable-next-line simple-import-sort/imports
21
import { subcommands } from 'cmd-ts';
3-
import { ExtractCommand } from './cli/cli.extract.js';
2+
43
import { CreateCommand } from './cli/cli.create.js';
4+
import { DocsCommand } from './cli/cli.docs.js';
5+
import { ExtractCommand } from './cli/cli.extract.js';
56
import { JoinCommand } from './cli/cli.join.js';
67

78
export const VectorCli = subcommands({
@@ -10,5 +11,6 @@ export const VectorCli = subcommands({
1011
extract: ExtractCommand,
1112
create: CreateCommand,
1213
join: JoinCommand,
14+
docs: DocsCommand,
1315
},
1416
});
Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
{{#layers}}
2+
3+
## Layer "{{name}}"
4+
5+
{{description}}
6+
7+
### Properties
8+
9+
<table>
10+
<thead>
11+
<tr>
12+
<th>Name</th>
13+
<th>Type</th>
14+
<th>Description</th>
15+
</tr>
16+
</thead>
17+
<tbody>
18+
{{#properties}}
19+
<tr>
20+
<td>{{name}}</td>
21+
<td>{{type}}</td>
22+
<td>{{description}}</td>
23+
</tr>
24+
{{/properties}}
25+
</tbody>
26+
</table>
27+
28+
### Features
29+
30+
<table>
31+
<thead>
32+
<tr>
33+
<th>Name</th>
34+
<th>`kind`</th>
35+
<th>Geometry</th>
36+
<th>Zoom</th>
37+
</tr>
38+
</thead>
39+
<tbody>
40+
{{#features}}
41+
<tr>
42+
<td>{{name}}</td>
43+
<td>{{kind}}</td>
44+
<td>{{geometry}}</td>
45+
<td>{{zoom}}</td>
46+
</tr>
47+
{{/features}}
48+
</tbody>
49+
</table>
50+
51+
{{/layers}}

0 commit comments

Comments
 (0)