-
-
Notifications
You must be signed in to change notification settings - Fork 435
/
Copy pathexamples-service-impl.ts
225 lines (215 loc) · 7.21 KB
/
examples-service-impl.ts
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
import {
inject,
injectable,
postConstruct,
} from '@theia/core/shared/inversify';
import { join } from 'node:path';
import fs from 'node:fs';
import { FileUri } from '@theia/core/lib/common/file-uri';
import {
SketchRef,
SketchContainer,
} from '../common/protocol/sketches-service';
import { ExamplesService } from '../common/protocol/examples-service';
import { LibraryLocation, LibraryPackage } from '../common/protocol';
import { URI } from '@theia/core/lib/common/uri';
import { Path } from '@theia/core/lib/common/path';
import { LibraryServiceImpl } from './library-service-impl';
import { examplesPath } from './resources';
interface BuiltInSketchRef {
readonly name: string;
readonly relativePath: string;
}
namespace BuiltInSketchRef {
export function toSketchRef(
{ name, relativePath }: BuiltInSketchRef,
root: URI
): SketchRef {
return {
name,
uri: root.resolve(relativePath).toString(),
};
}
}
interface BuiltInSketchContainer {
readonly label: string;
readonly children: BuiltInSketchContainer[];
readonly sketches: BuiltInSketchRef[];
}
namespace BuiltInSketchContainer {
export function toSketchContainer(
source: BuiltInSketchContainer,
root: URI
): SketchContainer {
return {
label: source.label,
children: source.children.map((child) => toSketchContainer(child, root)),
sketches: source.sketches.map((child) =>
BuiltInSketchRef.toSketchRef(child, root)
),
};
}
}
@injectable()
export class BuiltInExamplesServiceImpl {
protected _builtIns: SketchContainer[] | undefined;
@postConstruct()
protected init(): void {
this.builtIns();
}
async builtIns(): Promise<SketchContainer[]> {
if (this._builtIns) {
return this._builtIns;
}
const examplesRootPath = examplesPath;
const examplesRootUri = FileUri.create(examplesRootPath);
const rawJson = await fs.promises.readFile(
join(examplesRootPath, 'examples.json'),
{ encoding: 'utf8' }
);
const examples: BuiltInSketchContainer[] = JSON.parse(rawJson);
this._builtIns = examples.map((container) =>
BuiltInSketchContainer.toSketchContainer(container, examplesRootUri)
);
return this._builtIns;
}
}
@injectable()
export class ExamplesServiceImpl implements ExamplesService {
@inject(LibraryServiceImpl)
private readonly libraryService: LibraryServiceImpl;
@inject(BuiltInExamplesServiceImpl)
private readonly builtInExamplesService: BuiltInExamplesServiceImpl;
builtIns(): Promise<SketchContainer[]> {
return this.builtInExamplesService.builtIns();
}
async installed({ fqbn }: { fqbn?: string }): Promise<{
user: SketchContainer[];
current: SketchContainer[];
any: SketchContainer[];
}> {
const user: SketchContainer[] = [];
const current: SketchContainer[] = [];
const any: SketchContainer[] = [];
const packages: LibraryPackage[] = await this.libraryService.list({
fqbn,
});
for (const pkg of packages) {
const container = await this.tryGroupExamples(pkg);
const { location } = pkg;
if (location === LibraryLocation.USER) {
user.push(container);
} else if (
location === LibraryLocation.PLATFORM_BUILTIN ||
LibraryLocation.REFERENCED_PLATFORM_BUILTIN
) {
current.push(container);
} else {
any.push(container);
}
}
return { user, current, any };
}
async find(options: { libraryName: string }): Promise<SketchContainer[]> {
const { libraryName } = options;
const packages = await this.libraryService.list({ libraryName });
return Promise.all(
packages
.filter(({ location }) => location === LibraryLocation.USER)
.map((pkg) => this.tryGroupExamples(pkg))
);
}
/**
* The CLI provides direct FS paths to the examples so that menus and menu groups cannot be built for the UI by traversing the
* folder hierarchy. This method tries to workaround it by falling back to the `installDirUri` and manually creating the
* location of the examples. Otherwise it creates the example container from the direct examples FS paths.
*/
private async tryGroupExamples({
name,
exampleUris,
installDirUri,
}: LibraryPackage): Promise<SketchContainer> {
const container = SketchContainer.create(name);
if (!installDirUri || !exampleUris.length) {
return container;
}
// Args example:
// exampleUris
// 0:'file:///Users/a.kitta/Documents/Arduino/libraries/ATOM_DTU_CAT1/examples/MQTT'
// 1:'file:///Users/a.kitta/Documents/Arduino/libraries/ATOM_DTU_CAT1/examples/Modbus/ModBus-RTU/Master'
// 2:'file:///Users/a.kitta/Documents/Arduino/libraries/ATOM_DTU_CAT1/examples/Modbus/ModBus-RTU/Slave'
// installDirUri
// 'file:///Users/a.kitta/Documents/Arduino/libraries/ATOM_DTU_CAT1'
// Expected menu structure:
// ATOM_DTU_CAT1 > Modbus > ModBus-RTU > Master
// | > Slave
// > MQTT
const logInfo = (ref: SketchRef) =>
`Example URI: ${ref.uri}, install location URI: ${installDirUri}.`;
for (const ref of exampleUris.map(SketchRef.fromUri)) {
const path = new URI(installDirUri).relative(new URI(ref.uri));
if (!path) {
console.warn(
`Could not resolve the sketch location from its install location. Skipping. ${logInfo(
ref
)}`
);
continue;
}
if (path.isAbsolute) {
console.warn(
`Expected a relative path between the sketch and the install locations. Skipping. Path was: ${path}. ${logInfo(
ref
)}`
);
continue;
}
const pathSegments = path.toString().split(Path.separator);
if (pathSegments.length < 2) {
console.warn(
`Expected at least two segments long relative path. Skipping. Path segments were: ${pathSegments}. ${logInfo(
ref
)}`
);
continue;
}
// the relative must start start with `example` or `Examples` or `EXAMPLE`, .etc. It's open source.
if (!/^examples?$/gi.test(pathSegments[0])) {
console.warn(
`First segment must start with "examples-like". More formally: \`/^examples?$/gi\`. Path segments were: ${pathSegments}. ${logInfo(
ref
)}`
);
}
const getOrCreateChildContainer = (
label: string,
parent: SketchContainer
) => {
let child = parent.children.find(
({ label: childLabel }) => childLabel === label
);
if (!child) {
child = SketchContainer.create(label);
parent.children.push(child);
}
return child;
};
const refContainer = pathSegments.reduce(
(container, segment, index, segments) => {
if (index === 0) {
// skip the first "example-like" segment
return container;
}
if (index === segments.length - 1) {
// if last segment, it's the example sketch itself, do not create container for it.
return container;
}
return getOrCreateChildContainer(segment, container);
},
container
);
refContainer.sketches.push(ref);
}
return container;
}
}