@@ -301,45 +301,114 @@ data class A2uiCatalog(
301301 append(" \n ${A2uiConstants .A2UI_SCHEMA_BLOCK_END } " )
302302 }
303303
304- /* * Loads and validates examples from a directory. */
304+ /* * Loads and validates examples from a directory or a glob pattern . */
305305 @JvmOverloads
306306 fun loadExamples (path : String? , validate : Boolean = false): String {
307307 if (path.isNullOrEmpty()) return " "
308- val dir = File (path)
309- if (! dir.isDirectory) {
310- logger.warning(" Example path $path is not a directory" )
308+
309+ val isDir = File (path).isDirectory
310+ val pattern =
311+ if (isDir) {
312+ val sep = if (path.endsWith(" /" ) || path.endsWith(File .separator)) " " else " /"
313+ " $path$sep *.json"
314+ } else {
315+ path
316+ }
317+
318+ // Extract the base directory to avoid walking the entire filesystem.
319+ val firstWildcard = pattern.indexOfFirst { it == ' *' || it == ' ?' || it == ' [' }
320+ val baseDirPath =
321+ if (firstWildcard != - 1 ) {
322+ val lastSlash = pattern.lastIndexOfAny(charArrayOf(' /' , ' \\ ' ), firstWildcard)
323+ if (lastSlash != - 1 ) {
324+ pattern.substring(startIndex = 0 , endIndex = lastSlash)
325+ } else {
326+ " "
327+ }
328+ } else {
329+ if (isDir) {
330+ path
331+ } else {
332+ val parent = File (path).parent
333+ parent ? : " "
334+ }
335+ }
336+
337+ val baseDirFile = if (baseDirPath.isEmpty()) File (" ." ) else File (baseDirPath)
338+ val matchedFiles = mutableListOf<File >()
339+
340+ if (baseDirFile.exists() && baseDirFile.isDirectory) {
341+ try {
342+ val matcher = java.nio.file.FileSystems .getDefault().getPathMatcher(" glob:$pattern " )
343+ // To support globstar matching where ** matches zero directories, create an alternate
344+ // matcher.
345+ val altPattern =
346+ pattern
347+ .replace(oldValue = " /**/" , newValue = " /" )
348+ .replace(regex = " ^\\ *\\ */" .toRegex(), replacement = " " )
349+ val altMatcher =
350+ if (altPattern != pattern) {
351+ java.nio.file.FileSystems .getDefault().getPathMatcher(" glob:$altPattern " )
352+ } else {
353+ null
354+ }
355+
356+ val startPath =
357+ if (baseDirPath.isEmpty()) {
358+ java.nio.file.Paths .get(" " )
359+ } else {
360+ java.nio.file.Paths .get(baseDirPath)
361+ }
362+
363+ java.nio.file.Files .walk(startPath).use { stream ->
364+ stream.forEach { p ->
365+ if (java.nio.file.Files .isRegularFile(p)) {
366+ if (matcher.matches(p) || altMatcher?.matches(p) == true ) {
367+ matchedFiles.add(p.toFile())
368+ }
369+ }
370+ }
371+ }
372+ } catch (e: Exception ) {
373+ logger.warning(" Error walking files for pattern $pattern : ${e.message} " )
374+ }
375+ }
376+
377+ if (matchedFiles.isEmpty()) {
378+ if (! isDir && ! path.any { it == ' *' || it == ' ?' || it == ' [' }) {
379+ logger.warning(" Example path $path is neither a directory nor a valid glob pattern" )
380+ }
311381 return " "
312382 }
313383
314- // Sort files by name to ensure deterministic output order for tests.
315- val files =
316- dir.listFiles { _, name -> name.endsWith(" .json" ) }?.sortedBy { it.name } ? : emptyList<File >()
384+ // Sort files alphabetically by path to ensure deterministic output order and logical grouping.
385+ val files = matchedFiles.sortedBy { it.path }
317386
318387 return files
319388 .mapNotNull { file ->
320389 val basename = file.nameWithoutExtension
321- try {
322- val content = file.readText()
323- if (validate && ! validateExample( file.path, content)) {
324- null
325- } else {
326- " ---BEGIN $basename --- \n $content \n ---END $basename --- "
390+ val content =
391+ try {
392+ file.readText()
393+ } catch (e : Exception ) {
394+ logger.warning( " Failed to read example ${file.path} : ${e.message} " )
395+ return @mapNotNull null
327396 }
328- } catch (e : Exception ) {
329- logger.warning( " Failed to load example ${file.path} : ${e.message} " )
330- null
397+
398+ if (validate) {
399+ validateExample(file.path, content)
331400 }
401+ " ---BEGIN $basename ---\n $content \n ---END $basename ---"
332402 }
333- .joinToString(" \n\n " )
403+ .joinToString(separator = " \n\n " )
334404 }
335405
336- private fun validateExample (fullPath : String , content : String ): Boolean =
406+ private fun validateExample (fullPath : String , content : String ) {
337407 try {
338408 val jsonElement = Json .parseToJsonElement(content)
339409 validator.validate(jsonElement)
340- true
341410 } catch (e: Exception ) {
342- logger.warning(" Failed to validate example $fullPath : ${e.message} " )
343- false
411+ throw IllegalArgumentException (" Failed to validate example $fullPath : ${e.message} " , e)
344412 }
413+ }
345414}
0 commit comments