1- const separatorRegex = / ^ \s * ( - { 3 , } ) \s * h t m l \s * \1\s * $ / i
1+ const fenceSeparatorRegex = / ^ - - - \s * $ /
2+ const legacySeparatorRegex = / ^ \s * ( - { 3 , } ) \s * h t m l \s * \1\s * $ / i
23const directiveStartRegex = / ^ \s * ! /
34
45export type HeaderNode = { type : 'Directive' ; text : string } | { type : 'Python' ; text : string }
@@ -13,34 +14,125 @@ export type ParsedDocument = {
1314}
1415
1516export type SplitSections = {
16- header : string
17+ directives : string
1718 separator : string | null
19+ python : string
1820 html : string
1921}
2022
23+ /**
24+ * Split a .wire file into its sections:
25+ * 1. Directives (optional) — lines starting with !
26+ * 2. Fenced Python (optional) — between --- separators
27+ * 3. Template HTML
28+ *
29+ * Also supports legacy ---html--- single separator format.
30+ */
2131export function splitPywireSections ( text : string ) : SplitSections {
2232 const lines = text . split ( / \r ? \n / )
23- const separatorIndex = lines . findIndex ( ( line ) => separatorRegex . test ( line ) )
2433
25- if ( separatorIndex < 0 ) {
34+ // First, check for legacy ---html--- format (single separator)
35+ const legacyIdx = lines . findIndex ( ( line ) => legacySeparatorRegex . test ( line ) )
36+ if ( legacyIdx >= 0 ) {
2637 return {
27- header : '' ,
38+ directives : '' ,
39+ separator : lines [ legacyIdx ] ,
40+ python : lines . slice ( 0 , legacyIdx ) . join ( '\n' ) ,
41+ html : lines . slice ( legacyIdx + 1 ) . join ( '\n' ) ,
42+ }
43+ }
44+
45+ // Find all --- fence lines
46+ const fenceIndices : number [ ] = [ ]
47+ for ( let i = 0 ; i < lines . length ; i ++ ) {
48+ if ( fenceSeparatorRegex . test ( lines [ i ] ) ) {
49+ fenceIndices . push ( i )
50+ }
51+ }
52+
53+ // No fences — everything is either directives + html or just html
54+ if ( fenceIndices . length < 2 ) {
55+ const firstNonDirectiveLine = lines . findIndex ( ( line ) => {
56+ const trimmed = line . trim ( )
57+ return trimmed . length > 0 && ! directiveStartRegex . test ( trimmed )
58+ } )
59+
60+ // Check if the first non-empty, non-directive line looks like html or python
61+ // If there are no directives at all, everything is html
62+ const hasDirectives = lines . some ( ( line ) => directiveStartRegex . test ( line . trim ( ) ) )
63+
64+ if ( ! hasDirectives ) {
65+ return {
66+ directives : '' ,
67+ separator : null ,
68+ python : '' ,
69+ html : text ,
70+ }
71+ }
72+
73+ // Has directives but no fence — split at first non-directive, non-empty line
74+ // that isn't part of a block directive
75+ let directiveEnd = 0
76+ let braceDepth = 0
77+ for ( let i = 0 ; i < lines . length ; i ++ ) {
78+ const trimmed = lines [ i ] . trim ( )
79+ braceDepth += countBraces ( lines [ i ] )
80+
81+ if ( braceDepth > 0 ) {
82+ directiveEnd = i + 1
83+ continue
84+ }
85+
86+ if ( directiveStartRegex . test ( trimmed ) ) {
87+ directiveEnd = i + 1
88+ continue
89+ }
90+
91+ if ( trimmed . length === 0 && directiveEnd === i ) {
92+ directiveEnd = i + 1
93+ continue
94+ }
95+
96+ if ( directiveEnd > 0 && trimmed . length > 0 && ! directiveStartRegex . test ( trimmed ) ) {
97+ break
98+ }
99+ }
100+
101+ return {
102+ directives : lines . slice ( 0 , directiveEnd ) . join ( '\n' ) ,
28103 separator : null ,
29- html : lines . join ( '\n' ) ,
104+ python : '' ,
105+ html : lines . slice ( directiveEnd ) . join ( '\n' ) ,
30106 }
31107 }
32108
109+ // Two or more fences — content between first two fences is Python
110+ const openFence = fenceIndices [ 0 ]
111+ const closeFence = fenceIndices [ 1 ]
112+
33113 return {
34- header : lines . slice ( 0 , separatorIndex ) . join ( '\n' ) ,
35- separator : lines [ separatorIndex ] ,
36- html : lines . slice ( separatorIndex + 1 ) . join ( '\n' ) ,
114+ directives : lines . slice ( 0 , openFence ) . join ( '\n' ) ,
115+ separator : lines [ openFence ] ,
116+ python : lines . slice ( openFence + 1 , closeFence ) . join ( '\n' ) ,
117+ html : lines . slice ( closeFence + 1 ) . join ( '\n' ) ,
37118 }
38119}
39120
40121export function parsePywire ( text : string ) : ParsedDocument {
41122 const sections = splitPywireSections ( text )
42- const headerLines = sections . header . length > 0 ? sections . header . split ( '\n' ) : [ ]
43- const headerNodes = parseHeader ( headerLines )
123+
124+ const headerNodes : HeaderNode [ ] = [ ]
125+
126+ // Parse directives
127+ if ( sections . directives . trim ( ) . length > 0 ) {
128+ const directiveNodes = parseDirectives ( sections . directives . split ( '\n' ) )
129+ headerNodes . push ( ...directiveNodes )
130+ }
131+
132+ // Parse python
133+ if ( sections . python . trim ( ) . length > 0 ) {
134+ headerNodes . push ( { type : 'Python' , text : sections . python } )
135+ }
44136
45137 return {
46138 type : 'Document' ,
@@ -52,45 +144,39 @@ export function parsePywire(text: string): ParsedDocument {
52144 }
53145}
54146
55- function parseHeader ( lines : string [ ] ) : HeaderNode [ ] {
147+ function parseDirectives ( lines : string [ ] ) : HeaderNode [ ] {
56148 const nodes : HeaderNode [ ] = [ ]
57- let currentPythonLines : string [ ] = [ ]
58-
59- const flushPython = ( ) => {
60- if ( currentPythonLines . length === 0 ) {
61- return
62- }
63- nodes . push ( { type : 'Python' , text : currentPythonLines . join ( '\n' ) } )
64- currentPythonLines = [ ]
65- }
66-
67149 let index = 0
150+
68151 while ( index < lines . length ) {
69152 const line = lines [ index ]
70- if ( ! directiveStartRegex . test ( line ) ) {
71- currentPythonLines . push ( line )
72- index += 1
153+ const trimmed = line . trim ( )
154+
155+ if ( trimmed . length === 0 ) {
156+ index ++
73157 continue
74158 }
75159
76- flushPython ( )
160+ if ( ! directiveStartRegex . test ( trimmed ) ) {
161+ // Non-directive, non-empty line in directives section — skip
162+ index ++
163+ continue
164+ }
77165
78166 const directiveLines : string [ ] = [ line ]
79167 let braceDepth = countBraces ( line )
80- index += 1
168+ index ++
81169
82170 while ( braceDepth > 0 && index < lines . length ) {
83171 const nextLine = lines [ index ]
84172 directiveLines . push ( nextLine )
85173 braceDepth += countBraces ( nextLine )
86- index += 1
174+ index ++
87175 }
88176
89177 nodes . push ( { type : 'Directive' , text : directiveLines . join ( '\n' ) } )
90178 }
91179
92- flushPython ( )
93-
94180 return nodes
95181}
96182
0 commit comments