Skip to content

Commit 91ebc16

Browse files
committed
Add support for wildcard patterns in configuration imports
- Implement `PathMatcher` to convert glob patterns to regex - Create `WildcardPathFinder` for filesystem traversal based on patterns - Support both single-level (``*`) and multi-level (`**`) wildcard patterns
1 parent 0c6f167 commit 91ebc16

File tree

5 files changed

+490
-34
lines changed

5 files changed

+490
-34
lines changed

context.yaml

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
import:
2-
- path: src/Console/context.yaml
2+
- path: src/**/context.yaml
33
- path: src/ConfigLoader/context.yaml
44

55
documents:
@@ -60,8 +60,8 @@ documents:
6060
sources:
6161
- type: file
6262
sourcePaths: src/Document
63-
filePattern: '*.php'
64-
showTreeView: true
63+
contains:
64+
- implements
6565

6666
- description: Source Implementations - File
6767
outputPath: sources/file-source.md
@@ -253,3 +253,16 @@ documents:
253253
sources:
254254
- type: git_diff
255255
commit: unstaged
256+
257+
- description: Import documents docs
258+
outputPath: docs/import-docs.md
259+
sources:
260+
- type: file
261+
sourcePaths:
262+
- src/ConfigLoader/Import
263+
showTreeView: true
264+
- type: github
265+
repository: context-hub/docs
266+
branch: main
267+
sourcePaths:
268+
- docs/configuration.md

src/ConfigLoader/Import/ImportConfig.php

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ public function __construct(
1313
public string $path,
1414
public string $absolutePath,
1515
public ?string $pathPrefix = null,
16+
public bool $hasWildcard = false,
1617
) {}
1718

1819
/**
@@ -27,13 +28,18 @@ public static function fromArray(array $config, string $basePath): self
2728
$path = $config['path'];
2829
$pathPrefix = $config['pathPrefix'] ?? null;
2930

31+
// Check if the path contains wildcards
32+
$hasWildcard = PathMatcher::containsWildcard($path);
33+
3034
// Resolve relative path to absolute path
35+
// Note: For wildcard paths, this will be used as a base path for pattern matching
3136
$absolutePath = self::resolvePath($path, $basePath);
3237

3338
return new self(
3439
path: $path,
3540
absolutePath: $absolutePath,
3641
pathPrefix: $pathPrefix,
42+
hasWildcard: $hasWildcard,
3743
);
3844
}
3945

src/ConfigLoader/Import/ImportResolver.php

Lines changed: 135 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -15,12 +15,16 @@
1515
*/
1616
final readonly class ImportResolver
1717
{
18+
private WildcardPathFinder $pathFinder;
19+
1820
public function __construct(
1921
private Directories $dirs,
2022
private FilesInterface $files,
2123
private ConfigLoaderFactory $loaderFactory,
2224
private ?LoggerInterface $logger = null,
23-
) {}
25+
) {
26+
$this->pathFinder = new WildcardPathFinder($files, $logger);
27+
}
2428

2529
/**
2630
* Process imports in a configuration
@@ -49,6 +53,19 @@ public function resolveImports(
4953
foreach ($imports as $importConfig) {
5054
$importCfg = ImportConfig::fromArray($importConfig, $basePath);
5155

56+
// Handle wildcard paths
57+
if ($importCfg->hasWildcard) {
58+
$this->processWildcardImport(
59+
$importCfg,
60+
$basePath,
61+
$parsedImports,
62+
$detector,
63+
$importedConfigs,
64+
$importConfig,
65+
);
66+
continue;
67+
}
68+
5269
// Skip if already processed
5370
if (\in_array($importCfg->absolutePath, $parsedImports, true)) {
5471
$this->logger?->debug('Skipping already processed import', [
@@ -58,46 +75,133 @@ public function resolveImports(
5875
continue;
5976
}
6077

61-
// Check for circular imports
62-
$detector->beginProcessing($importCfg->absolutePath);
63-
64-
try {
65-
// Load the import configuration
66-
$importedConfig = $this->loadImportConfig($importCfg);
78+
// Process a single standard import
79+
$this->processSingleImport(
80+
$importCfg,
81+
$basePath,
82+
$parsedImports,
83+
$detector,
84+
$importedConfigs,
85+
$importConfig,
86+
);
87+
}
6788

68-
// Recursively process nested imports
69-
$dirname = \dirname($importCfg->absolutePath);
70-
$importedConfig = $this->resolveImports(
71-
$importedConfig,
72-
$dirname,
73-
$parsedImports,
74-
$detector,
75-
);
89+
// Remove the import directive from the original config
90+
unset($config['import']);
7691

77-
// Apply path prefix if specified
78-
$importedConfig = $this->applyPathPrefix($importedConfig, \dirname((string) $importConfig['path']));
92+
// Merge all configurations
93+
return $this->mergeConfigurations([$config, ...$importedConfigs]);
94+
}
7995

80-
// Store for later merging
81-
$importedConfigs[] = $importedConfig;
96+
/**
97+
* Process a wildcard import pattern
98+
*/
99+
private function processWildcardImport(
100+
ImportConfig $importCfg,
101+
string $basePath,
102+
array &$parsedImports,
103+
CircularImportDetector $detector,
104+
array &$importedConfigs,
105+
array $importConfig,
106+
): void {
107+
// Find all files that match the pattern
108+
$matchingPaths = $this->pathFinder->findMatchingPaths($importCfg->path, $basePath);
109+
110+
if (empty($matchingPaths)) {
111+
$this->logger?->warning('No files match the wildcard pattern', [
112+
'pattern' => $importCfg->path,
113+
'basePath' => $basePath,
114+
]);
115+
return;
116+
}
82117

83-
// Mark as processed
84-
$parsedImports[] = $importCfg->absolutePath;
118+
$this->logger?->debug('Found files matching wildcard pattern', [
119+
'pattern' => $importCfg->path,
120+
'count' => \count($matchingPaths),
121+
'paths' => $matchingPaths,
122+
]);
85123

86-
$this->logger?->debug('Successfully processed import', [
87-
'path' => $importCfg->path,
88-
'absolutePath' => $importCfg->absolutePath,
124+
// Process each matching file
125+
foreach ($matchingPaths as $matchingPath) {
126+
// Skip if already processed
127+
if (\in_array($matchingPath, $parsedImports, true)) {
128+
$this->logger?->debug('Skipping already processed wildcard match', [
129+
'path' => $matchingPath,
89130
]);
90-
} finally {
91-
// Always end processing to maintain stack integrity
92-
$detector->endProcessing($importCfg->absolutePath);
131+
continue;
93132
}
133+
134+
// Create a single import config for this match
135+
$singleImportCfg = new ImportConfig(
136+
path: $matchingPath, // Use the actual file path
137+
absolutePath: $matchingPath, // The path finder returns absolute paths
138+
pathPrefix: $importCfg->pathPrefix,
139+
hasWildcard: false,
140+
);
141+
142+
// Process it using the standard import logic
143+
$this->processSingleImport(
144+
$singleImportCfg,
145+
\dirname($matchingPath), // Base path is the directory of the matched file
146+
$parsedImports,
147+
$detector,
148+
$importedConfigs,
149+
$importConfig,
150+
);
94151
}
152+
}
95153

96-
// Remove the import directive from the original config
97-
unset($config['import']);
154+
/**
155+
* Process a single non-wildcard import
156+
*/
157+
private function processSingleImport(
158+
ImportConfig $importCfg,
159+
string $basePath,
160+
array &$parsedImports,
161+
CircularImportDetector $detector,
162+
array &$importedConfigs,
163+
array $importConfig,
164+
): void {
165+
// Check for circular imports
166+
$detector->beginProcessing($importCfg->absolutePath);
98167

99-
// Merge all configurations
100-
return $this->mergeConfigurations([$config, ...$importedConfigs]);
168+
try {
169+
// Load the import configuration
170+
$importedConfig = $this->loadImportConfig($importCfg);
171+
172+
// Recursively process nested imports
173+
$dirname = \dirname($importCfg->absolutePath);
174+
$importedConfig = $this->resolveImports(
175+
$importedConfig,
176+
$dirname,
177+
$parsedImports,
178+
$detector,
179+
);
180+
181+
// Apply path prefix if specified
182+
$importedConfig = $this->applyPathPrefix($importedConfig, \dirname((string) $importConfig['path']));
183+
184+
// Store for later merging
185+
$importedConfigs[] = $importedConfig;
186+
187+
// Mark as processed
188+
$parsedImports[] = $importCfg->absolutePath;
189+
190+
$this->logger?->debug('Successfully processed import', [
191+
'path' => $importCfg->path,
192+
'absolutePath' => $importCfg->absolutePath,
193+
]);
194+
} catch (\Throwable $e) {
195+
$this->logger?->error('Failed to process import', [
196+
'path' => $importCfg->path,
197+
'absolutePath' => $importCfg->absolutePath,
198+
'error' => $e->getMessage(),
199+
]);
200+
throw $e;
201+
} finally {
202+
// Always end processing to maintain stack integrity
203+
$detector->endProcessing($importCfg->absolutePath);
204+
}
101205
}
102206

103207
/**

0 commit comments

Comments
 (0)