@@ -26,6 +26,174 @@ function setupCopyButtons() {
2626 }
2727}
2828
29+ function escapeHtml ( value ) {
30+ return value
31+ . replaceAll ( "&" , "&" )
32+ . replaceAll ( "<" , "<" )
33+ . replaceAll ( ">" , ">" ) ;
34+ }
35+
36+ function applyPatternSegments ( input , pattern , className ) {
37+ const segments = [ ] ;
38+ let output = "" ;
39+ let lastIndex = 0 ;
40+ let match ;
41+
42+ const regex = new RegExp ( pattern . source , pattern . flags ) ;
43+
44+ while ( ( match = regex . exec ( input ) ) !== null ) {
45+ const [ fullMatch ] = match ;
46+ const startIndex = match . index ;
47+ const endIndex = startIndex + fullMatch . length ;
48+
49+ output += input . slice ( lastIndex , startIndex ) ;
50+ const token = `__TOKEN_${ segments . length } __` ;
51+ segments . push ( `<span class="${ className } ">${ fullMatch } </span>` ) ;
52+ output += token ;
53+ lastIndex = endIndex ;
54+
55+ if ( fullMatch . length === 0 ) {
56+ regex . lastIndex += 1 ;
57+ }
58+ }
59+
60+ output += input . slice ( lastIndex ) ;
61+
62+ return {
63+ output,
64+ segments,
65+ } ;
66+ }
67+
68+ function restorePatternSegments ( input , segments ) {
69+ return segments . reduce (
70+ ( restored , segment , index ) => restored . replace ( `__TOKEN_${ index } __` , segment ) ,
71+ input ,
72+ ) ;
73+ }
74+
75+ function highlightShellLine ( line ) {
76+ if ( / ^ \s * # / . test ( line ) ) {
77+ return `<span class="token-comment">${ escapeHtml ( line ) } </span>` ;
78+ }
79+
80+ let html = escapeHtml ( line ) ;
81+
82+ const protectedSegments = [
83+ { pattern : / \$ \{ \{ [ ^ } ] + \} \} / g, className : "token-variable" } ,
84+ { pattern : / " (?: [ ^ " \\ ] | \\ .) * " | ' (?: [ ^ ' \\ ] | \\ .) * ' / g, className : "token-string" } ,
85+ { pattern : / \$ \{ [ ^ } ] + \} | \$ [ A - Z a - z _ ] [ A - Z a - z 0 - 9 _ ] * / g, className : "token-variable" } ,
86+ ] ;
87+
88+ const segmentGroups = [ ] ;
89+
90+ for ( const entry of protectedSegments ) {
91+ const result = applyPatternSegments ( html , entry . pattern , entry . className ) ;
92+ html = result . output ;
93+ segmentGroups . push ( result . segments ) ;
94+ }
95+
96+ html = html
97+ . replace ( / ( ^ | \s ) ( - - [ A - Z a - z 0 - 9 - ] + ) / g, '$1<span class="token-flag">$2</span>' )
98+ . replace (
99+ / ( ^ | \s ) ( c u r l | b a s h | m k d i r | p r i n t f | c h m o d | r s y n c | e c h o | i f | t h e n | f i | d o | d o n e | f o r | i n ) (? = \s | $ ) / g,
100+ '$1<span class="token-keyword">$2</span>' ,
101+ )
102+ . replace ( / ( ^ | \s ) ( [ A - Z ] [ A - Z 0 - 9 _ ] * ) ( = ) / g, '$1<span class="token-key">$2</span><span class="token-punctuation">$3</span>' ) ;
103+
104+ for ( let index = segmentGroups . length - 1 ; index >= 0 ; index -= 1 ) {
105+ html = restorePatternSegments ( html , segmentGroups [ index ] ) ;
106+ }
107+
108+ return html ;
109+ }
110+
111+ function highlightEnvLine ( line ) {
112+ const escapedLine = escapeHtml ( line ) ;
113+ const match = escapedLine . match ( / ^ ( [ A - Z ] [ A - Z 0 - 9 _ ] * ) ( = ) ( .* ) $ / ) ;
114+
115+ if ( ! match ) {
116+ return escapedLine ;
117+ }
118+
119+ const [ , key , separator , value ] = match ;
120+ const highlightedValue = value . replace (
121+ / ( \$ \{ [ ^ } ] + \} | \$ [ A - Z a - z _ ] [ A - Z a - z 0 - 9 _ ] * ) / g,
122+ '<span class="token-variable">$1</span>' ,
123+ ) ;
124+
125+ return `<span class="token-key">${ key } </span><span class="token-punctuation">${ separator } </span><span class="token-string">${ highlightedValue } </span>` ;
126+ }
127+
128+ function highlightJsonLine ( line ) {
129+ let html = escapeHtml ( line ) ;
130+
131+ html = html
132+ . replace (
133+ / ^ ( \s * ) " ( [ ^ " ] + ) " ( \s * : ) / ,
134+ '$1<span class="token-key">"$2"</span><span class="token-punctuation">$3</span>' ,
135+ )
136+ . replace ( / : \s * " ( [ ^ " ] * ) " / g, ': <span class="token-string">"$1"</span>' )
137+ . replace ( / \b ( t r u e | f a l s e | n u l l ) \b / g, '<span class="token-constant">$1</span>' )
138+ . replace ( / \b - ? \d + (?: \. \d + ) ? \b / g, '<span class="token-number">$&</span>' ) ;
139+
140+ return html ;
141+ }
142+
143+ function highlightYamlLine ( line ) {
144+ if ( / ^ \s * # / . test ( line ) ) {
145+ return `<span class="token-comment">${ escapeHtml ( line ) } </span>` ;
146+ }
147+
148+ let html = escapeHtml ( line ) ;
149+
150+ html = html
151+ . replace ( / ( \$ \{ \{ [ ^ } ] + \} \} ) / g, '<span class="token-variable">$1</span>' )
152+ . replace (
153+ / ^ ( \s * - ? \s * ) ( [ A - Z a - z 0 - 9 _ . - ] + ) ( : \s * ) / ,
154+ '$1<span class="token-key">$2</span><span class="token-punctuation">$3</span>' ,
155+ )
156+ . replace ( / : \s * " ( [ ^ " ] * ) " / g, ': <span class="token-string">"$1"</span>' )
157+ . replace ( / : \s * ' ( [ ^ ' ] * ) ' / g, ": <span class=\"token-string\">'$1'</span>" )
158+ . replace ( / \b ( t r u e | f a l s e | n u l l ) \b / g, '<span class="token-constant">$1</span>' )
159+ . replace ( / \b \d + \b / g, '<span class="token-number">$&</span>' )
160+ . replace ( / \b ( a c t i o n s \/ c h e c k o u t @ v \d + | a c t i o n s \/ s e t u p - n o d e @ v \d + | S e l f l i f y \/ [ A - Z a - z 0 - 9 - ] + @ v \d + ) \b / g, '<span class="token-string">$1</span>' ) ;
161+
162+ return html ;
163+ }
164+
165+ function highlightCodeBlock ( code ) {
166+ const language = code . getAttribute ( "data-lang" ) ;
167+
168+ if ( ! language ) {
169+ return ;
170+ }
171+
172+ const raw = code . textContent ?? "" ;
173+ const lines = raw . split ( "\n" ) ;
174+
175+ const highlighter =
176+ language === "json"
177+ ? highlightJsonLine
178+ : language === "yaml"
179+ ? highlightYamlLine
180+ : language === "env"
181+ ? highlightEnvLine
182+ : highlightShellLine ;
183+
184+ code . innerHTML = lines . map ( ( line ) => highlighter ( line ) ) . join ( "\n" ) ;
185+ }
186+
187+ function setupSyntaxHighlighting ( ) {
188+ const blocks = document . querySelectorAll ( "pre code[data-lang]" ) ;
189+
190+ for ( const block of blocks ) {
191+ if ( block instanceof HTMLElement ) {
192+ highlightCodeBlock ( block ) ;
193+ }
194+ }
195+ }
196+
29197function setupExpandableCodeExamples ( ) {
30198 const containers = document . querySelectorAll ( "[data-expandable-code]" ) ;
31199
@@ -50,6 +218,7 @@ function setupExpandableCodeExamples() {
50218}
51219
52220document . addEventListener ( "DOMContentLoaded" , ( ) => {
221+ setupSyntaxHighlighting ( ) ;
53222 setupCopyButtons ( ) ;
54223 setupExpandableCodeExamples ( ) ;
55224} ) ;
0 commit comments