Skip to content

Commit b5f1d41

Browse files
authored
Merge pull request #212 from context-hub/issue/201
Global Exclusion Pattern System
2 parents 4013e5a + 5219540 commit b5f1d41

25 files changed

+723
-28
lines changed
+28
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
# Example configuration with exclusion patterns
2+
3+
# Global exclusion patterns
4+
exclude:
5+
# File patterns to exclude globally (glob patterns with wildcards)
6+
patterns:
7+
- "**/.env*"
8+
- "**/config/secrets.yaml"
9+
- "**/*.pem"
10+
- "**/*.key"
11+
- "**/id_rsa"
12+
- "**/credentials.json"
13+
14+
# Paths to exclude globally (exact directories or files)
15+
paths:
16+
- ".secrets/"
17+
- "config/credentials/"
18+
- "node_modules"
19+
- "vendor"
20+
21+
# Regular configuration continues
22+
documents:
23+
- description: "Project Documentation"
24+
outputPath: "docs/project.md"
25+
sources:
26+
- type: file
27+
sourcePaths:
28+
- "src/"

json-schema.json

+21-1
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,26 @@
4444
"$ref": "#/definitions/prompt"
4545
}
4646
},
47+
"exclude": {
48+
"type": "object",
49+
"description": "Global exclusion patterns for filtering files from being included in documents",
50+
"properties": {
51+
"patterns": {
52+
"type": "array",
53+
"description": "Glob patterns to exclude files globally (e.g., '**/.env*', '**/*.pem')",
54+
"items": {
55+
"type": "string"
56+
}
57+
},
58+
"paths": {
59+
"type": "array",
60+
"description": "Specific paths to exclude globally (directories or files)",
61+
"items": {
62+
"type": "string"
63+
}
64+
}
65+
}
66+
},
4767
"variables": {
4868
"type": "object",
4969
"description": "Custom variables to use throughout the configuration",
@@ -705,7 +725,7 @@
705725
}
706726
}
707727
},
708-
"allOf": [
728+
"anyOf": [
709729
{
710730
"if": {
711731
"properties": {
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Butschster\ContextGenerator\Application\Bootloader;
6+
7+
use Butschster\ContextGenerator\Config\Exclude\ExcludeParserPlugin;
8+
use Butschster\ContextGenerator\Config\Exclude\ExcludeRegistry;
9+
use Butschster\ContextGenerator\Config\Exclude\ExcludeRegistryInterface;
10+
use Spiral\Boot\Bootloader\Bootloader;
11+
use Spiral\Core\Attribute\Singleton;
12+
13+
#[Singleton]
14+
final class ExcludeBootloader extends Bootloader
15+
{
16+
#[\Override]
17+
public function defineSingletons(): array
18+
{
19+
return [
20+
ExcludeRegistryInterface::class => ExcludeRegistry::class,
21+
];
22+
}
23+
24+
public function boot(ConfigLoaderBootloader $configLoader, ExcludeParserPlugin $excludeParser): void
25+
{
26+
// Register the exclude parser plugin
27+
$configLoader->registerParserPlugin($excludeParser);
28+
}
29+
}

src/Application/Kernel.php

+3-1
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@
1010
use Butschster\ContextGenerator\Application\Bootloader\ConsoleBootloader;
1111
use Butschster\ContextGenerator\Application\Bootloader\ContentRendererBootloader;
1212
use Butschster\ContextGenerator\Application\Bootloader\CoreBootloader;
13+
use Butschster\ContextGenerator\Application\Bootloader\ExcludeBootloader;
1314
use Butschster\ContextGenerator\Application\Bootloader\GithubClientBootloader;
1415
use Butschster\ContextGenerator\Application\Bootloader\GitlabClientBootloader;
1516
use Butschster\ContextGenerator\Application\Bootloader\HttpClientBootloader;
@@ -58,9 +59,10 @@ protected function defineBootloaders(): array
5859
GithubClientBootloader::class,
5960
ComposerClientBootloader::class,
6061
ConfigLoaderBootloader::class,
62+
VariableBootloader::class,
63+
ExcludeBootloader::class,
6164
ModifierBootloader::class,
6265
ContentRendererBootloader::class,
63-
VariableBootloader::class,
6466
SourceFetcherBootloader::class,
6567
SourceRegistryBootloader::class,
6668

+42
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Butschster\ContextGenerator\Config\Exclude;
6+
7+
/**
8+
* Base class for all exclusion patterns
9+
*/
10+
abstract readonly class AbstractExclusion implements ExclusionPatternInterface
11+
{
12+
/**
13+
* @param string $pattern Exclusion pattern value
14+
*/
15+
public function __construct(
16+
protected string $pattern,
17+
) {}
18+
19+
/**
20+
* Get the raw pattern string
21+
*/
22+
public function getPattern(): string
23+
{
24+
return $this->pattern;
25+
}
26+
27+
/**
28+
* Abstract method to check if a path matches this pattern
29+
*/
30+
abstract public function matches(string $path): bool;
31+
32+
/**
33+
* Normalize a pattern for consistent comparison
34+
*/
35+
protected function normalizePattern(string $pattern): string
36+
{
37+
$pattern = \preg_replace('#^\./#', '', $pattern);
38+
39+
// Remove trailing slash
40+
return \rtrim((string) $pattern, '/');
41+
}
42+
}
+98
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,98 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Butschster\ContextGenerator\Config\Exclude;
6+
7+
use Butschster\ContextGenerator\Application\Logger\LoggerPrefix;
8+
use Butschster\ContextGenerator\Config\Parser\ConfigParserPluginInterface;
9+
use Butschster\ContextGenerator\Config\Registry\RegistryInterface;
10+
use Psr\Log\LoggerInterface;
11+
12+
/**
13+
* Parser plugin for the 'exclude' section in configuration
14+
*/
15+
final readonly class ExcludeParserPlugin implements ConfigParserPluginInterface
16+
{
17+
public function __construct(
18+
private ExcludeRegistryInterface $registry,
19+
#[LoggerPrefix(prefix: 'exclude-parser')]
20+
private ?LoggerInterface $logger = null,
21+
) {}
22+
23+
public function getConfigKey(): string
24+
{
25+
return 'exclude';
26+
}
27+
28+
public function supports(array $config): bool
29+
{
30+
return isset($config['exclude']) && \is_array($config['exclude']);
31+
}
32+
33+
public function parse(array $config, string $rootPath): ?RegistryInterface
34+
{
35+
if (!$this->supports($config)) {
36+
return null;
37+
}
38+
39+
\assert($this->registry instanceof RegistryInterface);
40+
$excludeConfig = $config['exclude'];
41+
42+
// Parse patterns
43+
if (isset($excludeConfig['patterns']) && \is_array($excludeConfig['patterns'])) {
44+
$this->parsePatterns($excludeConfig['patterns']);
45+
}
46+
47+
// Parse paths
48+
if (isset($excludeConfig['paths']) && \is_array($excludeConfig['paths'])) {
49+
$this->parsePaths($excludeConfig['paths']);
50+
}
51+
52+
$this->logger?->info('Parsed exclusion configuration', [
53+
'patternCount' => \count($this->registry->getPatterns()),
54+
]);
55+
56+
return $this->registry;
57+
}
58+
59+
public function updateConfig(array $config, string $rootPath): array
60+
{
61+
// We don't need to modify the config, just return it as is
62+
return $config;
63+
}
64+
65+
/**
66+
* Parse glob pattern exclusions
67+
*/
68+
private function parsePatterns(array $patterns): void
69+
{
70+
foreach ($patterns as $pattern) {
71+
if (!\is_string($pattern) || empty($pattern)) {
72+
$this->logger?->warning('Invalid exclusion pattern, skipping', [
73+
'pattern' => $pattern,
74+
]);
75+
continue;
76+
}
77+
78+
$this->registry->addPattern(new PatternExclusion($pattern));
79+
}
80+
}
81+
82+
/**
83+
* Parse path exclusions
84+
*/
85+
private function parsePaths(array $paths): void
86+
{
87+
foreach ($paths as $path) {
88+
if (!\is_string($path) || empty($path)) {
89+
$this->logger?->warning('Invalid exclusion path, skipping', [
90+
'path' => $path,
91+
]);
92+
continue;
93+
}
94+
95+
$this->registry->addPattern(new PathExclusion($path));
96+
}
97+
}
98+
}
+105
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,105 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Butschster\ContextGenerator\Config\Exclude;
6+
7+
use Butschster\ContextGenerator\Application\Logger\LoggerPrefix;
8+
use Butschster\ContextGenerator\Config\Registry\RegistryInterface;
9+
use Psr\Log\LoggerInterface;
10+
use Spiral\Core\Attribute\Singleton;
11+
12+
/**
13+
* Registry for file exclusion patterns
14+
*
15+
* @implements RegistryInterface<ExclusionPatternInterface>
16+
*/
17+
#[Singleton]
18+
final class ExcludeRegistry implements ExcludeRegistryInterface, RegistryInterface
19+
{
20+
/** @var array<ExclusionPatternInterface> */
21+
private array $patterns = [];
22+
23+
public function __construct(
24+
#[LoggerPrefix(prefix: 'exclude-registry')]
25+
private readonly ?LoggerInterface $logger = null,
26+
) {}
27+
28+
/**
29+
* Add a new exclusion pattern
30+
*/
31+
public function addPattern(ExclusionPatternInterface $pattern): self
32+
{
33+
$this->patterns[] = $pattern;
34+
35+
$this->logger?->debug('Added exclusion pattern', [
36+
'pattern' => $pattern->getPattern(),
37+
]);
38+
39+
return $this;
40+
}
41+
42+
/**
43+
* Check if a path should be excluded
44+
*/
45+
public function shouldExclude(string $path): bool
46+
{
47+
foreach ($this->patterns as $pattern) {
48+
if ($pattern->matches($path)) {
49+
$this->logger?->debug('Path excluded by pattern', [
50+
'path' => $path,
51+
'pattern' => $pattern->getPattern(),
52+
]);
53+
54+
return true;
55+
}
56+
}
57+
58+
return false;
59+
}
60+
61+
/**
62+
* Get all registered exclusion patterns
63+
*/
64+
public function getPatterns(): array
65+
{
66+
return $this->patterns;
67+
}
68+
69+
/**
70+
* Get the registry type
71+
*/
72+
public function getType(): string
73+
{
74+
return 'exclude';
75+
}
76+
77+
/**
78+
* Get all items in the registry
79+
*/
80+
public function getItems(): array
81+
{
82+
return $this->patterns;
83+
}
84+
85+
/**
86+
* Make the registry iterable
87+
*/
88+
public function getIterator(): \Traversable
89+
{
90+
return new \ArrayIterator($this->patterns);
91+
}
92+
93+
/**
94+
* JSON serialization
95+
*/
96+
public function jsonSerialize(): array
97+
{
98+
return [
99+
'patterns' => \array_map(
100+
static fn(ExclusionPatternInterface $pattern) => $pattern->jsonSerialize(),
101+
$this->patterns,
102+
),
103+
];
104+
}
105+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
<?php
2+
3+
declare(strict_types=1);
4+
5+
namespace Butschster\ContextGenerator\Config\Exclude;
6+
7+
/**
8+
* Interface for exclusion pattern registry
9+
*/
10+
interface ExcludeRegistryInterface
11+
{
12+
/**
13+
* Add an exclusion pattern
14+
*/
15+
public function addPattern(ExclusionPatternInterface $pattern): self;
16+
17+
/**
18+
* Check if a path should be excluded
19+
*/
20+
public function shouldExclude(string $path): bool;
21+
22+
/**
23+
* Get all registered exclusion patterns
24+
*
25+
* @return array<ExclusionPatternInterface>
26+
*/
27+
public function getPatterns(): array;
28+
}

0 commit comments

Comments
 (0)