Skip to content

Commit 6717f38

Browse files
committed
feat: Added wild card matching for import
1 parent f6adbcd commit 6717f38

File tree

7 files changed

+108
-66
lines changed

7 files changed

+108
-66
lines changed

src/orchestrators/destroy.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ export class DestroyOrchestrator {
2323

2424
ctx.processStarted(ProcessName.DESTROY)
2525

26-
const { dependencyMap, pluginManager, project } = await InitializeOrchestrator.run({
26+
const { typeIdsToDependenciesMap, pluginManager, project } = await InitializeOrchestrator.run({
2727
...args,
2828
allowEmptyProject: true,
2929
transformProject(project) {
@@ -42,10 +42,10 @@ export class DestroyOrchestrator {
4242
}
4343
}, reporter);
4444

45-
await DestroyOrchestrator.validate(project, pluginManager, dependencyMap)
45+
await DestroyOrchestrator.validate(project, pluginManager, typeIdsToDependenciesMap)
4646

4747
const uninstallProject = project.toDestroyProject()
48-
uninstallProject.resolveDependenciesAndCalculateEvalOrder(dependencyMap);
48+
uninstallProject.resolveDependenciesAndCalculateEvalOrder(typeIdsToDependenciesMap);
4949

5050
const plan = await ctx.subprocess(ProcessName.PLAN, () =>
5151
pluginManager.plan(uninstallProject)

src/orchestrators/import.ts

Lines changed: 41 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import { PromptType, Reporter } from '../ui/reporters/reporter.js';
1010
import { FileUtils } from '../utils/file.js';
1111
import { FileModificationCalculator, ModificationType } from '../utils/file-modification-calculator.js';
1212
import { sleep } from '../utils/index.js';
13+
import { wildCardMatch } from '../utils/wild-card-match.js';
1314
import { InitializeOrchestrator } from './initialize.js';
1415

1516
export type RequiredParameters = Map<string, RequiredParameter[]>;
@@ -51,16 +52,18 @@ export class ImportOrchestrator {
5152

5253
ctx.processStarted(ProcessName.IMPORT)
5354

54-
const { dependencyMap, pluginManager, project } = await InitializeOrchestrator.run(
55+
const { typeIdsToDependenciesMap, pluginManager, project } = await InitializeOrchestrator.run(
5556
{ ...args, allowEmptyProject: true },
5657
reporter
5758
);
58-
await ImportOrchestrator.validate(typeIds, project, pluginManager, dependencyMap)
59-
const resourceInfoList = await pluginManager.getMultipleResourceInfo(typeIds);
6059

60+
const matchedTypes = this.matchTypeIds(typeIds, [...typeIdsToDependenciesMap.keys()])
61+
await ImportOrchestrator.validate(matchedTypes, project, pluginManager, typeIdsToDependenciesMap);
62+
63+
const resourceInfoList = await pluginManager.getMultipleResourceInfo(matchedTypes);
64+
// Figure out which resources we need to prompt the user for additional info (based on the resource info)
6165
const [noPrompt, askPrompt] = resourceInfoList.reduce((result, info) => {
6266
info.getRequiredParameters().length === 0 ? result[0].push(info) : result[1].push(info);
63-
6467
return result;
6568
}, [<ResourceInfo[]>[], <ResourceInfo[]>[]])
6669

@@ -115,6 +118,39 @@ export class ImportOrchestrator {
115118
}
116119
}
117120

121+
private static matchTypeIds(typeIds: string[], validTypeIds: string[]): string[] {
122+
const result: string[] = [];
123+
const unsupportedTypeIds: string[] = [];
124+
125+
for (const typeId of typeIds) {
126+
if (!typeId.includes('*') && !typeId.includes('?')) {
127+
const matched = validTypeIds.includes(typeId);
128+
if (!matched) {
129+
unsupportedTypeIds.push(typeId);
130+
continue;
131+
}
132+
133+
result.push(typeId)
134+
continue;
135+
}
136+
137+
const matched = validTypeIds.filter((valid) => wildCardMatch(valid, typeId))
138+
if (matched.length === 0) {
139+
unsupportedTypeIds.push(typeId);
140+
continue;
141+
}
142+
143+
result.push(...matched);
144+
}
145+
146+
if (unsupportedTypeIds.length > 0) {
147+
throw new Error(`The following resources cannot be imported. No plugins found that support the following types:
148+
${JSON.stringify(unsupportedTypeIds)}`);
149+
}
150+
151+
return result;
152+
}
153+
118154
private static async validate(typeIds: string[], project: Project, pluginManager: PluginManager, dependencyMap: DependencyMap): Promise<void> {
119155
ctx.subprocessStarted(SubProcessName.VALIDATE)
120156

@@ -237,3 +273,4 @@ ${JSON.stringify(unsupportedTypeIds)}`);
237273
})
238274
}
239275
}
276+

src/orchestrators/initialize.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ export interface InitializeArgs {
1616
}
1717

1818
export interface InitializationResult {
19-
dependencyMap: DependencyMap
19+
typeIdsToDependenciesMap: DependencyMap
2020
pluginManager: PluginManager,
2121
project: Project,
2222
}
@@ -37,10 +37,10 @@ export class InitializeOrchestrator {
3737

3838
ctx.subprocessStarted(SubProcessName.INITIALIZE_PLUGINS)
3939
const pluginManager = new PluginManager();
40-
const dependencyMap = await pluginManager.initialize(project, args.secure);
40+
const typeIdsToDependenciesMap = await pluginManager.initialize(project, args.secure);
4141
ctx.subprocessFinished(SubProcessName.INITIALIZE_PLUGINS)
4242

43-
return { dependencyMap, pluginManager, project };
43+
return { typeIdsToDependenciesMap, pluginManager, project };
4444
}
4545

4646
private static async parse(

src/orchestrators/plan.ts

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -21,14 +21,14 @@ export class PlanOrchestrator {
2121
static async run(args: PlanArgs, reporter: Reporter): Promise<PlanOrchestratorResponse> {
2222
ctx.processStarted(ProcessName.PLAN)
2323

24-
const { dependencyMap, pluginManager, project } = await InitializeOrchestrator.run({
24+
const { typeIdsToDependenciesMap, pluginManager, project } = await InitializeOrchestrator.run({
2525
...args,
2626
}, reporter);
2727

2828
await createStartupShellScriptsIfNotExists();
2929

30-
await PlanOrchestrator.validate(project, pluginManager, dependencyMap)
31-
project.resolveDependenciesAndCalculateEvalOrder(dependencyMap);
30+
await PlanOrchestrator.validate(project, pluginManager, typeIdsToDependenciesMap)
31+
project.resolveDependenciesAndCalculateEvalOrder(typeIdsToDependenciesMap);
3232
project.addXCodeToolsConfig(); // We have to add xcode-tools config always since almost every resource depends on it
3333

3434
const plan = await PlanOrchestrator.plan(project, pluginManager);

src/plugins/plugin-manager.ts

Lines changed: 2 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -54,25 +54,11 @@ export class PluginManager {
5454
);
5555
}
5656

57-
async getResourceInfo(type: string): Promise<GetResourceInfoResponseData> {
58-
const pluginName = this.resourceToPluginMapping.get(type);
59-
if (!pluginName) {
60-
throw new Error(`Unable to find plugin for resource: ${type}`);
61-
}
62-
63-
const plugin = this.plugins.get(pluginName)
64-
if (!plugin) {
65-
throw new Error(`Unable to find plugin for resource ${type}`);
66-
}
67-
68-
return plugin.getResourceInfo(type);
69-
}
70-
7157
async getMultipleResourceInfo(typeIds: string[]): Promise<ResourceInfo[]> {
72-
return Promise.all(typeIds.map((type) => this.getResourceInfoV2(type)))
58+
return Promise.all(typeIds.map((type) => this.getResourceInfo(type)))
7359
}
7460

75-
async getResourceInfoV2(type: string): Promise<ResourceInfo> {
61+
async getResourceInfo(type: string): Promise<ResourceInfo> {
7662
const pluginName = this.resourceToPluginMapping.get(type);
7763
if (!pluginName) {
7864
throw new Error(`Unable to find plugin for resource: ${type}`);
@@ -143,41 +129,6 @@ export class PluginManager {
143129
}
144130
}
145131

146-
async getRequiredParameters(
147-
typeIds: string[],
148-
): Promise<RequiredParameters> {
149-
const allRequiredParameters = new Map<string, RequiredParameter[]>();
150-
for (const type of typeIds) {
151-
const resourceInfo = await this.getResourceInfo(type);
152-
153-
const { schema } = resourceInfo;
154-
if (!schema) {
155-
continue;
156-
}
157-
158-
const requiredParameterNames = resourceInfo.import?.requiredParameters;
159-
if (!requiredParameterNames || requiredParameterNames.length === 0) {
160-
continue;
161-
}
162-
163-
requiredParameterNames
164-
.forEach((name) => {
165-
if (!allRequiredParameters.has(type)) {
166-
allRequiredParameters.set(type, []);
167-
}
168-
169-
const schemaInfo = (schema.properties as any)[name];
170-
171-
allRequiredParameters.get(type)!.push({
172-
name,
173-
type: schemaInfo.type ?? null
174-
})
175-
});
176-
}
177-
178-
return allRequiredParameters;
179-
}
180-
181132
private async resolvePlugins(project: Project | null): Promise<Plugin[]> {
182133
const pluginDefinitions: Record<string, string> = {
183134
...DEFAULT_PLUGINS,

src/utils/wild-card-match.ts

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
// JavaScript program for wild card matching using single traversal. From: https://www.geeksforgeeks.org/wildcard-pattern-matching/
2+
// O(n) time and O(1) space complexity
3+
export function wildCardMatch(txt: string, pat: string) {
4+
const n = txt.length;
5+
const m = pat.length;
6+
let i = 0, j = 0, startIndex = -1, match = 0;
7+
8+
while (i < n) {
9+
10+
// Characters match or '?' in pattern matches
11+
// any character.
12+
if (j < m && (pat[j] === '?' || pat[j] === txt[i])) {
13+
i++;
14+
j++;
15+
}
16+
17+
else if (j < m && pat[j] === '*') {
18+
19+
// Wildcard character '*', mark the current
20+
// position in the pattern and the text as a
21+
// proper match.
22+
startIndex = j;
23+
match = i;
24+
j++;
25+
}
26+
27+
else if (startIndex !== -1) {
28+
29+
// No match, but a previous wildcard was found.
30+
// Backtrack to the last '*' character position
31+
// and try for a different match.
32+
j = startIndex + 1;
33+
match++;
34+
i = match;
35+
}
36+
37+
else {
38+
39+
// If none of the above cases comply, the
40+
// pattern does not match.
41+
return false;
42+
}
43+
}
44+
45+
// Consume any remaining '*' characters in the given
46+
// pattern.
47+
while (j < m && pat[j] === '*') {
48+
j++;
49+
}
50+
51+
// If we have reached the end of both the pattern and
52+
// the text, the pattern matches the text.
53+
return j === m;
54+
}

test/orchestrator/initialize/initialize.test.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,7 +64,7 @@ describe('Parser integration tests', () => {
6464
const cwdSpy = vi.spyOn(process, 'cwd');
6565
cwdSpy.mockReturnValue(folder);
6666

67-
const { project, pluginManager, dependencyMap } = await InitializeOrchestrator.run({}, reporter);
67+
const { project, pluginManager, typeIdsToDependenciesMap } = await InitializeOrchestrator.run({}, reporter);
6868

6969
console.log(project);
7070
expect(project).toMatchObject({
@@ -106,7 +106,7 @@ describe('Parser integration tests', () => {
106106
const cwdSpy = vi.spyOn(process, 'cwd');
107107
cwdSpy.mockReturnValue(innerFolder);
108108

109-
const { project, pluginManager, dependencyMap } = await InitializeOrchestrator.run({}, reporter);
109+
const { project, pluginManager, typeIdsToDependenciesMap } = await InitializeOrchestrator.run({}, reporter);
110110

111111
console.log(project);
112112
expect(project).toMatchObject({

0 commit comments

Comments
 (0)