wyreframe 0.1.0
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/README.md +123 -0
- package/dist/index.d.ts +267 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +195 -0
- package/dist/index.js.map +1 -0
- package/package.json +63 -0
- package/src/parser/Core/Bounds.mjs +61 -0
- package/src/parser/Core/Bounds.res +65 -0
- package/src/parser/Core/Grid.mjs +268 -0
- package/src/parser/Core/Grid.res +265 -0
- package/src/parser/Core/Position.mjs +83 -0
- package/src/parser/Core/Position.res +54 -0
- package/src/parser/Core/Types.mjs +435 -0
- package/src/parser/Core/Types.res +331 -0
- package/src/parser/Core/__tests__/Bounds_test.mjs +326 -0
- package/src/parser/Core/__tests__/Bounds_test.res +412 -0
- package/src/parser/Core/__tests__/Grid_test.mjs +322 -0
- package/src/parser/Core/__tests__/Grid_test.res +319 -0
- package/src/parser/Core/__tests__/Types_test.mjs +614 -0
- package/src/parser/Core/__tests__/Types_test.res +650 -0
- package/src/parser/Detector/BoxTracer.mjs +302 -0
- package/src/parser/Detector/BoxTracer.res +374 -0
- package/src/parser/Detector/HierarchyBuilder.mjs +158 -0
- package/src/parser/Detector/HierarchyBuilder.res +315 -0
- package/src/parser/Detector/ShapeDetector.mjs +134 -0
- package/src/parser/Detector/ShapeDetector.res +236 -0
- package/src/parser/Detector/__tests__/BoxTracer_test.mjs +70 -0
- package/src/parser/Detector/__tests__/BoxTracer_test.res +92 -0
- package/src/parser/Detector/__tests__/HierarchyBuilder_test.mjs +489 -0
- package/src/parser/Detector/__tests__/HierarchyBuilder_test.res +849 -0
- package/src/parser/Detector/__tests__/ShapeDetector_test.mjs +377 -0
- package/src/parser/Detector/__tests__/ShapeDetector_test.res +563 -0
- package/src/parser/Errors/ErrorContext.mjs +106 -0
- package/src/parser/Errors/ErrorContext.res +191 -0
- package/src/parser/Errors/ErrorMessages.mjs +289 -0
- package/src/parser/Errors/ErrorMessages.res +303 -0
- package/src/parser/Errors/ErrorTypes.mjs +105 -0
- package/src/parser/Errors/ErrorTypes.res +169 -0
- package/src/parser/Interactions/InteractionMerger.mjs +266 -0
- package/src/parser/Interactions/InteractionMerger.res +450 -0
- package/src/parser/Interactions/InteractionParser.mjs +88 -0
- package/src/parser/Interactions/InteractionParser.res +127 -0
- package/src/parser/Interactions/SimpleInteractionParser.mjs +278 -0
- package/src/parser/Interactions/SimpleInteractionParser.res +262 -0
- package/src/parser/Interactions/__tests__/InteractionMerger_test.mjs +576 -0
- package/src/parser/Interactions/__tests__/InteractionMerger_test.res +646 -0
- package/src/parser/Parser.gen.tsx +96 -0
- package/src/parser/Parser.mjs +212 -0
- package/src/parser/Parser.res +481 -0
- package/src/parser/Scanner/__tests__/Grid_manual.mjs +214 -0
- package/src/parser/Scanner/__tests__/Grid_manual.res +141 -0
- package/src/parser/Semantic/ASTBuilder.mjs +197 -0
- package/src/parser/Semantic/ASTBuilder.res +288 -0
- package/src/parser/Semantic/AlignmentCalc.mjs +41 -0
- package/src/parser/Semantic/AlignmentCalc.res +104 -0
- package/src/parser/Semantic/Elements/ButtonParser.mjs +58 -0
- package/src/parser/Semantic/Elements/ButtonParser.res +131 -0
- package/src/parser/Semantic/Elements/CheckboxParser.mjs +58 -0
- package/src/parser/Semantic/Elements/CheckboxParser.res +79 -0
- package/src/parser/Semantic/Elements/CodeTextParser.mjs +50 -0
- package/src/parser/Semantic/Elements/CodeTextParser.res +111 -0
- package/src/parser/Semantic/Elements/ElementParser.mjs +15 -0
- package/src/parser/Semantic/Elements/ElementParser.res +83 -0
- package/src/parser/Semantic/Elements/EmphasisParser.mjs +46 -0
- package/src/parser/Semantic/Elements/EmphasisParser.res +67 -0
- package/src/parser/Semantic/Elements/InputParser.mjs +41 -0
- package/src/parser/Semantic/Elements/InputParser.res +97 -0
- package/src/parser/Semantic/Elements/LinkParser.mjs +60 -0
- package/src/parser/Semantic/Elements/LinkParser.res +156 -0
- package/src/parser/Semantic/Elements/TextParser.mjs +19 -0
- package/src/parser/Semantic/Elements/TextParser.res +42 -0
- package/src/parser/Semantic/Elements/__tests__/ButtonParser_test.mjs +189 -0
- package/src/parser/Semantic/Elements/__tests__/ButtonParser_test.res +257 -0
- package/src/parser/Semantic/Elements/__tests__/CheckboxParser_test.mjs +202 -0
- package/src/parser/Semantic/Elements/__tests__/CheckboxParser_test.res +250 -0
- package/src/parser/Semantic/Elements/__tests__/CodeTextParser_manual.mjs +293 -0
- package/src/parser/Semantic/Elements/__tests__/CodeTextParser_manual.res +134 -0
- package/src/parser/Semantic/Elements/__tests__/InputParser_test.mjs +253 -0
- package/src/parser/Semantic/Elements/__tests__/InputParser_test.res +304 -0
- package/src/parser/Semantic/Elements/__tests__/LinkParser_test.mjs +289 -0
- package/src/parser/Semantic/Elements/__tests__/LinkParser_test.res +402 -0
- package/src/parser/Semantic/Elements/__tests__/TextParser_test.mjs +149 -0
- package/src/parser/Semantic/Elements/__tests__/TextParser_test.res +167 -0
- package/src/parser/Semantic/ParserRegistry.mjs +82 -0
- package/src/parser/Semantic/ParserRegistry.res +145 -0
- package/src/parser/Semantic/SemanticParser.mjs +850 -0
- package/src/parser/Semantic/SemanticParser.res +1368 -0
- package/src/parser/Semantic/__tests__/ASTBuilder_test.mjs +187 -0
- package/src/parser/Semantic/__tests__/ASTBuilder_test.res +192 -0
- package/src/parser/Semantic/__tests__/ParserRegistry_test.mjs +154 -0
- package/src/parser/Semantic/__tests__/ParserRegistry_test.res +191 -0
- package/src/parser/Semantic/__tests__/SemanticParser_integration_test.mjs +768 -0
- package/src/parser/Semantic/__tests__/SemanticParser_integration_test.res +1069 -0
- package/src/parser/Semantic/__tests__/SemanticParser_manual.mjs +1329 -0
- package/src/parser/Semantic/__tests__/SemanticParser_manual.res +544 -0
- package/src/parser/TestMain.mjs +21 -0
- package/src/parser/TestMain.res +14 -0
- package/src/parser/TextExtractor.mjs +179 -0
- package/src/parser/TextExtractor.res +264 -0
- package/src/parser/__tests__/GridScanner_integration.test.mjs +632 -0
- package/src/parser/__tests__/GridScanner_integration.test.res +816 -0
- package/src/parser/__tests__/Performance.test.mjs +244 -0
- package/src/parser/__tests__/Performance.test.res +371 -0
- package/src/parser/__tests__/PerformanceFixtures.mjs +200 -0
- package/src/parser/__tests__/PerformanceFixtures.res +284 -0
- package/src/parser/__tests__/WyreframeParser_integration.test.mjs +770 -0
- package/src/parser/__tests__/WyreframeParser_integration.test.res +1008 -0
- package/src/parser/__tests__/fixtures/alignment-test.txt +9 -0
- package/src/parser/__tests__/fixtures/all-elements.txt +16 -0
- package/src/parser/__tests__/fixtures/login-scene.txt +17 -0
- package/src/parser/__tests__/fixtures/multi-scene.txt +25 -0
- package/src/parser/__tests__/fixtures/nested-boxes.txt +15 -0
- package/src/parser/__tests__/fixtures/simple-box.txt +5 -0
- package/src/parser/__tests__/fixtures/with-dividers.txt +14 -0
- package/src/renderer/Renderer.gen.tsx +32 -0
- package/src/renderer/Renderer.mjs +391 -0
- package/src/renderer/Renderer.res +558 -0
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
// Generated by ReScript, PLEASE EDIT WITH CARE
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
function isWireframeLine(line) {
|
|
5
|
+
let trimmed = line.trim();
|
|
6
|
+
if (trimmed === "") {
|
|
7
|
+
return false;
|
|
8
|
+
}
|
|
9
|
+
let boxBorder = /^\+[-=]+\+/;
|
|
10
|
+
let boxSide = /^\|.*\|$/;
|
|
11
|
+
let namedBorder = /^\+--[^-].*--\+/;
|
|
12
|
+
let startsWithPlus = trimmed.startsWith("+");
|
|
13
|
+
let startsWithPipe = trimmed.startsWith("|");
|
|
14
|
+
if (boxBorder.test(trimmed) || boxSide.test(trimmed) || namedBorder.test(trimmed) || startsWithPlus && trimmed.includes("-")) {
|
|
15
|
+
return true;
|
|
16
|
+
} else if (startsWithPipe) {
|
|
17
|
+
return trimmed.endsWith("|");
|
|
18
|
+
} else {
|
|
19
|
+
return false;
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function isDirectiveLine(line) {
|
|
24
|
+
let trimmed = line.trim();
|
|
25
|
+
if (trimmed.startsWith("@scene:") || trimmed.startsWith("@title:") || trimmed.startsWith("@device:")) {
|
|
26
|
+
return true;
|
|
27
|
+
} else {
|
|
28
|
+
return trimmed.startsWith("@transition:");
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function isSceneSeparator(line) {
|
|
33
|
+
return line.trim() === "---";
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function isInteractionSelector(line) {
|
|
37
|
+
let trimmed = line.trim();
|
|
38
|
+
let inputSelector = /^#[\w-]+:$/;
|
|
39
|
+
let buttonSelector = /^\[.+\]:$/;
|
|
40
|
+
let linkSelector = /^\"[^\"]+\":$/;
|
|
41
|
+
if (inputSelector.test(trimmed) || buttonSelector.test(trimmed)) {
|
|
42
|
+
return true;
|
|
43
|
+
} else {
|
|
44
|
+
return linkSelector.test(trimmed);
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function isInteractionProperty(line) {
|
|
49
|
+
let hasIndent = line.length > 0 && (line.startsWith(" ") || line.startsWith("\t"));
|
|
50
|
+
if (!hasIndent) {
|
|
51
|
+
return false;
|
|
52
|
+
}
|
|
53
|
+
let trimmed = line.trim();
|
|
54
|
+
let propertyPattern = /^[\w@-]+:?\s*(->|\S)/;
|
|
55
|
+
if (trimmed !== "") {
|
|
56
|
+
return propertyPattern.test(trimmed);
|
|
57
|
+
} else {
|
|
58
|
+
return false;
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function classifyLine(line) {
|
|
63
|
+
let trimmed = line.trim();
|
|
64
|
+
if (trimmed === "") {
|
|
65
|
+
return "EmptyLine";
|
|
66
|
+
} else if (line.trim() === "---") {
|
|
67
|
+
return "SceneSeparator";
|
|
68
|
+
} else if (isDirectiveLine(line)) {
|
|
69
|
+
return "Directive";
|
|
70
|
+
} else if (isInteractionSelector(line)) {
|
|
71
|
+
return "InteractionSelector";
|
|
72
|
+
} else if (isInteractionProperty(line)) {
|
|
73
|
+
return "InteractionProperty";
|
|
74
|
+
} else if (isWireframeLine(line)) {
|
|
75
|
+
return "Wireframe";
|
|
76
|
+
} else {
|
|
77
|
+
return "Noise";
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function extract(text) {
|
|
82
|
+
let lines = text.split("\n");
|
|
83
|
+
let wireframeLines = [];
|
|
84
|
+
let interactionLines = [];
|
|
85
|
+
let context = {
|
|
86
|
+
contents: "Initial"
|
|
87
|
+
};
|
|
88
|
+
let currentSceneId = {
|
|
89
|
+
contents: undefined
|
|
90
|
+
};
|
|
91
|
+
let interactionHasSceneHeader = {
|
|
92
|
+
contents: false
|
|
93
|
+
};
|
|
94
|
+
lines.forEach(line => {
|
|
95
|
+
let lineType = classifyLine(line);
|
|
96
|
+
switch (lineType) {
|
|
97
|
+
case "Wireframe" :
|
|
98
|
+
wireframeLines.push(line);
|
|
99
|
+
context.contents = "InWireframe";
|
|
100
|
+
return;
|
|
101
|
+
case "Directive" :
|
|
102
|
+
wireframeLines.push(line);
|
|
103
|
+
let trimmed = line.trim();
|
|
104
|
+
if (trimmed.startsWith("@scene:")) {
|
|
105
|
+
let sceneId = trimmed.slice(7).trim();
|
|
106
|
+
currentSceneId.contents = sceneId;
|
|
107
|
+
interactionHasSceneHeader.contents = false;
|
|
108
|
+
}
|
|
109
|
+
context.contents = "InWireframe";
|
|
110
|
+
return;
|
|
111
|
+
case "SceneSeparator" :
|
|
112
|
+
wireframeLines.push(line);
|
|
113
|
+
if (interactionLines.length > 0) {
|
|
114
|
+
interactionLines.push(line);
|
|
115
|
+
}
|
|
116
|
+
context.contents = "Initial";
|
|
117
|
+
interactionHasSceneHeader.contents = false;
|
|
118
|
+
return;
|
|
119
|
+
case "InteractionSelector" :
|
|
120
|
+
let sceneId$1 = currentSceneId.contents;
|
|
121
|
+
if (sceneId$1 !== undefined && !interactionHasSceneHeader.contents) {
|
|
122
|
+
interactionLines.push("@scene: " + sceneId$1);
|
|
123
|
+
interactionLines.push("");
|
|
124
|
+
interactionHasSceneHeader.contents = true;
|
|
125
|
+
}
|
|
126
|
+
interactionLines.push(line);
|
|
127
|
+
context.contents = "InInteraction";
|
|
128
|
+
return;
|
|
129
|
+
case "InteractionProperty" :
|
|
130
|
+
if (context.contents === "InInteraction") {
|
|
131
|
+
interactionLines.push(line);
|
|
132
|
+
return;
|
|
133
|
+
} else {
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
case "EmptyLine" :
|
|
137
|
+
let match = context.contents;
|
|
138
|
+
switch (match) {
|
|
139
|
+
case "Initial" :
|
|
140
|
+
return;
|
|
141
|
+
case "InWireframe" :
|
|
142
|
+
wireframeLines.push(line);
|
|
143
|
+
return;
|
|
144
|
+
case "InInteraction" :
|
|
145
|
+
interactionLines.push(line);
|
|
146
|
+
return;
|
|
147
|
+
}
|
|
148
|
+
case "Noise" :
|
|
149
|
+
return;
|
|
150
|
+
}
|
|
151
|
+
});
|
|
152
|
+
return {
|
|
153
|
+
wireframe: wireframeLines.join("\n"),
|
|
154
|
+
interactions: interactionLines.join("\n").trim()
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
function hasWireframe(text) {
|
|
159
|
+
let lines = text.split("\n");
|
|
160
|
+
return lines.some(isWireframeLine);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
function hasInteractions(text) {
|
|
164
|
+
let lines = text.split("\n");
|
|
165
|
+
return lines.some(isInteractionSelector);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
export {
|
|
169
|
+
isWireframeLine,
|
|
170
|
+
isDirectiveLine,
|
|
171
|
+
isSceneSeparator,
|
|
172
|
+
isInteractionSelector,
|
|
173
|
+
isInteractionProperty,
|
|
174
|
+
classifyLine,
|
|
175
|
+
extract,
|
|
176
|
+
hasWireframe,
|
|
177
|
+
hasInteractions,
|
|
178
|
+
}
|
|
179
|
+
/* No side effect */
|
|
@@ -0,0 +1,264 @@
|
|
|
1
|
+
// TextExtractor.res
|
|
2
|
+
// Intelligent extraction of wireframe and interaction content from mixed text
|
|
3
|
+
// Supports markdown, comments, and other noise in the input
|
|
4
|
+
|
|
5
|
+
// ============================================================================
|
|
6
|
+
// Pattern Detection
|
|
7
|
+
// ============================================================================
|
|
8
|
+
|
|
9
|
+
/**
|
|
10
|
+
* Check if a line is part of an ASCII wireframe.
|
|
11
|
+
* Matches: +---+, |...|, +===+, box borders
|
|
12
|
+
*/
|
|
13
|
+
let isWireframeLine = (line: string): bool => {
|
|
14
|
+
let trimmed = line->String.trim
|
|
15
|
+
|
|
16
|
+
// Empty lines within wireframe context are preserved
|
|
17
|
+
if trimmed === "" {
|
|
18
|
+
false // Will be handled by context
|
|
19
|
+
} else {
|
|
20
|
+
// Box top/bottom border: +---+ or +===+
|
|
21
|
+
let boxBorder = %re("/^\+[-=]+\+/")
|
|
22
|
+
// Box side with content: | ... |
|
|
23
|
+
let boxSide = %re("/^\|.*\|$/")
|
|
24
|
+
// Named box border: +--Name--+
|
|
25
|
+
let namedBorder = %re("/^\+--[^-].*--\+/")
|
|
26
|
+
// Partial box patterns (for multi-line detection)
|
|
27
|
+
let startsWithPlus = trimmed->String.startsWith("+")
|
|
28
|
+
let startsWithPipe = trimmed->String.startsWith("|")
|
|
29
|
+
|
|
30
|
+
Js.Re.test_(boxBorder, trimmed) ||
|
|
31
|
+
Js.Re.test_(boxSide, trimmed) ||
|
|
32
|
+
Js.Re.test_(namedBorder, trimmed) ||
|
|
33
|
+
(startsWithPlus && trimmed->String.includes("-")) ||
|
|
34
|
+
(startsWithPipe && trimmed->String.endsWith("|"))
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
/**
|
|
39
|
+
* Check if a line is a scene/title/device directive.
|
|
40
|
+
* Matches: @scene:, @title:, @device:, @transition:
|
|
41
|
+
*/
|
|
42
|
+
let isDirectiveLine = (line: string): bool => {
|
|
43
|
+
let trimmed = line->String.trim
|
|
44
|
+
trimmed->String.startsWith("@scene:") ||
|
|
45
|
+
trimmed->String.startsWith("@title:") ||
|
|
46
|
+
trimmed->String.startsWith("@device:") ||
|
|
47
|
+
trimmed->String.startsWith("@transition:")
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
/**
|
|
51
|
+
* Check if a line is a scene separator.
|
|
52
|
+
*/
|
|
53
|
+
let isSceneSeparator = (line: string): bool => {
|
|
54
|
+
line->String.trim === "---"
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
/**
|
|
58
|
+
* Check if a line starts an interaction block.
|
|
59
|
+
* Matches: #id:, [Button]:, "Link":
|
|
60
|
+
*/
|
|
61
|
+
let isInteractionSelector = (line: string): bool => {
|
|
62
|
+
let trimmed = line->String.trim
|
|
63
|
+
|
|
64
|
+
// Input selector: #email:
|
|
65
|
+
let inputSelector = %re("/^#[\w-]+:$/")
|
|
66
|
+
// Button selector: [Button Text]:
|
|
67
|
+
let buttonSelector = %re("/^\[.+\]:$/")
|
|
68
|
+
// Link selector: "Link Text":
|
|
69
|
+
let linkSelector = %re("/^\"[^\"]+\":$/")
|
|
70
|
+
|
|
71
|
+
Js.Re.test_(inputSelector, trimmed) ||
|
|
72
|
+
Js.Re.test_(buttonSelector, trimmed) ||
|
|
73
|
+
Js.Re.test_(linkSelector, trimmed)
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Check if a line is an indented interaction property.
|
|
78
|
+
* Matches: lines starting with 2+ spaces followed by property
|
|
79
|
+
*/
|
|
80
|
+
let isInteractionProperty = (line: string): bool => {
|
|
81
|
+
// Must start with whitespace (indentation)
|
|
82
|
+
let hasIndent = line->String.length > 0 &&
|
|
83
|
+
(line->String.startsWith(" ") || line->String.startsWith("\t"))
|
|
84
|
+
|
|
85
|
+
if !hasIndent {
|
|
86
|
+
false
|
|
87
|
+
} else {
|
|
88
|
+
let trimmed = line->String.trim
|
|
89
|
+
// Property patterns: key: value, @click -> action
|
|
90
|
+
let propertyPattern = %re("/^[\w@-]+:?\s*(->|\S)/")
|
|
91
|
+
trimmed !== "" && Js.Re.test_(propertyPattern, trimmed)
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// ============================================================================
|
|
96
|
+
// Content Classification
|
|
97
|
+
// ============================================================================
|
|
98
|
+
|
|
99
|
+
type lineType =
|
|
100
|
+
| Wireframe
|
|
101
|
+
| Directive
|
|
102
|
+
| SceneSeparator
|
|
103
|
+
| InteractionSelector
|
|
104
|
+
| InteractionProperty
|
|
105
|
+
| EmptyLine
|
|
106
|
+
| Noise
|
|
107
|
+
|
|
108
|
+
/**
|
|
109
|
+
* Classify a single line.
|
|
110
|
+
*/
|
|
111
|
+
let classifyLine = (line: string): lineType => {
|
|
112
|
+
let trimmed = line->String.trim
|
|
113
|
+
|
|
114
|
+
if trimmed === "" {
|
|
115
|
+
EmptyLine
|
|
116
|
+
} else if isSceneSeparator(line) {
|
|
117
|
+
SceneSeparator
|
|
118
|
+
} else if isDirectiveLine(line) {
|
|
119
|
+
Directive
|
|
120
|
+
} else if isInteractionSelector(line) {
|
|
121
|
+
InteractionSelector
|
|
122
|
+
} else if isInteractionProperty(line) {
|
|
123
|
+
InteractionProperty
|
|
124
|
+
} else if isWireframeLine(line) {
|
|
125
|
+
Wireframe
|
|
126
|
+
} else {
|
|
127
|
+
Noise
|
|
128
|
+
}
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// ============================================================================
|
|
132
|
+
// Extraction State Machine
|
|
133
|
+
// ============================================================================
|
|
134
|
+
|
|
135
|
+
type extractionContext =
|
|
136
|
+
| Initial
|
|
137
|
+
| InWireframe
|
|
138
|
+
| InInteraction
|
|
139
|
+
|
|
140
|
+
type extractedContent = {
|
|
141
|
+
wireframe: string,
|
|
142
|
+
interactions: string,
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
/**
|
|
146
|
+
* Extract wireframe and interaction content from mixed text.
|
|
147
|
+
*
|
|
148
|
+
* Algorithm:
|
|
149
|
+
* 1. Process line by line with context awareness
|
|
150
|
+
* 2. Directives and scene separators go to wireframe
|
|
151
|
+
* 3. ASCII box patterns go to wireframe
|
|
152
|
+
* 4. Selector + indented properties go to interactions
|
|
153
|
+
* 5. Empty lines are context-sensitive
|
|
154
|
+
* 6. Everything else is ignored (noise)
|
|
155
|
+
*/
|
|
156
|
+
let extract = (text: string): extractedContent => {
|
|
157
|
+
let lines = text->String.split("\n")
|
|
158
|
+
|
|
159
|
+
let wireframeLines: array<string> = []
|
|
160
|
+
let interactionLines: array<string> = []
|
|
161
|
+
let context = ref(Initial)
|
|
162
|
+
let currentSceneId = ref(None)
|
|
163
|
+
let interactionHasSceneHeader = ref(false)
|
|
164
|
+
|
|
165
|
+
lines->Array.forEach(line => {
|
|
166
|
+
let lineType = classifyLine(line)
|
|
167
|
+
|
|
168
|
+
switch lineType {
|
|
169
|
+
| SceneSeparator => {
|
|
170
|
+
// Scene separator goes to both
|
|
171
|
+
wireframeLines->Array.push(line)->ignore
|
|
172
|
+
if interactionLines->Array.length > 0 {
|
|
173
|
+
interactionLines->Array.push(line)->ignore
|
|
174
|
+
}
|
|
175
|
+
context := Initial
|
|
176
|
+
interactionHasSceneHeader := false
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
| Directive => {
|
|
180
|
+
// Directives go to wireframe
|
|
181
|
+
wireframeLines->Array.push(line)->ignore
|
|
182
|
+
|
|
183
|
+
// Track current scene for interactions
|
|
184
|
+
let trimmed = line->String.trim
|
|
185
|
+
if trimmed->String.startsWith("@scene:") {
|
|
186
|
+
let sceneId = trimmed
|
|
187
|
+
->String.sliceToEnd(~start=7)
|
|
188
|
+
->String.trim
|
|
189
|
+
currentSceneId := Some(sceneId)
|
|
190
|
+
interactionHasSceneHeader := false
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
context := InWireframe
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
| Wireframe => {
|
|
197
|
+
wireframeLines->Array.push(line)->ignore
|
|
198
|
+
context := InWireframe
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
| InteractionSelector => {
|
|
202
|
+
// Add scene header to interactions if needed
|
|
203
|
+
switch currentSceneId.contents {
|
|
204
|
+
| Some(sceneId) if !interactionHasSceneHeader.contents => {
|
|
205
|
+
interactionLines->Array.push("@scene: " ++ sceneId)->ignore
|
|
206
|
+
interactionLines->Array.push("")->ignore
|
|
207
|
+
interactionHasSceneHeader := true
|
|
208
|
+
}
|
|
209
|
+
| _ => ()
|
|
210
|
+
}
|
|
211
|
+
|
|
212
|
+
interactionLines->Array.push(line)->ignore
|
|
213
|
+
context := InInteraction
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
| InteractionProperty => {
|
|
217
|
+
// Only add if we're in an interaction context
|
|
218
|
+
if context.contents === InInteraction {
|
|
219
|
+
interactionLines->Array.push(line)->ignore
|
|
220
|
+
}
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
| EmptyLine => {
|
|
224
|
+
// Empty lines are context-sensitive
|
|
225
|
+
switch context.contents {
|
|
226
|
+
| InWireframe => wireframeLines->Array.push(line)->ignore
|
|
227
|
+
| InInteraction => interactionLines->Array.push(line)->ignore
|
|
228
|
+
| Initial => () // Ignore leading empty lines
|
|
229
|
+
}
|
|
230
|
+
}
|
|
231
|
+
|
|
232
|
+
| Noise => {
|
|
233
|
+
// Ignore noise (markdown, comments, etc.)
|
|
234
|
+
// But preserve empty-like structure in wireframe context
|
|
235
|
+
if context.contents === InWireframe {
|
|
236
|
+
// Check if this might be text content inside a box
|
|
237
|
+
// (text that doesn't match wireframe patterns but is between | |)
|
|
238
|
+
()
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
}
|
|
242
|
+
})
|
|
243
|
+
|
|
244
|
+
{
|
|
245
|
+
wireframe: wireframeLines->Array.join("\n"),
|
|
246
|
+
interactions: interactionLines->Array.join("\n")->String.trim,
|
|
247
|
+
}
|
|
248
|
+
}
|
|
249
|
+
|
|
250
|
+
/**
|
|
251
|
+
* Check if text contains any wireframe content.
|
|
252
|
+
*/
|
|
253
|
+
let hasWireframe = (text: string): bool => {
|
|
254
|
+
let lines = text->String.split("\n")
|
|
255
|
+
lines->Array.some(line => isWireframeLine(line))
|
|
256
|
+
}
|
|
257
|
+
|
|
258
|
+
/**
|
|
259
|
+
* Check if text contains any interaction content.
|
|
260
|
+
*/
|
|
261
|
+
let hasInteractions = (text: string): bool => {
|
|
262
|
+
let lines = text->String.split("\n")
|
|
263
|
+
lines->Array.some(line => isInteractionSelector(line))
|
|
264
|
+
}
|