Skip to content

Commit 628dc0f

Browse files
authored
Merge pull request #425 from AntoineGautier/issue422_routing
Improve routing logic
2 parents ab91918 + 794cd33 commit 628dc0f

File tree

14 files changed

+221
-124
lines changed

14 files changed

+221
-124
lines changed

server/bin/install-modelica-dependencies.sh

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
#!/bin/sh
22
set -x
33

4-
MODELICA_BUILDINGS_COMMIT=6263b4788b2013a5a5ac3bf5ef163d453995058c
4+
MODELICA_BUILDINGS_COMMIT=b399379315641da39b231033b0660100fd6489a5
55
MODELICA_JSON_COMMIT=a46a361c3047c0a2b3d1cfc9bc8b0a4ced16006a
66

77
parent_path=$( cd "$(dirname "${BASH_SOURCE[0]}")" ; pwd -P )

server/src/parser/README.md

Lines changed: 15 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -172,18 +172,21 @@ It creates the following dictionary of modifiers on the option `Buildings.Templa
172172
In [parser.ts](./parser.ts) `loadPackage` wwill attemp to load the provided path.
173173

174174
### Template Entry Points
175-
There is a simple template discovery process with a grep for "__ctrlFlow_template".
175+
The template discovery process relies on a grep for the following hierarchical class annotations:
176176

177-
The executed command looks as follows:
177+
- `__ctrlFlow(routing="root")`: Marks the top-level package containing all templates, e.g.,
178+
`Buildings.Templates`
179+
- `__ctrlFlow(routing="template")`: Marks individual template classes, e.g.,
180+
`Buildings.Templates.AirHandlersFans.VAVMultiZone`
178181

179-
```typescript
180-
const cmd = `grep -rl ${dirPath} -e "${TEMPLATE_IDENTIFIER}"`;
181-
```
182-
183-
This command ultimately returns a list of files that contain the unique string.
184-
185-
Each of these file paths then get 'loaded' in [parser.ts](./parser.ts) into a `File` instance, which starts the process of consuming modelica JSON and generating various types of parse `Element`s.
182+
All template classes, and all packages between the root package and the template classes
183+
are considered as "entry points" and added to the `templateNodes` array of [loader.ts](./loader.ts).
184+
This means that the class URI ultimately dictates the explorer tree structure,
185+
starting from the "root" package which must be unique inside a library.
186186

187+
Based on their URIs, the entry points are loaded in [parser.ts](./parser.ts) into
188+
`File` instances, which starts the process of consuming modelica JSON and generating
189+
various types of parsed `Element`s.
187190

188191
### Type Store
189192
During the process of creating `Element`s, each element gets registered to a typestore using the modelica path.
@@ -215,23 +218,6 @@ Currently data is NOT being pulled in from the Modelica Standard Library. A simp
215218
It is likely a good idea to try and separate out 'Modifier' like objects that have a modelicaPath vs. those that do not. `redeclare` and `final` only relate to modifiers that have a `modelicaPath`.
216219

217220
### Template Entry Points
218-
219-
The current approach is a simplistic and not very flexible. A more robust approach has been discussed:
220-
221-
- Use a flag indicating that a package (in our case Buildings.Templates) is to be considered as the "root" for all template URIs, for instance:
222-
__ctrlFlow(routing="root")
223-
- For each template class (for instance Buildings.Templates.AirHandlersFans.VAVMultiZone):
224-
__ctrlFlow(routing="template")
225-
226-
227-
>The contract for the template developer will then be that the class URI dictates the explorer tree structure, starting from the "root" package (necessarily unique inside a library).
228-
So for instance the template Buildings.Templates.AirHandlersFans.VAVMultiZone with the above annotation would yield the following tree structure:
229-
>
230-
>AirHandlersFans
231-
>
232-
>└── VAVMultiZone
233-
>
234-
>Without having to add any annotation to the subpackage Buildings.Templates.AirHandlersFans.
235-
```
236-
237-
To implement this, the grep command can continue to be used (by changing the template identifier), however the process for finding subpackages would need to be tweaked a bit in the parser since they are not explicitly listed from the grep command.
221+
The UI doesn't currently support multiple subpackages. Therefore, only the
222+
parent package of each template class is used to generate the tree structure in the UI.
223+
See the function `_extractSystemTypes` in [template.ts](./template.ts).

server/src/parser/loader.ts

Lines changed: 124 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,13 @@ import { execSync } from "child_process";
55
import { typeStore } from "./parser";
66
import config from "../../src/config";
77

8-
export const TEMPLATE_IDENTIFIER = "__ctrlFlow_template";
8+
// The following arrays are populated with the ***full class names***
9+
// of templates identified with class annotation __ctrlFlow(routing="template")
10+
// and all packages containing templates up to the root package
11+
// identified with class annotation __ctrlFlow(routing="root").
12+
export const TEMPLATE_LIST: string[] = [];
13+
export const PACKAGE_LIST: string[] = [];
14+
915
export let MODELICA_JSON_PATH = [
1016
`${config.MODELICA_DEPENDENCIES}/template-json/json/`,
1117
];
@@ -15,48 +21,143 @@ export function prependToModelicaJsonPath(paths: string[]) {
1521
MODELICA_JSON_PATH = [...paths, ...MODELICA_JSON_PATH];
1622
}
1723

24+
// The following regexp are prettyPrint safe (\s*) when used with GNU (not BSD) grep -z
25+
const TEMPLATE_ROOT =
26+
'annotation.*__ctrlFlow.*name":\\s*"routing".*simple_expression":\\s*"\\\\"root\\\\"';
27+
const TEMPLATE_IDENTIFIER =
28+
'annotation.*__ctrlFlow.*name":\\s*"routing".*simple_expression":\\s*"\\\\"template\\\\"';
29+
30+
type TemplateNode = {
31+
className: string;
32+
description: string;
33+
json: Object;
34+
isPackage: boolean;
35+
parentName: string | null;
36+
};
37+
38+
// Master list of TemplateNode objects representing all templates and packages.
39+
// It is intended to be used in the future when the UI can handle nested packages.
40+
const templateNodes: TemplateNode[] = [];
41+
1842
export function getClassNameFromRelativePath(filePath: string) {
1943
filePath = filePath.endsWith(".json") ? filePath.slice(0, -5) : filePath;
2044
return filePath.replace(/\//g, ".");
2145
}
2246

47+
export function getClassNameFromJson(json: any): string {
48+
return (
49+
(json.within ? json.within + "." : "") +
50+
json.class_definition[0].class_specifier.long_class_specifier.identifier
51+
);
52+
}
53+
54+
/**
55+
* Creates a TemplateNode object from a JSON representation of a Modelica class.
56+
* @param json - The JSON object containing the Modelica class definition
57+
* @returns A TemplateNode object with class information
58+
*/
59+
function createTemplateNode(json: any): TemplateNode {
60+
const classDefinition = json.class_definition[0];
61+
return {
62+
className: getClassNameFromJson(json),
63+
description:
64+
classDefinition.class_specifier.long_class_specifier.description_string,
65+
json: json,
66+
isPackage: classDefinition.class_prefixes.includes("package"),
67+
parentName: json.within ?? null,
68+
};
69+
}
70+
71+
/**
72+
* Executes a system grep command to search for a regular expression pattern in files.
73+
* @param regExp - The regular expression pattern to search for
74+
* @param dirPath - The directory path to search in
75+
* @returns Array of matching file paths or null if no matches found
76+
*/
77+
function systemGrep(regExp: string, dirPath: string): string[] | null {
78+
const cmd = `grep -lrz '${regExp}' ${dirPath}`;
79+
try {
80+
return execSync(cmd)
81+
.toString()
82+
.split("\n")
83+
.filter((p) => p != "");
84+
} catch (error) {
85+
return null;
86+
}
87+
}
88+
2389
/**
2490
* Finds all entry points that contain the template identifier for a given package.
2591
* - LIMITATION: This function requires that the package uses
2692
* [Directory Hierarchy Mapping](https://specification.modelica.org/maint/3.6/packages.html#directory-hierarchy-mapping)
93+
* - Currently, only entryPoints, TEMPLATE_LIST and PACKAGE_LIST are used.
94+
* - In the future, when the UI can handle nested packages, entryPoints should be removed
95+
* and templateNodes should be used to create the file tree structure.
2796
* @param packageName - The Modelica class name of the package to search for entry points
2897
* @returns An array of objects containing the path and parsed JSON for each entry point found
2998
*/
3099
export function findPackageEntryPoints(
31100
packageName: string,
32-
): { path: string; json: Object | undefined }[] {
33-
const entryPoints: { path: string; json: Object | undefined }[] = [];
101+
): { className: string; json: Object | undefined }[] {
102+
const entryPoints: { className: string; json: Object | undefined }[] = [];
34103
MODELICA_JSON_PATH.forEach((dir) => {
35-
// We need a top directory to look up for entry points
36-
// so we can simply convert the class name to a relative path
37-
// without adding any file extension.
104+
// We can simply convert the class name to a relative path without adding any file extension
105+
// because we only need a top directory to look up for entry points.
38106
const dirPath = path.resolve(dir, packageName.replace(/\./g, "/"));
39107
if (fs.existsSync(dirPath)) {
40-
const cmd = `grep -rl ${dirPath} -e "${TEMPLATE_IDENTIFIER}"`;
41-
const response = execSync(cmd).toString();
42-
entryPoints.push(
43-
...response
44-
.split("\n")
45-
.filter((p) => p != "")
46-
.sort((a, b) => (a.includes("package.json") ? -1 : 1))
47-
.map((p) => path.relative(dir, p))
48-
.map((p) => {
49-
const path = getClassNameFromRelativePath(p);
50-
return {
51-
path: path,
52-
json: loader(path),
53-
};
54-
}),
55-
);
108+
// Find all template files.
109+
const templatePaths = systemGrep(TEMPLATE_IDENTIFIER, dirPath);
110+
111+
// Populate arrays storing templates.
112+
templatePaths?.forEach((p) => {
113+
const templateJson = loader(p);
114+
const templateNode = createTemplateNode(templateJson);
115+
TEMPLATE_LIST.push(templateNode.className);
116+
templateNodes.push(templateNode);
117+
});
118+
119+
// Find root package.
120+
const rootPackagePath = systemGrep(TEMPLATE_ROOT, dirPath)?.[0];
121+
if (!rootPackagePath) {
122+
console.error("Error: root package not found in " + dirPath);
123+
process.exit(1);
124+
}
125+
const rootPackageJson = loader(rootPackagePath);
126+
const rootPackageName = getClassNameFromJson(rootPackageJson);
127+
128+
// Iterate over all template files up to the root package and populate
129+
// templateNodes, TEMPLATE_LIST, PACKAGE_LIST and entryPoints.
130+
131+
for (let templateJson of [...templateNodes.map(({json}) => json)]) {
132+
let packageName = (templateJson as any).within;
133+
while (packageName && packageName !== rootPackageName) {
134+
const packagePath = getPathFromClassName(packageName, dir);
135+
if (!packagePath) {
136+
break;
137+
}
138+
const packageJson = loader(packagePath);
139+
// If the package is already in the templateNodes array, its parents have also been added.
140+
if (
141+
templateNodes
142+
.map(({ className }) => className)
143+
.includes(packageName)
144+
) {
145+
break;
146+
}
147+
if (!packageJson) {
148+
continue;
149+
}
150+
const packageNode = createTemplateNode(packageJson);
151+
PACKAGE_LIST.push(packageNode.className);
152+
templateNodes.push(packageNode);
153+
154+
packageName = (packageJson as any).within;
155+
}
156+
}
56157
}
57158
});
58159

59-
return entryPoints;
160+
return templateNodes.map(({ className, json }) => { return { className, json }; });
60161
}
61162

62163
/**

0 commit comments

Comments
 (0)