Files
Fabric/internal/plugins/db/fsdb/patterns.go
Kayvan Sylvan ebc59ee82a refactor: move common package to domain and util packages for better organization
## CHANGES

- Move domain types from common to domain package
- Move utility functions from common to util package
- Update all import statements across codebase
- Reorganize OAuth storage functionality into util package
- Move file management functions to domain package
- Update test files to use new package structure
- Maintain backward compatibility for existing functionality
2025-07-08 23:26:11 -07:00

244 lines
6.6 KiB
Go

package fsdb
import (
"fmt"
"os"
"path/filepath"
"sort"
"strings"
"github.com/danielmiessler/fabric/internal/plugins/template"
"github.com/danielmiessler/fabric/internal/util"
)
const inputSentinel = "__FABRIC_INPUT_SENTINEL_TOKEN__"
type PatternsEntity struct {
*StorageEntity
SystemPatternFile string
UniquePatternsFilePath string
CustomPatternsDir string
}
// Pattern represents a single pattern with its metadata
type Pattern struct {
Name string
Description string
Pattern string
}
// GetApplyVariables main entry point for getting patterns from any source
func (o *PatternsEntity) GetApplyVariables(
source string, variables map[string]string, input string) (pattern *Pattern, err error) {
// Determine if this is a file path
isFilePath := strings.HasPrefix(source, "\\") ||
strings.HasPrefix(source, "/") ||
strings.HasPrefix(source, "~") ||
strings.HasPrefix(source, ".")
if isFilePath {
// Resolve the file path using GetAbsolutePath
absPath, err := util.GetAbsolutePath(source)
if err != nil {
return nil, fmt.Errorf("could not resolve file path: %v", err)
}
// Use the resolved absolute path to get the pattern
pattern, _ = o.getFromFile(absPath)
} else {
// Otherwise, get the pattern from the database
pattern, err = o.getFromDB(source)
}
if err != nil {
return
}
// Apply variables to the pattern
err = o.applyVariables(pattern, variables, input)
return
}
func (o *PatternsEntity) applyVariables(
pattern *Pattern, variables map[string]string, input string) (err error) {
// Ensure pattern has an {{input}} placeholder
// If not present, append it on a new line
if !strings.Contains(pattern.Pattern, "{{input}}") {
if !strings.HasSuffix(pattern.Pattern, "\n") {
pattern.Pattern += "\n"
}
pattern.Pattern += "{{input}}"
}
// Temporarily replace {{input}} with a sentinel token to protect it
// from recursive variable resolution
withSentinel := strings.ReplaceAll(pattern.Pattern, "{{input}}", inputSentinel)
// Process all other template variables in the pattern
// At this point, our sentinel ensures {{input}} won't be affected
var processed string
if processed, err = template.ApplyTemplate(withSentinel, variables, ""); err != nil {
return
}
// Finally, replace our sentinel with the actual user input
// The input has already been processed for variables if InputHasVars was true
pattern.Pattern = strings.ReplaceAll(processed, inputSentinel, input)
return
}
// retrieves a pattern from the database by name
func (o *PatternsEntity) getFromDB(name string) (ret *Pattern, err error) {
// First check custom patterns directory if it exists
if o.CustomPatternsDir != "" {
customPatternPath := filepath.Join(o.CustomPatternsDir, name, o.SystemPatternFile)
if pattern, customErr := os.ReadFile(customPatternPath); customErr == nil {
ret = &Pattern{
Name: name,
Pattern: string(pattern),
}
return ret, nil
}
}
// Fallback to main patterns directory
patternPath := filepath.Join(o.Dir, name, o.SystemPatternFile)
var pattern []byte
if pattern, err = os.ReadFile(patternPath); err != nil {
return
}
patternStr := string(pattern)
ret = &Pattern{
Name: name,
Pattern: patternStr,
}
return
}
func (o *PatternsEntity) PrintLatestPatterns(latestNumber int) (err error) {
var contents []byte
if contents, err = os.ReadFile(o.UniquePatternsFilePath); err != nil {
err = fmt.Errorf("could not read unique patterns file. Please run --updatepatterns (%s)", err)
return
}
uniquePatterns := strings.Split(string(contents), "\n")
if latestNumber > len(uniquePatterns) {
latestNumber = len(uniquePatterns)
}
for i := len(uniquePatterns) - 1; i > len(uniquePatterns)-latestNumber-1; i-- {
fmt.Println(uniquePatterns[i])
}
return
}
// reads a pattern from a file path and returns it
func (o *PatternsEntity) getFromFile(pathStr string) (pattern *Pattern, err error) {
// Handle home directory expansion
if strings.HasPrefix(pathStr, "~/") {
var homedir string
if homedir, err = os.UserHomeDir(); err != nil {
err = fmt.Errorf("could not get home directory: %v", err)
return
}
pathStr = filepath.Join(homedir, pathStr[2:])
}
var content []byte
if content, err = os.ReadFile(pathStr); err != nil {
err = fmt.Errorf("could not read pattern file %s: %v", pathStr, err)
return
}
pattern = &Pattern{
Name: pathStr,
Pattern: string(content),
}
return
}
// GetNames overrides StorageEntity.GetNames to include custom patterns directory
func (o *PatternsEntity) GetNames() (ret []string, err error) {
// Get names from main patterns directory
mainNames, err := o.StorageEntity.GetNames()
if err != nil {
return nil, err
}
// Create a map to track unique pattern names (custom patterns override main ones)
nameMap := make(map[string]bool)
for _, name := range mainNames {
nameMap[name] = true
}
// Get names from custom patterns directory if it exists
if o.CustomPatternsDir != "" {
// Create a temporary StorageEntity for the custom directory
customStorage := &StorageEntity{
Dir: o.CustomPatternsDir,
ItemIsDir: o.StorageEntity.ItemIsDir,
FileExtension: o.StorageEntity.FileExtension,
}
customNames, customErr := customStorage.GetNames()
if customErr == nil {
// Add custom patterns, they will override main patterns with same name
for _, name := range customNames {
nameMap[name] = true
}
}
// Ignore errors from custom directory (it might not exist)
}
// Convert map keys back to slice
ret = make([]string, 0, len(nameMap))
for name := range nameMap {
ret = append(ret, name)
}
// Sort the patterns alphabetically
sort.Strings(ret)
return ret, nil
}
// ListNames overrides StorageEntity.ListNames to use PatternsEntity.GetNames
func (o *PatternsEntity) ListNames(shellCompleteList bool) (err error) {
var names []string
if names, err = o.GetNames(); err != nil {
return
}
if len(names) == 0 {
if !shellCompleteList {
fmt.Printf("\nNo %v\n", o.StorageEntity.Label)
}
return
}
for _, item := range names {
fmt.Printf("%s\n", item)
}
return
}
// Get required for Storage interface
func (o *PatternsEntity) Get(name string) (*Pattern, error) {
// Use GetPattern with no variables
return o.GetApplyVariables(name, nil, "")
}
func (o *PatternsEntity) Save(name string, content []byte) (err error) {
patternDir := filepath.Join(o.Dir, name)
if err = os.MkdirAll(patternDir, os.ModePerm); err != nil {
return fmt.Errorf("could not create pattern directory: %v", err)
}
patternPath := filepath.Join(patternDir, o.SystemPatternFile)
if err = os.WriteFile(patternPath, content, 0644); err != nil {
return fmt.Errorf("could not save pattern: %v", err)
}
return nil
}