mirror of
https://github.com/arduino/arduino-ide.git
synced 2025-06-10 22:26:32 +00:00
226 lines
7.2 KiB
TypeScript
226 lines
7.2 KiB
TypeScript
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;
|
|
}
|
|
}
|