wyreframe 0.1.0 → 0.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +692 -0
- package/README.md +65 -5
- package/package.json +8 -7
- package/src/index.ts +425 -0
- package/src/renderer/Renderer.gen.tsx +49 -0
- package/src/renderer/Renderer.mjs +41 -1
- package/src/renderer/Renderer.res +78 -0
- package/src/parser/Core/__tests__/Bounds_test.mjs +0 -326
- package/src/parser/Core/__tests__/Bounds_test.res +0 -412
- package/src/parser/Core/__tests__/Grid_test.mjs +0 -322
- package/src/parser/Core/__tests__/Grid_test.res +0 -319
- package/src/parser/Core/__tests__/Types_test.mjs +0 -614
- package/src/parser/Core/__tests__/Types_test.res +0 -650
- package/src/parser/Detector/__tests__/BoxTracer_test.mjs +0 -70
- package/src/parser/Detector/__tests__/BoxTracer_test.res +0 -92
- package/src/parser/Detector/__tests__/HierarchyBuilder_test.mjs +0 -489
- package/src/parser/Detector/__tests__/HierarchyBuilder_test.res +0 -849
- package/src/parser/Detector/__tests__/ShapeDetector_test.mjs +0 -377
- package/src/parser/Detector/__tests__/ShapeDetector_test.res +0 -563
- package/src/parser/Interactions/__tests__/InteractionMerger_test.mjs +0 -576
- package/src/parser/Interactions/__tests__/InteractionMerger_test.res +0 -646
- package/src/parser/Scanner/__tests__/Grid_manual.mjs +0 -214
- package/src/parser/Scanner/__tests__/Grid_manual.res +0 -141
- package/src/parser/Semantic/Elements/__tests__/ButtonParser_test.mjs +0 -189
- package/src/parser/Semantic/Elements/__tests__/ButtonParser_test.res +0 -257
- package/src/parser/Semantic/Elements/__tests__/CheckboxParser_test.mjs +0 -202
- package/src/parser/Semantic/Elements/__tests__/CheckboxParser_test.res +0 -250
- package/src/parser/Semantic/Elements/__tests__/CodeTextParser_manual.mjs +0 -293
- package/src/parser/Semantic/Elements/__tests__/CodeTextParser_manual.res +0 -134
- package/src/parser/Semantic/Elements/__tests__/InputParser_test.mjs +0 -253
- package/src/parser/Semantic/Elements/__tests__/InputParser_test.res +0 -304
- package/src/parser/Semantic/Elements/__tests__/LinkParser_test.mjs +0 -289
- package/src/parser/Semantic/Elements/__tests__/LinkParser_test.res +0 -402
- package/src/parser/Semantic/Elements/__tests__/TextParser_test.mjs +0 -149
- package/src/parser/Semantic/Elements/__tests__/TextParser_test.res +0 -167
- package/src/parser/Semantic/__tests__/ASTBuilder_test.mjs +0 -187
- package/src/parser/Semantic/__tests__/ASTBuilder_test.res +0 -192
- package/src/parser/Semantic/__tests__/ParserRegistry_test.mjs +0 -154
- package/src/parser/Semantic/__tests__/ParserRegistry_test.res +0 -191
- package/src/parser/Semantic/__tests__/SemanticParser_integration_test.mjs +0 -768
- package/src/parser/Semantic/__tests__/SemanticParser_integration_test.res +0 -1069
- package/src/parser/Semantic/__tests__/SemanticParser_manual.mjs +0 -1329
- package/src/parser/Semantic/__tests__/SemanticParser_manual.res +0 -544
- package/src/parser/__tests__/GridScanner_integration.test.mjs +0 -632
- package/src/parser/__tests__/GridScanner_integration.test.res +0 -816
- package/src/parser/__tests__/Performance.test.mjs +0 -244
- package/src/parser/__tests__/Performance.test.res +0 -371
- package/src/parser/__tests__/PerformanceFixtures.mjs +0 -200
- package/src/parser/__tests__/PerformanceFixtures.res +0 -284
- package/src/parser/__tests__/WyreframeParser_integration.test.mjs +0 -770
- package/src/parser/__tests__/WyreframeParser_integration.test.res +0 -1008
- package/src/parser/__tests__/fixtures/alignment-test.txt +0 -9
- package/src/parser/__tests__/fixtures/all-elements.txt +0 -16
- package/src/parser/__tests__/fixtures/login-scene.txt +0 -17
- package/src/parser/__tests__/fixtures/multi-scene.txt +0 -25
- package/src/parser/__tests__/fixtures/nested-boxes.txt +0 -15
- package/src/parser/__tests__/fixtures/simple-box.txt +0 -5
- package/src/parser/__tests__/fixtures/with-dividers.txt +0 -14
|
@@ -1,200 +0,0 @@
|
|
|
1
|
-
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
-
|
|
3
|
-
import * as Primitive_object from "@rescript/runtime/lib/es6/Primitive_object.js";
|
|
4
|
-
import * as Primitive_option from "@rescript/runtime/lib/es6/Primitive_option.js";
|
|
5
|
-
|
|
6
|
-
function generateWireframe(targetLines) {
|
|
7
|
-
let match = targetLines <= 50 ? [
|
|
8
|
-
1,
|
|
9
|
-
3,
|
|
10
|
-
6
|
|
11
|
-
] : (
|
|
12
|
-
targetLines <= 100 ? [
|
|
13
|
-
2,
|
|
14
|
-
3,
|
|
15
|
-
6
|
|
16
|
-
] : (
|
|
17
|
-
targetLines <= 200 ? [
|
|
18
|
-
2,
|
|
19
|
-
5,
|
|
20
|
-
8
|
|
21
|
-
] : (
|
|
22
|
-
targetLines <= 500 ? [
|
|
23
|
-
3,
|
|
24
|
-
7,
|
|
25
|
-
10
|
|
26
|
-
] : (
|
|
27
|
-
targetLines <= 1000 ? [
|
|
28
|
-
4,
|
|
29
|
-
10,
|
|
30
|
-
12
|
|
31
|
-
] : [
|
|
32
|
-
6,
|
|
33
|
-
12,
|
|
34
|
-
14
|
|
35
|
-
]
|
|
36
|
-
)
|
|
37
|
-
)
|
|
38
|
-
)
|
|
39
|
-
);
|
|
40
|
-
let elementsPerBox = match[2];
|
|
41
|
-
let boxesPerScene = match[1];
|
|
42
|
-
let numScenes = match[0];
|
|
43
|
-
let scenes = [];
|
|
44
|
-
for (let sceneIdx = 0; sceneIdx < numScenes; ++sceneIdx) {
|
|
45
|
-
let sceneName = `scene` + (sceneIdx + 1 | 0).toString();
|
|
46
|
-
let sceneTitle = `Scene ` + (sceneIdx + 1 | 0).toString();
|
|
47
|
-
scenes.push(`@scene: ` + sceneName);
|
|
48
|
-
scenes.push(`@title: ` + sceneTitle);
|
|
49
|
-
scenes.push("");
|
|
50
|
-
for (let boxIdx = 0; boxIdx < boxesPerScene; ++boxIdx) {
|
|
51
|
-
let boxName = `Box` + (boxIdx + 1 | 0).toString();
|
|
52
|
-
let topBorder = `+--` + boxName + "-".repeat((40 - boxName.length | 0) - 2 | 0) + `+`;
|
|
53
|
-
scenes.push(topBorder);
|
|
54
|
-
scenes.push(`|` + " ".repeat(40) + `|`);
|
|
55
|
-
for (let elemIdx = 0; elemIdx < elementsPerBox; ++elemIdx) {
|
|
56
|
-
let elementType = elemIdx % 5;
|
|
57
|
-
switch (elementType) {
|
|
58
|
-
case 0 :
|
|
59
|
-
let buttonText = `Button ` + (elemIdx + 1 | 0).toString();
|
|
60
|
-
let button = `[ ` + buttonText + ` ]`;
|
|
61
|
-
let padding = (40 - button.length | 0) / 2 | 0;
|
|
62
|
-
let line = `|` + " ".repeat(padding) + button + " ".repeat((40 - padding | 0) - button.length | 0) + `|`;
|
|
63
|
-
scenes.push(line);
|
|
64
|
-
break;
|
|
65
|
-
case 1 :
|
|
66
|
-
let fieldName = `field` + (elemIdx + 1 | 0).toString();
|
|
67
|
-
let label = `Label ` + (elemIdx + 1 | 0).toString();
|
|
68
|
-
scenes.push(`| ` + label + " ".repeat((40 - label.length | 0) - 2 | 0) + `|`);
|
|
69
|
-
scenes.push(`| #` + fieldName + " ".repeat((40 - fieldName.length | 0) - 3 | 0) + `|`);
|
|
70
|
-
break;
|
|
71
|
-
case 2 :
|
|
72
|
-
let linkText = `Link ` + (elemIdx + 1 | 0).toString();
|
|
73
|
-
let link = `"` + linkText + `"`;
|
|
74
|
-
scenes.push(`| ` + link + " ".repeat((40 - link.length | 0) - 2 | 0) + `|`);
|
|
75
|
-
break;
|
|
76
|
-
case 3 :
|
|
77
|
-
let checked = elemIdx % 2 === 0;
|
|
78
|
-
let checkboxLabel = `Option ` + (elemIdx + 1 | 0).toString();
|
|
79
|
-
let checkbox = checked ? "[x]" : "[ ]";
|
|
80
|
-
scenes.push(`| ` + checkbox + ` ` + checkboxLabel + " ".repeat(((40 - checkbox.length | 0) - checkboxLabel.length | 0) - 3 | 0) + `|`);
|
|
81
|
-
break;
|
|
82
|
-
case 4 :
|
|
83
|
-
let emphasisText = `Important ` + (elemIdx + 1 | 0).toString();
|
|
84
|
-
scenes.push(`| * ` + emphasisText + " ".repeat((40 - emphasisText.length | 0) - 4 | 0) + `|`);
|
|
85
|
-
break;
|
|
86
|
-
}
|
|
87
|
-
if (elemIdx < (elementsPerBox - 1 | 0)) {
|
|
88
|
-
scenes.push(`|` + " ".repeat(40) + `|`);
|
|
89
|
-
}
|
|
90
|
-
}
|
|
91
|
-
if (boxIdx < (boxesPerScene - 1 | 0) && boxIdx === (boxesPerScene / 2 | 0)) {
|
|
92
|
-
scenes.push(`|` + "=".repeat(40) + `|`);
|
|
93
|
-
}
|
|
94
|
-
scenes.push(`|` + " ".repeat(40) + `|`);
|
|
95
|
-
let bottomBorder = `+` + "-".repeat(40) + `+`;
|
|
96
|
-
scenes.push(bottomBorder);
|
|
97
|
-
if (boxIdx < (boxesPerScene - 1 | 0)) {
|
|
98
|
-
scenes.push("");
|
|
99
|
-
}
|
|
100
|
-
}
|
|
101
|
-
if (sceneIdx < (numScenes - 1 | 0)) {
|
|
102
|
-
scenes.push("");
|
|
103
|
-
scenes.push("---");
|
|
104
|
-
scenes.push("");
|
|
105
|
-
}
|
|
106
|
-
}
|
|
107
|
-
return scenes.join("\n");
|
|
108
|
-
}
|
|
109
|
-
|
|
110
|
-
function generateSimpleBox(widthOpt, heightOpt, nameOpt) {
|
|
111
|
-
let width = widthOpt !== undefined ? widthOpt : 20;
|
|
112
|
-
let height = heightOpt !== undefined ? heightOpt : 3;
|
|
113
|
-
let name = nameOpt !== undefined ? Primitive_option.valFromOption(nameOpt) : undefined;
|
|
114
|
-
let lines = [];
|
|
115
|
-
let totalWidth = width + 4 | 0;
|
|
116
|
-
let topBorder;
|
|
117
|
-
if (name !== undefined) {
|
|
118
|
-
let nameStr = `--` + name + `--`;
|
|
119
|
-
let padding = (totalWidth - 2 | 0) - nameStr.length | 0;
|
|
120
|
-
if (padding < 0) {
|
|
121
|
-
topBorder = `+` + "-".repeat(totalWidth - 2 | 0) + `+`;
|
|
122
|
-
} else {
|
|
123
|
-
let leftPad = padding / 2 | 0;
|
|
124
|
-
let rightPad = padding - leftPad | 0;
|
|
125
|
-
topBorder = `+` + "-".repeat(leftPad) + nameStr + "-".repeat(rightPad) + `+`;
|
|
126
|
-
}
|
|
127
|
-
} else {
|
|
128
|
-
topBorder = `+` + "-".repeat(totalWidth - 2 | 0) + `+`;
|
|
129
|
-
}
|
|
130
|
-
lines.push(topBorder);
|
|
131
|
-
for (let _for = 0; _for < height; ++_for) {
|
|
132
|
-
lines.push(`| ` + " ".repeat(width) + ` |`);
|
|
133
|
-
}
|
|
134
|
-
lines.push(`+` + "-".repeat(totalWidth - 2 | 0) + `+`);
|
|
135
|
-
return lines.join("\n");
|
|
136
|
-
}
|
|
137
|
-
|
|
138
|
-
function generateNestedBoxes(depth) {
|
|
139
|
-
let buildNested = (currentDepth, maxDepth) => {
|
|
140
|
-
if (currentDepth > maxDepth) {
|
|
141
|
-
return [];
|
|
142
|
-
}
|
|
143
|
-
let lines = [];
|
|
144
|
-
let boxName = `Level` + currentDepth.toString();
|
|
145
|
-
let boxWidth = 40 - ((currentDepth - 1 | 0) << 2) | 0;
|
|
146
|
-
let topBorder = `+--` + boxName + "-".repeat((boxWidth - boxName.length | 0) - 2 | 0) + `+`;
|
|
147
|
-
lines.push(topBorder);
|
|
148
|
-
lines.push(`|` + " ".repeat(boxWidth) + `|`);
|
|
149
|
-
if (currentDepth < maxDepth) {
|
|
150
|
-
let nested = buildNested(currentDepth + 1 | 0, maxDepth);
|
|
151
|
-
nested.forEach(nestedLine => {
|
|
152
|
-
let paddedLine = `|` + " ".repeat(2) + nestedLine + " ".repeat((boxWidth - 2 | 0) - nestedLine.length | 0) + `|`;
|
|
153
|
-
lines.push(paddedLine);
|
|
154
|
-
});
|
|
155
|
-
} else {
|
|
156
|
-
let buttonText = "[ OK ]";
|
|
157
|
-
let padding = (boxWidth - buttonText.length | 0) / 2 | 0;
|
|
158
|
-
let contentLine = `|` + " ".repeat(padding) + buttonText + " ".repeat((boxWidth - padding | 0) - buttonText.length | 0) + `|`;
|
|
159
|
-
lines.push(contentLine);
|
|
160
|
-
}
|
|
161
|
-
lines.push(`|` + " ".repeat(boxWidth) + `|`);
|
|
162
|
-
lines.push(`+` + "-".repeat(boxWidth) + `+`);
|
|
163
|
-
return lines;
|
|
164
|
-
};
|
|
165
|
-
let lines = buildNested(1, depth);
|
|
166
|
-
return lines.join("\n");
|
|
167
|
-
}
|
|
168
|
-
|
|
169
|
-
function getLineCount(wireframe) {
|
|
170
|
-
return wireframe.split("\n").length;
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
function validateWireframe(wireframe) {
|
|
174
|
-
let lines = wireframe.split("\n");
|
|
175
|
-
let openCorners = {
|
|
176
|
-
contents: 0
|
|
177
|
-
};
|
|
178
|
-
lines.forEach(line => {
|
|
179
|
-
let plusCount = 0;
|
|
180
|
-
for (let i = 0, i_finish = line.length; i < i_finish; ++i) {
|
|
181
|
-
if (Primitive_object.equal(line[i], "+")) {
|
|
182
|
-
plusCount = plusCount + 1 | 0;
|
|
183
|
-
}
|
|
184
|
-
}
|
|
185
|
-
if (plusCount >= 2) {
|
|
186
|
-
openCorners.contents = openCorners.contents + 1 | 0;
|
|
187
|
-
return;
|
|
188
|
-
}
|
|
189
|
-
});
|
|
190
|
-
return openCorners.contents > 0;
|
|
191
|
-
}
|
|
192
|
-
|
|
193
|
-
export {
|
|
194
|
-
generateWireframe,
|
|
195
|
-
generateSimpleBox,
|
|
196
|
-
generateNestedBoxes,
|
|
197
|
-
getLineCount,
|
|
198
|
-
validateWireframe,
|
|
199
|
-
}
|
|
200
|
-
/* No side effect */
|
|
@@ -1,284 +0,0 @@
|
|
|
1
|
-
// PerformanceFixtures.res
|
|
2
|
-
// Programmatic wireframe generation for performance testing
|
|
3
|
-
|
|
4
|
-
/**
|
|
5
|
-
* Generate a wireframe with approximately the target number of lines.
|
|
6
|
-
* Creates realistic nested structures with various element types.
|
|
7
|
-
*
|
|
8
|
-
* @param targetLines - Approximate number of lines to generate
|
|
9
|
-
* @returns ASCII wireframe string
|
|
10
|
-
*/
|
|
11
|
-
let generateWireframe = (targetLines: int): string => {
|
|
12
|
-
// Calculate structure to approximate target line count
|
|
13
|
-
// Each box produces roughly: 3 (borders) + 2 (empty) + elementsPerBox * 1.5 (elements + spacing) lines
|
|
14
|
-
// Each scene adds: 3 (header) + scene separators
|
|
15
|
-
// Formula: targetLines ≈ numScenes * (3 + boxesPerScene * (5 + elementsPerBox * 1.5))
|
|
16
|
-
|
|
17
|
-
// Use more granular scaling
|
|
18
|
-
let (numScenes, boxesPerScene, elementsPerBox) = if targetLines <= 50 {
|
|
19
|
-
(1, 3, 6) // ~45 lines
|
|
20
|
-
} else if targetLines <= 100 {
|
|
21
|
-
(2, 3, 6) // ~95 lines
|
|
22
|
-
} else if targetLines <= 200 {
|
|
23
|
-
(2, 5, 8) // ~185 lines
|
|
24
|
-
} else if targetLines <= 500 {
|
|
25
|
-
(3, 7, 10) // ~480 lines
|
|
26
|
-
} else if targetLines <= 1000 {
|
|
27
|
-
(4, 10, 12) // ~950 lines
|
|
28
|
-
} else {
|
|
29
|
-
(6, 12, 14) // ~2000 lines
|
|
30
|
-
}
|
|
31
|
-
|
|
32
|
-
let scenes = []
|
|
33
|
-
|
|
34
|
-
// Generate each scene
|
|
35
|
-
for sceneIdx in 0 to numScenes - 1 {
|
|
36
|
-
let sceneName = `scene${Int.toString(sceneIdx + 1)}`
|
|
37
|
-
let sceneTitle = `Scene ${Int.toString(sceneIdx + 1)}`
|
|
38
|
-
|
|
39
|
-
// Scene header
|
|
40
|
-
scenes->Array.push(`@scene: ${sceneName}`)->ignore
|
|
41
|
-
scenes->Array.push(`@title: ${sceneTitle}`)->ignore
|
|
42
|
-
scenes->Array.push("")->ignore
|
|
43
|
-
|
|
44
|
-
// Generate boxes for this scene
|
|
45
|
-
for boxIdx in 0 to boxesPerScene - 1 {
|
|
46
|
-
let boxName = `Box${Int.toString(boxIdx + 1)}`
|
|
47
|
-
let boxWidth = 40
|
|
48
|
-
|
|
49
|
-
// Box top border with name
|
|
50
|
-
// Total width = boxWidth + 2 (for the two | characters on content lines)
|
|
51
|
-
// Top border: + -- Name dashes +
|
|
52
|
-
// Content: | spaces |
|
|
53
|
-
let topBorder = `+--${boxName}${"-"->String.repeat(boxWidth - String.length(boxName) - 2)}+`
|
|
54
|
-
scenes->Array.push(topBorder)->ignore
|
|
55
|
-
|
|
56
|
-
// Empty line
|
|
57
|
-
scenes->Array.push(`|${" "->String.repeat(boxWidth)}|`)->ignore
|
|
58
|
-
|
|
59
|
-
// Generate elements
|
|
60
|
-
for elemIdx in 0 to elementsPerBox - 1 {
|
|
61
|
-
let elementType = mod(elemIdx, 5)
|
|
62
|
-
|
|
63
|
-
switch elementType {
|
|
64
|
-
| 0 => {
|
|
65
|
-
// Button
|
|
66
|
-
let buttonText = `Button ${Int.toString(elemIdx + 1)}`
|
|
67
|
-
let button = `[ ${buttonText} ]`
|
|
68
|
-
let padding = (boxWidth - String.length(button)) / 2
|
|
69
|
-
let line = `|${" "->String.repeat(padding)}${button}${" "->String.repeat(
|
|
70
|
-
boxWidth - padding - String.length(button),
|
|
71
|
-
)}|`
|
|
72
|
-
scenes->Array.push(line)->ignore
|
|
73
|
-
}
|
|
74
|
-
| 1 => {
|
|
75
|
-
// Input field
|
|
76
|
-
let fieldName = `field${Int.toString(elemIdx + 1)}`
|
|
77
|
-
let label = `Label ${Int.toString(elemIdx + 1)}`
|
|
78
|
-
scenes->Array.push(`| ${label}${" "->String.repeat(boxWidth - String.length(label) - 2)}|`)->ignore
|
|
79
|
-
scenes->Array.push(`| #${fieldName}${" "->String.repeat(
|
|
80
|
-
boxWidth - String.length(fieldName) - 3,
|
|
81
|
-
)}|`)->ignore
|
|
82
|
-
}
|
|
83
|
-
| 2 => {
|
|
84
|
-
// Link
|
|
85
|
-
let linkText = `Link ${Int.toString(elemIdx + 1)}`
|
|
86
|
-
let link = `"${linkText}"`
|
|
87
|
-
scenes->Array.push(`| ${link}${" "->String.repeat(boxWidth - String.length(link) - 2)}|`)->ignore
|
|
88
|
-
}
|
|
89
|
-
| 3 => {
|
|
90
|
-
// Checkbox
|
|
91
|
-
let checked = mod(elemIdx, 2) == 0
|
|
92
|
-
let checkboxLabel = `Option ${Int.toString(elemIdx + 1)}`
|
|
93
|
-
let checkbox = checked ? "[x]" : "[ ]"
|
|
94
|
-
scenes->Array.push(`| ${checkbox} ${checkboxLabel}${" "->String.repeat(
|
|
95
|
-
boxWidth - String.length(checkbox) - String.length(checkboxLabel) - 3,
|
|
96
|
-
)}|`)->ignore
|
|
97
|
-
}
|
|
98
|
-
| 4 => {
|
|
99
|
-
// Emphasis text
|
|
100
|
-
let emphasisText = `Important ${Int.toString(elemIdx + 1)}`
|
|
101
|
-
scenes->Array.push(`| * ${emphasisText}${" "->String.repeat(
|
|
102
|
-
boxWidth - String.length(emphasisText) - 4,
|
|
103
|
-
)}|`)->ignore
|
|
104
|
-
}
|
|
105
|
-
| _ => ()
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
// Add spacing between elements
|
|
109
|
-
if elemIdx < elementsPerBox - 1 {
|
|
110
|
-
scenes->Array.push(`|${" "->String.repeat(boxWidth)}|`)->ignore
|
|
111
|
-
}
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
// Add divider if not last box in scene
|
|
115
|
-
if boxIdx < boxesPerScene - 1 && boxIdx == boxesPerScene / 2 {
|
|
116
|
-
scenes->Array.push(`|${"="->String.repeat(boxWidth)}|`)->ignore
|
|
117
|
-
}
|
|
118
|
-
|
|
119
|
-
// Empty line
|
|
120
|
-
scenes->Array.push(`|${" "->String.repeat(boxWidth)}|`)->ignore
|
|
121
|
-
|
|
122
|
-
// Box bottom border
|
|
123
|
-
let bottomBorder = `+${"-"->String.repeat(boxWidth)}+`
|
|
124
|
-
scenes->Array.push(bottomBorder)->ignore
|
|
125
|
-
|
|
126
|
-
// Space between boxes
|
|
127
|
-
if boxIdx < boxesPerScene - 1 {
|
|
128
|
-
scenes->Array.push("")->ignore
|
|
129
|
-
}
|
|
130
|
-
}
|
|
131
|
-
|
|
132
|
-
// Space between scenes
|
|
133
|
-
if sceneIdx < numScenes - 1 {
|
|
134
|
-
scenes->Array.push("")->ignore
|
|
135
|
-
scenes->Array.push("---")->ignore
|
|
136
|
-
scenes->Array.push("")->ignore
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
|
|
140
|
-
scenes->Array.joinWith("\n")
|
|
141
|
-
}
|
|
142
|
-
|
|
143
|
-
/**
|
|
144
|
-
* Generate a simple box for basic testing
|
|
145
|
-
*
|
|
146
|
-
* @param width - Box width (interior)
|
|
147
|
-
* @param height - Box height (interior lines)
|
|
148
|
-
* @param name - Optional box name
|
|
149
|
-
* @returns ASCII box string
|
|
150
|
-
*/
|
|
151
|
-
let generateSimpleBox = (~width: int=20, ~height: int=3, ~name: option<string>=None): string => {
|
|
152
|
-
let lines = []
|
|
153
|
-
|
|
154
|
-
// Total width = width + 4 (for "| " and " |" on content lines)
|
|
155
|
-
let totalWidth = width + 4
|
|
156
|
-
|
|
157
|
-
// Top border
|
|
158
|
-
let topBorder = switch name {
|
|
159
|
-
| Some(n) => {
|
|
160
|
-
let nameStr = `--${n}--`
|
|
161
|
-
let padding = totalWidth - 2 - String.length(nameStr) // -2 for the two +
|
|
162
|
-
if padding < 0 {
|
|
163
|
-
`+${"-"->String.repeat(totalWidth - 2)}+` // Name too long, use plain border
|
|
164
|
-
} else {
|
|
165
|
-
let leftPad = padding / 2
|
|
166
|
-
let rightPad = padding - leftPad
|
|
167
|
-
`+${"-"->String.repeat(leftPad)}${nameStr}${"-"->String.repeat(rightPad)}+`
|
|
168
|
-
}
|
|
169
|
-
}
|
|
170
|
-
| None => `+${"-"->String.repeat(totalWidth - 2)}+`
|
|
171
|
-
}
|
|
172
|
-
lines->Array.push(topBorder)->ignore
|
|
173
|
-
|
|
174
|
-
// Interior lines
|
|
175
|
-
for _ in 0 to height - 1 {
|
|
176
|
-
lines->Array.push(`| ${" "->String.repeat(width)} |`)->ignore
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
// Bottom border
|
|
180
|
-
lines->Array.push(`+${"-"->String.repeat(totalWidth - 2)}+`)->ignore
|
|
181
|
-
|
|
182
|
-
lines->Array.joinWith("\n")
|
|
183
|
-
}
|
|
184
|
-
|
|
185
|
-
/**
|
|
186
|
-
* Generate nested boxes for hierarchy testing
|
|
187
|
-
*
|
|
188
|
-
* @param depth - Nesting depth (1 = no nesting, 2 = one level, etc.)
|
|
189
|
-
* @returns ASCII wireframe with nested boxes
|
|
190
|
-
*/
|
|
191
|
-
let generateNestedBoxes = (depth: int): string => {
|
|
192
|
-
// Generate properly nested boxes where inner boxes are contained within outer boxes
|
|
193
|
-
// Each level reduces width by 4 (2 for indent + 2 for padding)
|
|
194
|
-
let baseWidth = 40
|
|
195
|
-
|
|
196
|
-
let rec buildNested = (currentDepth: int, maxDepth: int): array<string> => {
|
|
197
|
-
if currentDepth > maxDepth {
|
|
198
|
-
[]
|
|
199
|
-
} else {
|
|
200
|
-
let lines = []
|
|
201
|
-
let boxName = `Level${Int.toString(currentDepth)}`
|
|
202
|
-
let boxWidth = baseWidth - (currentDepth - 1) * 4
|
|
203
|
-
let innerPadding = 2 // Padding inside box for nested content
|
|
204
|
-
|
|
205
|
-
// Top border
|
|
206
|
-
let topBorder = `+--${boxName}${"-"->String.repeat(boxWidth - String.length(boxName) - 2)}+`
|
|
207
|
-
lines->Array.push(topBorder)->ignore
|
|
208
|
-
|
|
209
|
-
// Empty line
|
|
210
|
-
lines->Array.push(`|${" "->String.repeat(boxWidth)}|`)->ignore
|
|
211
|
-
|
|
212
|
-
// Nested content
|
|
213
|
-
if currentDepth < maxDepth {
|
|
214
|
-
let nested = buildNested(currentDepth + 1, maxDepth)
|
|
215
|
-
// Wrap each nested line with outer box borders
|
|
216
|
-
nested->Array.forEach(nestedLine => {
|
|
217
|
-
let paddedLine = `|${" "->String.repeat(innerPadding)}${nestedLine}${" "->String.repeat(boxWidth - innerPadding - String.length(nestedLine))}|`
|
|
218
|
-
lines->Array.push(paddedLine)->ignore
|
|
219
|
-
})
|
|
220
|
-
} else {
|
|
221
|
-
// Leaf content - add a button
|
|
222
|
-
let buttonText = "[ OK ]"
|
|
223
|
-
let padding = (boxWidth - String.length(buttonText)) / 2
|
|
224
|
-
let contentLine = `|${" "->String.repeat(padding)}${buttonText}${" "->String.repeat(boxWidth - padding - String.length(buttonText))}|`
|
|
225
|
-
lines->Array.push(contentLine)->ignore
|
|
226
|
-
}
|
|
227
|
-
|
|
228
|
-
// Empty line
|
|
229
|
-
lines->Array.push(`|${" "->String.repeat(boxWidth)}|`)->ignore
|
|
230
|
-
|
|
231
|
-
// Bottom border
|
|
232
|
-
lines->Array.push(`+${"-"->String.repeat(boxWidth)}+`)->ignore
|
|
233
|
-
|
|
234
|
-
lines
|
|
235
|
-
}
|
|
236
|
-
}
|
|
237
|
-
|
|
238
|
-
let lines = buildNested(1, depth)
|
|
239
|
-
lines->Array.joinWith("\n")
|
|
240
|
-
}
|
|
241
|
-
|
|
242
|
-
/**
|
|
243
|
-
* Get the approximate line count of a generated wireframe
|
|
244
|
-
* Useful for validation in tests
|
|
245
|
-
*
|
|
246
|
-
* @param wireframe - ASCII wireframe string
|
|
247
|
-
* @returns Number of lines
|
|
248
|
-
*/
|
|
249
|
-
let getLineCount = (wireframe: string): int => {
|
|
250
|
-
wireframe->String.split("\n")->Array.length
|
|
251
|
-
}
|
|
252
|
-
|
|
253
|
-
/**
|
|
254
|
-
* Validate that a wireframe has basic syntactic correctness
|
|
255
|
-
* Checks for balanced borders, no empty lines in boxes, etc.
|
|
256
|
-
*
|
|
257
|
-
* @param wireframe - ASCII wireframe string
|
|
258
|
-
* @returns true if valid, false otherwise
|
|
259
|
-
*/
|
|
260
|
-
let validateWireframe = (wireframe: string): bool => {
|
|
261
|
-
let lines = wireframe->String.split("\n")
|
|
262
|
-
|
|
263
|
-
// Check that corner characters are balanced
|
|
264
|
-
let openCorners = ref(0)
|
|
265
|
-
let closeCorners = ref(0)
|
|
266
|
-
|
|
267
|
-
lines->Array.forEach(line => {
|
|
268
|
-
// Count '+' characters
|
|
269
|
-
let plusCount = ref(0)
|
|
270
|
-
for i in 0 to String.length(line) - 1 {
|
|
271
|
-
if String.get(line, i) == Some("+") {
|
|
272
|
-
plusCount := plusCount.contents + 1
|
|
273
|
-
}
|
|
274
|
-
}
|
|
275
|
-
|
|
276
|
-
// Assume pairs of '+' (start and end of border)
|
|
277
|
-
if plusCount.contents >= 2 {
|
|
278
|
-
openCorners := openCorners.contents + 1
|
|
279
|
-
}
|
|
280
|
-
})
|
|
281
|
-
|
|
282
|
-
// Basic validation: should have at least some box structure
|
|
283
|
-
openCorners.contents > 0
|
|
284
|
-
}
|