Skip to content

Commit 26b5bb2

Browse files
Merge pull request #1577 from ksylvan/0705-custom-patterns-dir
Add Custom Patterns Directory Support
2 parents d081fd2 + b751d32 commit 26b5bb2

File tree

6 files changed

+335
-2
lines changed

6 files changed

+335
-2
lines changed

core/plugin_registry.go

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ import (
3131
"github.com/danielmiessler/fabric/plugins/db/fsdb"
3232
"github.com/danielmiessler/fabric/plugins/template"
3333
"github.com/danielmiessler/fabric/plugins/tools"
34+
"github.com/danielmiessler/fabric/plugins/tools/custom_patterns"
3435
"github.com/danielmiessler/fabric/plugins/tools/jina"
3536
"github.com/danielmiessler/fabric/plugins/tools/lang"
3637
"github.com/danielmiessler/fabric/plugins/tools/youtube"
@@ -69,6 +70,7 @@ func NewPluginRegistry(db *fsdb.Db) (ret *PluginRegistry, err error) {
6970
VendorManager: ai.NewVendorsManager(),
7071
VendorsAll: ai.NewVendorsManager(),
7172
PatternsLoader: tools.NewPatternsLoader(db.Patterns),
73+
CustomPatterns: custom_patterns.NewCustomPatterns(),
7274
YouTube: youtube.NewYouTube(),
7375
Language: lang.NewLanguage(),
7476
Jina: jina.NewClient(),
@@ -138,6 +140,7 @@ type PluginRegistry struct {
138140
VendorsAll *ai.VendorsManager
139141
Defaults *tools.Defaults
140142
PatternsLoader *tools.PatternsLoader
143+
CustomPatterns *custom_patterns.CustomPatterns
141144
YouTube *youtube.YouTube
142145
Language *lang.Language
143146
Jina *jina.Client
@@ -151,6 +154,7 @@ func (o *PluginRegistry) SaveEnvFile() (err error) {
151154

152155
o.Defaults.Settings.FillEnvFileContent(&envFileContent)
153156
o.PatternsLoader.SetupFillEnvFileContent(&envFileContent)
157+
o.CustomPatterns.SetupFillEnvFileContent(&envFileContent)
154158
o.Strategies.SetupFillEnvFileContent(&envFileContent)
155159

156160
for _, vendor := range o.VendorManager.Vendors {
@@ -183,7 +187,7 @@ func (o *PluginRegistry) Setup() (err error) {
183187
return vendor
184188
})...)
185189

186-
groupsPlugins.AddGroupItems("Tools", o.Defaults, o.Jina, o.Language, o.PatternsLoader, o.Strategies, o.YouTube)
190+
groupsPlugins.AddGroupItems("Tools", o.CustomPatterns, o.Defaults, o.Jina, o.Language, o.PatternsLoader, o.Strategies, o.YouTube)
187191

188192
for {
189193
groupsPlugins.Print(false)

plugins/db/fsdb/db.go

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import (
44
"fmt"
55
"os"
66
"path/filepath"
7+
"strings"
78
"time"
89

910
"github.com/joho/godotenv"
@@ -15,10 +16,22 @@ func NewDb(dir string) (db *Db) {
1516

1617
db.EnvFilePath = db.FilePath(".env")
1718

19+
// Check for custom patterns directory from environment variable
20+
customPatternsDir := os.Getenv("CUSTOM_PATTERNS_DIRECTORY")
21+
if customPatternsDir != "" {
22+
// Expand home directory if needed
23+
if strings.HasPrefix(customPatternsDir, "~/") {
24+
if homeDir, err := os.UserHomeDir(); err == nil {
25+
customPatternsDir = filepath.Join(homeDir, customPatternsDir[2:])
26+
}
27+
}
28+
}
29+
1830
db.Patterns = &PatternsEntity{
1931
StorageEntity: &StorageEntity{Label: "Patterns", Dir: db.FilePath("patterns"), ItemIsDir: true},
2032
SystemPatternFile: "system.md",
2133
UniquePatternsFilePath: db.FilePath("unique_patterns.txt"),
34+
CustomPatternsDir: customPatternsDir,
2235
}
2336

2437
db.Sessions = &SessionsEntity{

plugins/db/fsdb/patterns.go

Lines changed: 57 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ type PatternsEntity struct {
1616
*StorageEntity
1717
SystemPatternFile string
1818
UniquePatternsFilePath string
19+
CustomPatternsDir string
1920
}
2021

2122
// Pattern represents a single pattern with its metadata
@@ -43,7 +44,7 @@ func (o *PatternsEntity) GetApplyVariables(
4344
}
4445

4546
// Use the resolved absolute path to get the pattern
46-
pattern, err = o.getFromFile(absPath)
47+
pattern, _ = o.getFromFile(absPath)
4748
} else {
4849
// Otherwise, get the pattern from the database
4950
pattern, err = o.getFromDB(source)
@@ -89,6 +90,19 @@ func (o *PatternsEntity) applyVariables(
8990

9091
// retrieves a pattern from the database by name
9192
func (o *PatternsEntity) getFromDB(name string) (ret *Pattern, err error) {
93+
// First check custom patterns directory if it exists
94+
if o.CustomPatternsDir != "" {
95+
customPatternPath := filepath.Join(o.CustomPatternsDir, name, o.SystemPatternFile)
96+
if pattern, customErr := os.ReadFile(customPatternPath); customErr == nil {
97+
ret = &Pattern{
98+
Name: name,
99+
Pattern: string(pattern),
100+
}
101+
return ret, nil
102+
}
103+
}
104+
105+
// Fallback to main patterns directory
92106
patternPath := filepath.Join(o.Dir, name, o.SystemPatternFile)
93107

94108
var pattern []byte
@@ -145,6 +159,48 @@ func (o *PatternsEntity) getFromFile(pathStr string) (pattern *Pattern, err erro
145159
return
146160
}
147161

162+
// GetNames overrides StorageEntity.GetNames to include custom patterns directory
163+
func (o *PatternsEntity) GetNames() (ret []string, err error) {
164+
// Get names from main patterns directory
165+
mainNames, err := o.StorageEntity.GetNames()
166+
if err != nil {
167+
return nil, err
168+
}
169+
170+
// Create a map to track unique pattern names (custom patterns override main ones)
171+
nameMap := make(map[string]bool)
172+
for _, name := range mainNames {
173+
nameMap[name] = true
174+
}
175+
176+
// Get names from custom patterns directory if it exists
177+
if o.CustomPatternsDir != "" {
178+
// Create a temporary StorageEntity for the custom directory
179+
customStorage := &StorageEntity{
180+
Dir: o.CustomPatternsDir,
181+
ItemIsDir: o.StorageEntity.ItemIsDir,
182+
FileExtension: o.StorageEntity.FileExtension,
183+
}
184+
185+
customNames, customErr := customStorage.GetNames()
186+
if customErr == nil {
187+
// Add custom patterns, they will override main patterns with same name
188+
for _, name := range customNames {
189+
nameMap[name] = true
190+
}
191+
}
192+
// Ignore errors from custom directory (it might not exist)
193+
}
194+
195+
// Convert map keys back to slice
196+
ret = make([]string, 0, len(nameMap))
197+
for name := range nameMap {
198+
ret = append(ret, name)
199+
}
200+
201+
return ret, nil
202+
}
203+
148204
// Get required for Storage interface
149205
func (o *PatternsEntity) Get(name string) (*Pattern, error) {
150206
// Use GetPattern with no variables

plugins/db/fsdb/patterns_test.go

Lines changed: 120 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -162,3 +162,123 @@ func TestPatternsEntity_Save(t *testing.T) {
162162
require.NoError(t, err)
163163
assert.Equal(t, content, data)
164164
}
165+
166+
func TestPatternsEntity_CustomPatterns(t *testing.T) {
167+
// Create main patterns directory
168+
mainDir, err := os.MkdirTemp("", "test-main-patterns-*")
169+
require.NoError(t, err)
170+
defer os.RemoveAll(mainDir)
171+
172+
// Create custom patterns directory
173+
customDir, err := os.MkdirTemp("", "test-custom-patterns-*")
174+
require.NoError(t, err)
175+
defer os.RemoveAll(customDir)
176+
177+
entity := &PatternsEntity{
178+
StorageEntity: &StorageEntity{
179+
Dir: mainDir,
180+
Label: "patterns",
181+
ItemIsDir: true,
182+
},
183+
SystemPatternFile: "system.md",
184+
CustomPatternsDir: customDir,
185+
}
186+
187+
// Create a pattern in main directory
188+
createTestPattern(t, &PatternsEntity{
189+
StorageEntity: &StorageEntity{
190+
Dir: mainDir,
191+
Label: "patterns",
192+
ItemIsDir: true,
193+
},
194+
SystemPatternFile: "system.md",
195+
}, "main-pattern", "Main pattern content")
196+
197+
// Create a pattern in custom directory
198+
createTestPattern(t, &PatternsEntity{
199+
StorageEntity: &StorageEntity{
200+
Dir: customDir,
201+
Label: "patterns",
202+
ItemIsDir: true,
203+
},
204+
SystemPatternFile: "system.md",
205+
}, "custom-pattern", "Custom pattern content")
206+
207+
// Create a pattern with same name in both directories (custom should override)
208+
createTestPattern(t, &PatternsEntity{
209+
StorageEntity: &StorageEntity{
210+
Dir: mainDir,
211+
Label: "patterns",
212+
ItemIsDir: true,
213+
},
214+
SystemPatternFile: "system.md",
215+
}, "shared-pattern", "Main shared pattern")
216+
217+
createTestPattern(t, &PatternsEntity{
218+
StorageEntity: &StorageEntity{
219+
Dir: customDir,
220+
Label: "patterns",
221+
ItemIsDir: true,
222+
},
223+
SystemPatternFile: "system.md",
224+
}, "shared-pattern", "Custom shared pattern")
225+
226+
// Test GetNames includes both directories
227+
names, err := entity.GetNames()
228+
require.NoError(t, err)
229+
assert.Contains(t, names, "main-pattern")
230+
assert.Contains(t, names, "custom-pattern")
231+
assert.Contains(t, names, "shared-pattern")
232+
233+
// Test that custom pattern overrides main pattern
234+
pattern, err := entity.getFromDB("shared-pattern")
235+
require.NoError(t, err)
236+
assert.Equal(t, "Custom shared pattern", pattern.Pattern)
237+
238+
// Test that main pattern is accessible when not overridden
239+
pattern, err = entity.getFromDB("main-pattern")
240+
require.NoError(t, err)
241+
assert.Equal(t, "Main pattern content", pattern.Pattern)
242+
243+
// Test that custom pattern is accessible
244+
pattern, err = entity.getFromDB("custom-pattern")
245+
require.NoError(t, err)
246+
assert.Equal(t, "Custom pattern content", pattern.Pattern)
247+
}
248+
249+
func TestPatternsEntity_CustomPatternsEmpty(t *testing.T) {
250+
// Test behavior when custom patterns directory is empty or doesn't exist
251+
mainDir, err := os.MkdirTemp("", "test-main-patterns-*")
252+
require.NoError(t, err)
253+
defer os.RemoveAll(mainDir)
254+
255+
entity := &PatternsEntity{
256+
StorageEntity: &StorageEntity{
257+
Dir: mainDir,
258+
Label: "patterns",
259+
ItemIsDir: true,
260+
},
261+
SystemPatternFile: "system.md",
262+
CustomPatternsDir: "/nonexistent/directory",
263+
}
264+
265+
// Create a pattern in main directory
266+
createTestPattern(t, &PatternsEntity{
267+
StorageEntity: &StorageEntity{
268+
Dir: mainDir,
269+
Label: "patterns",
270+
ItemIsDir: true,
271+
},
272+
SystemPatternFile: "system.md",
273+
}, "main-pattern", "Main pattern content")
274+
275+
// Test GetNames works even with nonexistent custom directory
276+
names, err := entity.GetNames()
277+
require.NoError(t, err)
278+
assert.Contains(t, names, "main-pattern")
279+
280+
// Test that main pattern is accessible
281+
pattern, err := entity.getFromDB("main-pattern")
282+
require.NoError(t, err)
283+
assert.Equal(t, "Main pattern content", pattern.Pattern)
284+
}
Lines changed: 61 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,61 @@
1+
package custom_patterns
2+
3+
import (
4+
"os"
5+
"path/filepath"
6+
"strings"
7+
8+
"github.com/danielmiessler/fabric/plugins"
9+
)
10+
11+
func NewCustomPatterns() (ret *CustomPatterns) {
12+
label := "Custom Patterns"
13+
ret = &CustomPatterns{}
14+
15+
ret.PluginBase = &plugins.PluginBase{
16+
Name: label,
17+
SetupDescription: "Custom Patterns - Set directory for your custom patterns (optional)",
18+
EnvNamePrefix: plugins.BuildEnvVariablePrefix(label),
19+
ConfigureCustom: ret.configure,
20+
}
21+
22+
ret.CustomPatternsDir = ret.AddSetupQuestionCustom("Directory", false,
23+
"Enter the path to your custom patterns directory (leave empty to skip)")
24+
25+
return
26+
}
27+
28+
type CustomPatterns struct {
29+
*plugins.PluginBase
30+
CustomPatternsDir *plugins.SetupQuestion
31+
}
32+
33+
func (o *CustomPatterns) configure() error {
34+
if o.CustomPatternsDir.Value != "" {
35+
// Expand home directory if needed
36+
if strings.HasPrefix(o.CustomPatternsDir.Value, "~/") {
37+
if homeDir, err := os.UserHomeDir(); err == nil {
38+
o.CustomPatternsDir.Value = filepath.Join(homeDir, o.CustomPatternsDir.Value[2:])
39+
}
40+
}
41+
42+
// Convert to absolute path
43+
if absPath, err := filepath.Abs(o.CustomPatternsDir.Value); err == nil {
44+
o.CustomPatternsDir.Value = absPath
45+
}
46+
47+
// Create the directory if it doesn't exist
48+
if err := os.MkdirAll(o.CustomPatternsDir.Value, 0755); err != nil {
49+
// If we can't create it, clear the value to avoid errors
50+
o.CustomPatternsDir.Value = ""
51+
}
52+
}
53+
54+
return nil
55+
}
56+
57+
// IsConfigured returns true if a custom patterns directory has been set
58+
func (o *CustomPatterns) IsConfigured() bool {
59+
// Check if the plugin has been configured with a directory
60+
return o.CustomPatternsDir.Value != ""
61+
}

0 commit comments

Comments
 (0)