@@ -45,23 +45,24 @@ function stripDoxygenAnchors(line: string): string {
4545 return line . replace ( / \s * \{ # [ ^ } ] + \} \s * / g, "" ) . trimEnd ( ) ;
4646}
4747
48+ /** Human-friendly labels for the Doxygen callout/aside commands we render. */
49+ const CALLOUT_LABELS : Record < string , string > = {
50+ warning : "Warning" ,
51+ attention : "Warning" ,
52+ important : "Warning" ,
53+ note : "Note" ,
54+ remark : "Note" ,
55+ tip : "Tip" ,
56+ hint : "Tip" ,
57+ } ;
58+
4859/**
4960 * Map Doxygen callout/aside commands to human-friendly labels.
5061 * @param command The Doxygen callout command (e.g. "warning", "note", "tip", etc.).
5162 * @returns A human-friendly label for the callout (e.g. "Warning", "Note", "Tip"), or null if the command is not recognized as a callout.
5263 */
5364function getCalloutLabel ( command : string ) : string | null {
54- const key = command . toLowerCase ( ) ;
55- if ( key === "warning" || key === "attention" || key === "important" ) {
56- return "Warning" ;
57- }
58- if ( key === "note" || key === "remark" ) {
59- return "Note" ;
60- }
61- if ( key === "tip" || key === "hint" ) {
62- return "Tip" ;
63- }
64- return null ;
65+ return CALLOUT_LABELS [ command . toLowerCase ( ) ] ?? null ;
6566}
6667
6768/**
@@ -100,20 +101,11 @@ export function extractDoxygenGroupTitleFromDoxygen(
100101 groupPrefix : string ,
101102 fallback : string ,
102103) : string {
103- const lines = content . split ( "\n" ) ;
104- const defgroupPattern = new RegExp (
105- `@defgroup\\s+${ groupPrefix } _[A-Za-z0-9._-]+\\s+(.+)$`
104+ const match = content . match (
105+ new RegExp ( `@defgroup\\s+${ groupPrefix } _[A-Za-z0-9._-]+\\s+(.+)$` , "m" ) ,
106106 ) ;
107107
108- for ( let i = 0 ; i < lines . length ; i ++ ) {
109- const match = lines [ i ] . match ( defgroupPattern ) ;
110- if ( ! match ) {
111- continue ;
112- }
113- return stripAsterisksAndQuotes ( match [ 1 ] ) ;
114- }
115-
116- return fallback ;
108+ return match ? stripAsterisksAndQuotes ( match [ 1 ] ) : fallback ;
117109}
118110
119111/**
@@ -184,40 +176,110 @@ function transformImageLine(line: string): string | null {
184176}
185177
186178/**
187- * Transform Doxygen markdown content to remove unsupported directives and convert some to standard markdown.
188- * @param content The raw Doxygen markdown content to transform.
189- * @returns The transformed markdown content with unsupported directives removed and some converted to standard markdown.
190- * @warn This function does not support all Doxygen directives and may not cover all edge cases.
191- * It is intended to handle the most common directives found in RIOT board documentation,
192- * but may need to be extended in the future to support additional directives or edge cases.
179+ * Astro site-relative path under which the images of the Doxygen source tree are
180+ * served.
193181 */
194- export function transformDoxygenMarkdown ( content : string ) : string {
195- // Split the content into lines and process each line to handle Doxygen directives.
196- const sourceLines = content . split ( / \r ? \n / ) ;
197- const out : string [ ] = [ ] ;
198-
199- for ( let i = 0 ; i < sourceLines . length ; i ++ ) {
200- const rawLine = sourceLines [ i ] ;
201- const line = rawLine . trim ( ) ;
202- const brief = transformBriefLine ( line ) ;
203- const callout = transformCalloutLine ( line ) ;
204- const image = transformImageLine ( line ) ;
205-
206- if ( isGroupMetadata ( line ) ) {
207- // If desired, we could potentially use these to structure the content in the future,
208- // but for now we just ignore them.
209- } else if ( brief !== null ) {
210- out . push ( brief ) ;
211- } else if ( callout !== null ) {
212- if ( callout ) {
213- out . push ( callout ) ;
214- }
215- } else if ( image !== null ) {
216- out . push ( image ) ;
217- } else {
218- out . push ( stripDoxygenAnchors ( replaceInlineDoxygenCommands ( rawLine ) ) ) ;
219- }
182+ const IMAGE_BASE_PATH = "/img" ;
183+
184+ /** File extensions that are treated as images when rewriting relative paths. */
185+ const IMAGE_EXTENSION_PATTERN = / \. (?: s v g | p n g | j p e ? g | g i f | w e b p | a v i f ) $ / i;
186+
187+ /** URLs that must be kept as they are (absolute, protocol relative, data, anchors). */
188+ const ABSOLUTE_URL_PATTERN = / ^ (?: [ A - Z a - z ] [ A - Z a - z 0 - 9 + . - ] * : | \/ \/ | \/ | # ) / ;
189+
190+ /**
191+ * Rewrite a single image reference to a path that the site can serve.
192+ *
193+ * Doxygen resolves image references through its IMAGE_PATH, because the docs sadly
194+ * only spell out the file name (e.g. `nucleo-f031k6-and-more.svg`) even though the
195+ * file lives in `doc/doxygen/src/pinouts/`.
196+ * The image will be hosted by starlight inside the `IMAGE_BASE_PATH`.
197+ *
198+ * @param target The image reference as written in the Doxygen documentation.
199+ * @returns The rewritten reference, or the unchanged target if it is not a relative image.
200+ */
201+ function rewriteImageReference ( target : string ) : string {
202+ if (
203+ ABSOLUTE_URL_PATTERN . test ( target ) ||
204+ ! IMAGE_EXTENSION_PATTERN . test ( target )
205+ ) {
206+ return target ;
220207 }
221208
222- return out . join ( "\n" ) ;
209+ // Only the file name is relevant
210+ const fileName = target . split ( "/" ) . pop ( ) ;
211+ return `${ IMAGE_BASE_PATH } /${ fileName } ` ;
212+ }
213+
214+ /**
215+ * Rewrite the relative image references of a line of text, both in markdown
216+ * image syntax (``) and in raw `<img>` tags, which the board
217+ * and CPU docs use to be able to set an image width.
218+ *
219+ * @param line The line of text to rewrite the image references in.
220+ * @returns The line of text with all relative image references rewritten.
221+ */
222+ function rewriteImageReferences ( line : string ) : string {
223+ return line
224+ . replace (
225+ / ( < i m g \b [ ^ > ] * ?\s s r c \s * = \s * ( [ " ' ] ) ) ( .* ?) \2/ gi,
226+ ( _match , prefix : string , quote : string , target : string ) =>
227+ `${ prefix } ${ rewriteImageReference ( target ) } ${ quote } ` ,
228+ )
229+ . replace (
230+ / ( ! \[ [ ^ \] ] * \] \( \s * ) ( [ ^ ) \s ] + ) / g,
231+ ( _match , prefix : string , target : string ) =>
232+ `${ prefix } ${ rewriteImageReference ( target ) } ` ,
233+ ) ;
234+ }
235+
236+ /**
237+ * Transform a single line of Doxygen markdown.
238+ * @param rawLine The untrimmed input line.
239+ * @returns The transformed line, or null if the line should be dropped.
240+ */
241+ function transformDoxygenLine ( rawLine : string ) : string | null {
242+ const line = rawLine . trim ( ) ;
243+
244+ // Group metadata could be used to structure the content in the future,
245+ // but for now we just drop it.
246+ if ( isGroupMetadata ( line ) ) {
247+ return null ;
248+ }
249+
250+ const brief = transformBriefLine ( line ) ;
251+ if ( brief !== null ) {
252+ return brief ;
253+ }
254+
255+ // Callout commands without a human-friendly label (e.g. @experimental)
256+ // yield an empty string and are dropped.
257+ const callout = transformCalloutLine ( line ) ;
258+ if ( callout !== null ) {
259+ return callout || null ;
260+ }
261+
262+ const image = transformImageLine ( line ) ;
263+ if ( image !== null ) {
264+ return rewriteImageReferences ( image ) ;
265+ }
266+
267+ return rewriteImageReferences (
268+ stripDoxygenAnchors ( replaceInlineDoxygenCommands ( rawLine ) ) ,
269+ ) ;
270+ }
271+
272+ /**
273+ * Transform Doxygen markdown content to remove unsupported directives and
274+ * convert some to standard markdown.
275+ *
276+ * @param content The raw Doxygen markdown content to transform.
277+ * @returns The transformed markdown content.
278+ */
279+ export function transformDoxygenMarkdown ( content : string ) : string {
280+ return content
281+ . split ( / \r ? \n / )
282+ . map ( transformDoxygenLine )
283+ . filter ( ( line ) : line is string => line !== null )
284+ . join ( "\n" ) ;
223285}
0 commit comments