Skip to content

Navigation Menu

Sign in
Appearance settings

Search code, repositories, users, issues, pull requests...

Provide feedback

We read every piece of feedback, and take your input very seriously.

Saved searches

Use saved searches to filter your results more quickly

Sign up
Appearance settings

Commit 692f29f

Browse files
Akos Kittakittaakos
Akos Kitta
authored andcommitted
fix: sketchbook container building
Closes #1185 Signed-off-by: Akos Kitta <a.kitta@arduino.cc>
1 parent 40e7979 commit 692f29f

File tree

19 files changed

+342
-113
lines changed

19 files changed

+342
-113
lines changed

‎.gitignore‎

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@ node_modules/
44
lib/
55
downloads/
66
build/
7-
Examples/
7+
arduino-ide-extension/Examples/
88
!electron/build/
99
src-gen/
1010
webpack.config.js
@@ -21,3 +21,5 @@ scripts/themes/tokens
2121
.env
2222
# content trace files for electron
2323
electron-app/traces
24+
# any Arduino LS generated log files
25+
inols*.log

‎.vscode/settings.json‎

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,9 @@
22
"files.exclude": {
33
"**/lib": false
44
},
5+
"search.exclude": {
6+
"arduino-ide-extension/src/test/node/__test_sketchbook__": true
7+
},
58
"typescript.tsdk": "node_modules/typescript/lib",
69
"editor.codeActionsOnSave": {
710
"source.fixAll.eslint": true

‎arduino-ide-extension/package.json‎

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,7 @@
6969
"dateformat": "^3.0.3",
7070
"deepmerge": "2.0.1",
7171
"electron-updater": "^4.6.5",
72+
"fast-json-stable-stringify": "^2.1.0",
7273
"fast-safe-stringify": "^2.1.1",
7374
"glob": "^7.1.6",
7475
"google-protobuf": "^3.20.1",

‎arduino-ide-extension/src/node/sketches-service-impl.ts‎

Lines changed: 164 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ import * as glob from 'glob';
77
import * as crypto from 'crypto';
88
import * as PQueue from 'p-queue';
99
import { ncp } from 'ncp';
10+
import { Mutable } from '@theia/core/lib/common/types';
1011
import URI from '@theia/core/lib/common/uri';
1112
import { ILogger } from '@theia/core/lib/common/logger';
1213
import { FileUri } from '@theia/core/lib/node/file-uri';
@@ -84,108 +85,15 @@ export class SketchesServiceImpl
8485
this.logger.warn(`Could not derive sketchbook root from ${uri}.`);
8586
return SketchContainer.create('');
8687
}
87-
const exists = await this.exists(root);
88-
if (!exists) {
88+
const rootExists = await exists(root);
89+
if (!rootExists) {
8990
this.logger.warn(`Sketchbook root ${root} does not exist.`);
9091
return SketchContainer.create('');
9192
}
92-
const pathToAllSketchFiles = await new Promise<string[]>(
93-
(resolve, reject) => {
94-
glob(
95-
'/!(libraries|hardware)/**/*.{ino,pde}',
96-
{ root },
97-
(error, results) => {
98-
if (error) {
99-
reject(error);
100-
} else {
101-
resolve(results);
102-
}
103-
}
104-
);
105-
}
93+
const container = <Mutable<SketchContainer>>(
94+
SketchContainer.create(uri ? path.basename(root) : 'Sketchbook')
10695
);
107-
// Sort by path length to filter out nested sketches, such as the `Nested_folder` inside the `Folder` sketch.
108-
//
109-
// `directories#user`
110-
// |
111-
// +--Folder
112-
// |
113-
// +--Folder.ino
114-
// |
115-
// +--Nested_folder
116-
// |
117-
// +--Nested_folder.ino
118-
pathToAllSketchFiles.sort((left, right) => left.length - right.length);
119-
const container = SketchContainer.create(
120-
uri ? path.basename(root) : 'Sketchbook'
121-
);
122-
const getOrCreateChildContainer = (
123-
parent: SketchContainer,
124-
segments: string[]
125-
) => {
126-
if (segments.length === 1) {
127-
throw new Error(
128-
`Expected at least two segments relative path: ['ExampleSketchName', 'ExampleSketchName.{ino,pde}]. Was: ${segments}`
129-
);
130-
}
131-
if (segments.length === 2) {
132-
return parent;
133-
}
134-
const label = segments[0];
135-
const existingSketch = parent.sketches.find(
136-
(sketch) => sketch.name === label
137-
);
138-
if (existingSketch) {
139-
// If the container has a sketch with the same label, it cannot have a child container.
140-
// See above example about how to ignore nested sketches.
141-
return undefined;
142-
}
143-
let child = parent.children.find((child) => child.label === label);
144-
if (!child) {
145-
child = SketchContainer.create(label);
146-
parent.children.push(child);
147-
}
148-
return child;
149-
};
150-
for (const pathToSketchFile of pathToAllSketchFiles) {
151-
const relative = path.relative(root, pathToSketchFile);
152-
if (!relative) {
153-
this.logger.warn(
154-
`Could not determine relative sketch path from the root <${root}> to the sketch <${pathToSketchFile}>. Skipping. Relative path was: ${relative}`
155-
);
156-
continue;
157-
}
158-
const segments = relative.split(path.sep);
159-
if (segments.length < 2) {
160-
// folder name, and sketch name.
161-
this.logger.warn(
162-
`Expected at least one segment relative path from the root <${root}> to the sketch <${pathToSketchFile}>. Skipping. Segments were: ${segments}.`
163-
);
164-
continue;
165-
}
166-
// the folder name and the sketch name must match. For example, `Foo/foo.ino` is invalid.
167-
// drop the folder name from the sketch name, if `.ino` or `.pde` remains, it's valid
168-
const sketchName = segments[segments.length - 2];
169-
const sketchFilename = segments[segments.length - 1];
170-
const sketchFileExtension = segments[segments.length - 1].replace(
171-
new RegExp(escapeRegExpCharacters(sketchName)),
172-
''
173-
);
174-
if (sketchFileExtension !== '.ino' && sketchFileExtension !== '.pde') {
175-
this.logger.warn(
176-
`Mismatching sketch file <${sketchFilename}> and sketch folder name <${sketchName}>. Skipping`
177-
);
178-
continue;
179-
}
180-
const child = getOrCreateChildContainer(container, segments);
181-
if (child) {
182-
child.sketches.push({
183-
name: sketchName,
184-
uri: FileUri.create(path.dirname(pathToSketchFile)).toString(),
185-
});
186-
}
187-
}
188-
return container;
96+
return discoverSketches(root, container, this.logger);
18997
}
19098

19199
private async root(uri?: string | undefined): Promise<string | undefined> {
@@ -488,7 +396,7 @@ export class SketchesServiceImpl
488396
this.sketchSuffixIndex++
489397
)}`;
490398
// Note: we check the future destination folder (`directories.user`) for name collision and not the temp folder!
491-
const sketchExists = await this.exists(
399+
const sketchExists = await exists(
492400
path.join(sketchbookPath, sketchNameCandidate)
493401
);
494402
if (!sketchExists) {
@@ -579,8 +487,8 @@ export class SketchesServiceImpl
579487
{ destinationUri }: { destinationUri: string }
580488
): Promise<string> {
581489
const source = FileUri.fsPath(sketch.uri);
582-
const exists = await this.exists(source);
583-
if (!exists) {
490+
const sketchExists = await exists(source);
491+
if (!sketchExists) {
584492
throw new Error(`Sketch does not exist: ${sketch}`);
585493
}
586494
// Nothing to do when source and destination are the same.
@@ -635,7 +543,7 @@ export class SketchesServiceImpl
635543
const { client } = await this.coreClient;
636544
const archivePath = FileUri.fsPath(destinationUri);
637545
// The CLI cannot override existing archives, so we have to wipe it manually: https://github.com/arduino/arduino-cli/issues/1160
638-
if (await this.exists(archivePath)) {
546+
if (await exists(archivePath)) {
639547
await fs.unlink(archivePath);
640548
}
641549
const req = new ArchiveSketchRequest();
@@ -680,15 +588,6 @@ export class SketchesServiceImpl
680588
});
681589
}
682590

683-
private async exists(pathLike: string): Promise<boolean> {
684-
try {
685-
await fs.access(pathLike, constants.R_OK);
686-
return true;
687-
} catch {
688-
return false;
689-
}
690-
}
691-
692591
// Returns the default.ino from the settings or from default folder.
693592
private async readSettings(): Promise<Record<string, unknown> | undefined> {
694593
const configDirUri = await this.envVariableServer.getConfigDirUri();
@@ -837,3 +736,157 @@ function sketchIndexToLetters(num: number): string {
837736
} while (pow > 0);
838737
return out;
839738
}
739+
740+
async function exists(pathLike: string): Promise<boolean> {
741+
try {
742+
await fs.access(pathLike, constants.R_OK);
743+
return true;
744+
} catch {
745+
return false;
746+
}
747+
}
748+
749+
/**
750+
* Recursively discovers sketches in the `root` folder give by the filesystem path.
751+
* Missing `root` must be handled by callers. This function expects an accessible `root` directory.
752+
*/
753+
export async function discoverSketches(
754+
root: string,
755+
container: Mutable<SketchContainer>,
756+
logger?: ILogger
757+
): Promise<SketchContainer> {
758+
const pathToAllSketchFiles = await globSketches(
759+
'/!(libraries|hardware)/**/*.{ino,pde}',
760+
root
761+
);
762+
// if no match try to glob the sketchbook as a sketch folder
763+
if (!pathToAllSketchFiles.length) {
764+
pathToAllSketchFiles.push(...(await globSketches('/*.{ino,pde}', root)));
765+
}
766+
767+
// Sort by path length to filter out nested sketches, such as the `Nested_folder` inside the `Folder` sketch.
768+
//
769+
// `directories#user`
770+
// |
771+
// +--Folder
772+
// |
773+
// +--Folder.ino
774+
// |
775+
// +--Nested_folder
776+
// |
777+
// +--Nested_folder.ino
778+
pathToAllSketchFiles.sort((left, right) => left.length - right.length);
779+
const getOrCreateChildContainer = (
780+
container: SketchContainer,
781+
segments: string[]
782+
): SketchContainer => {
783+
// the sketchbook is a sketch folder
784+
if (segments.length === 1) {
785+
return container;
786+
}
787+
const segmentsCopy = segments.slice();
788+
let currentContainer = container;
789+
while (segmentsCopy.length > 2) {
790+
const currentSegment = segmentsCopy.shift();
791+
if (!currentSegment) {
792+
throw new Error(
793+
`'currentSegment' was not set when processing sketch container: ${JSON.stringify(
794+
container
795+
)}, original segments: ${JSON.stringify(
796+
segments
797+
)}, current container: ${JSON.stringify(
798+
currentContainer
799+
)}, current working segments: ${JSON.stringify(segmentsCopy)}`
800+
);
801+
}
802+
let childContainer = currentContainer.children.find(
803+
(childContainer) => childContainer.label === currentSegment
804+
);
805+
if (!childContainer) {
806+
childContainer = SketchContainer.create(currentSegment);
807+
currentContainer.children.push(childContainer);
808+
}
809+
currentContainer = childContainer;
810+
}
811+
if (segmentsCopy.length !== 2) {
812+
throw new Error(
813+
`Expected exactly two segments. A sketch folder name and the main sketch file name. For example, ['ExampleSketchName', 'ExampleSketchName.{ino,pde}]. Was: ${segmentsCopy}`
814+
);
815+
}
816+
return currentContainer;
817+
};
818+
819+
// If the container has a sketch with the same name, it cannot have a child container.
820+
// See above example about how to ignore nested sketches.
821+
const prune = (
822+
container: Mutable<SketchContainer>
823+
): Mutable<SketchContainer> => {
824+
for (const sketch of container.sketches) {
825+
const childContainerIndex = container.children.findIndex(
826+
(childContainer) => childContainer.label === sketch.name
827+
);
828+
if (childContainerIndex >= 0) {
829+
container.children.splice(childContainerIndex, 1);
830+
}
831+
}
832+
container.children.forEach(prune);
833+
return container;
834+
};
835+
836+
for (const pathToSketchFile of pathToAllSketchFiles) {
837+
const relative = path.relative(root, pathToSketchFile);
838+
if (!relative) {
839+
logger?.warn(
840+
`Could not determine relative sketch path from the root <${root}> to the sketch <${pathToSketchFile}>. Skipping. Relative path was: ${relative}`
841+
);
842+
continue;
843+
}
844+
const segments = relative.split(path.sep);
845+
let sketchName: string;
846+
let sketchFilename: string;
847+
if (!segments.length) {
848+
// no segments.
849+
logger?.warn(
850+
`Expected at least one segment relative path ${relative} from the root <${root}> to the sketch <${pathToSketchFile}>. Skipping.`
851+
);
852+
continue;
853+
} else if (segments.length === 1) {
854+
// The sketchbook root is a sketch folder
855+
sketchName = path.basename(root);
856+
sketchFilename = segments[0];
857+
} else {
858+
// the folder name and the sketch name must match. For example, `Foo/foo.ino` is invalid.
859+
// drop the folder name from the sketch name, if `.ino` or `.pde` remains, it's valid
860+
sketchName = segments[segments.length - 2];
861+
sketchFilename = segments[segments.length - 1];
862+
}
863+
const sketchFileExtension = segments[segments.length - 1].replace(
864+
new RegExp(escapeRegExpCharacters(sketchName)),
865+
''
866+
);
867+
if (sketchFileExtension !== '.ino' && sketchFileExtension !== '.pde') {
868+
logger?.warn(
869+
`Mismatching sketch file <${sketchFilename}> and sketch folder name <${sketchName}>. Skipping`
870+
);
871+
continue;
872+
}
873+
const child = getOrCreateChildContainer(container, segments);
874+
child.sketches.push({
875+
name: sketchName,
876+
uri: FileUri.create(path.dirname(pathToSketchFile)).toString(),
877+
});
878+
}
879+
return prune(container);
880+
}
881+
882+
async function globSketches(pattern: string, root: string): Promise<string[]> {
883+
return new Promise<string[]>((resolve, reject) => {
884+
glob(pattern, { root }, (error, results) => {
885+
if (error) {
886+
reject(error);
887+
} else {
888+
resolve(results);
889+
}
890+
});
891+
});
892+
}

‎arduino-ide-extension/src/test/node/__test_sketchbook__/a_sketch/a_sketch.ino‎

Whitespace-only changes.

‎arduino-ide-extension/src/test/node/__test_sketchbook__/bar++/bar++.ino‎

Whitespace-only changes.

‎arduino-ide-extension/src/test/node/__test_sketchbook__/defaultIno.ino‎

Whitespace-only changes.

‎arduino-ide-extension/src/test/node/__test_sketchbook__/empty/.gitkeep‎

Whitespace-only changes.

‎arduino-ide-extension/src/test/node/__test_sketchbook__/libraries/my_library/examples/Ethernet/Example_1/Example_1.ino‎

Whitespace-only changes.

‎arduino-ide-extension/src/test/node/__test_sketchbook__/libraries/my_library/examples/WiFi/Example_2/Example_2.ino‎

Whitespace-only changes.

0 commit comments

Comments
(0)

AltStyle によって変換されたページ (->オリジナル) /