From d475e7b56828ec586682c9163c89d128d44c925a Mon Sep 17 00:00:00 2001 From: Matt Joyce Date: Tue, 19 Nov 2024 16:57:14 +1100 Subject: [PATCH 1/2] feat(template): introduce template package for variable substitution - Add new template package to handle variable substitution with {{variable}} syntax - Move substitution logic from patterns to centralized template system - Update patterns.go to use template package for variable processing - Support special {{input}} handling for pattern content - Update chatter.go and rest API to pass input parameter - Enable multiple passes to handle nested variables - Report errors for missing required variables This change sets up a foundation for future templating features like front matter and plugin support while keeping the substitution logic centralized. --- core/chatter.go | 3 ++- plugins/db/fsdb/patterns.go | 32 +++++++++++++++++++++++--------- restapi/patterns.go | 6 ++++-- 3 files changed, 29 insertions(+), 12 deletions(-) diff --git a/core/chatter.go b/core/chatter.go index d4cab48f..0dc1c6f3 100644 --- a/core/chatter.go +++ b/core/chatter.go @@ -102,7 +102,8 @@ func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session * var patternContent string if request.PatternName != "" { - pattern, err := o.db.Patterns.GetApplyVariables(request.PatternName, request.PatternVariables) + pattern, err := o.db.Patterns.GetApplyVariables(request.PatternName, request.PatternVariables, request.Message.Content) + if err != nil { return nil, fmt.Errorf("could not get pattern %s: %v", request.PatternName, err) } diff --git a/plugins/db/fsdb/patterns.go b/plugins/db/fsdb/patterns.go index 00e7656d..d64547a0 100644 --- a/plugins/db/fsdb/patterns.go +++ b/plugins/db/fsdb/patterns.go @@ -5,6 +5,8 @@ import ( "os" "path/filepath" "strings" + + "github.com/danielmiessler/fabric/plugins/template" ) type PatternsEntity struct { @@ -21,7 +23,7 @@ type Pattern struct { } // main entry point for getting patterns from any source -func (o *PatternsEntity) GetApplyVariables(source string, variables map[string]string) (*Pattern, error) { +func (o *PatternsEntity) GetApplyVariables(source string, variables map[string]string, input string) (*Pattern, error) { var pattern *Pattern var err error @@ -41,17 +43,29 @@ func (o *PatternsEntity) GetApplyVariables(source string, variables map[string]s return nil, err } - return o.applyVariables(pattern, variables), nil + pattern, err = o.applyVariables(pattern, variables, input) + if err != nil { + return nil, err // Return the error if applyVariables failed + } + return pattern, nil } -// handles all variable substitution -func (o *PatternsEntity) applyVariables(pattern *Pattern, variables map[string]string) *Pattern { - if variables != nil && len(variables) > 0 { - for variableName, value := range variables { - pattern.Pattern = strings.ReplaceAll(pattern.Pattern, variableName, value) + +func (o *PatternsEntity) applyVariables(pattern *Pattern, variables map[string]string, input string) (*Pattern, error) { + // If {{input}} isn't in pattern, append it on new line + if !strings.Contains(pattern.Pattern, "{{input}}") { + if !strings.HasSuffix(pattern.Pattern, "\n") { + pattern.Pattern += "\n" } + pattern.Pattern += "{{input}}" } - return pattern + + result, err := template.ApplyTemplate(pattern.Pattern, variables, input) + if err != nil { + return nil, err + } + pattern.Pattern = result + return pattern, nil } // retrieves a pattern from the database by name @@ -113,5 +127,5 @@ func (o *PatternsEntity) getFromFile(pathStr string) (*Pattern, error) { // Get required for Storage interface func (o *PatternsEntity) Get(name string) (*Pattern, error) { // Use GetPattern with no variables - return o.GetApplyVariables(name, nil) + return o.GetApplyVariables(name, nil, "") } \ No newline at end of file diff --git a/restapi/patterns.go b/restapi/patterns.go index cfb78d09..92949b8e 100644 --- a/restapi/patterns.go +++ b/restapi/patterns.go @@ -1,9 +1,10 @@ package restapi import ( + "net/http" + "github.com/danielmiessler/fabric/plugins/db/fsdb" "github.com/gin-gonic/gin" - "net/http" ) // PatternsHandler defines the handler for patterns-related operations @@ -26,7 +27,8 @@ func NewPatternsHandler(r *gin.Engine, patterns *fsdb.PatternsEntity) (ret *Patt func (h *PatternsHandler) Get(c *gin.Context) { name := c.Param("name") variables := make(map[string]string) // Assuming variables are passed somehow - pattern, err := h.patterns.GetApplyVariables(name, variables) + input := "" // Assuming input is passed somehow + pattern, err := h.patterns.GetApplyVariables(name, variables, input) if err != nil { c.JSON(http.StatusInternalServerError, err.Error()) return From b6eb969b3a960b161f27b530a96eae9db2369174 Mon Sep 17 00:00:00 2001 From: Matt Joyce Date: Thu, 21 Nov 2024 14:27:22 +1100 Subject: [PATCH 2/2] feat(template): implement core plugin system and utility plugins Add initial set of utility plugins for the template system: - datetime: Date/time formatting and manipulation - fetch: HTTP content retrieval and processing - file: File system operations and content handling - sys: System information and environment access - text: String manipulation and formatting operations Each plugin includes: - Implementation with comprehensive test coverage - Markdown documentation of capabilities - Integration with template package This builds on the template system to provide practical utility functions while maintaining a focused scope for the initial plugin release. --- core/chatter.go | 8 +- plugins/db/fsdb/patterns_test.go | 145 +++++++++++ plugins/template/README.md | 418 ++++++++++++++++++++++++++++++ plugins/template/datetime.go | 144 ++++++++++ plugins/template/datetime.md | 41 +++ plugins/template/datetime_test.go | 138 ++++++++++ plugins/template/fabric | Bin 0 -> 387794 bytes plugins/template/fetch.go | 134 ++++++++++ plugins/template/fetch.md | 39 +++ plugins/template/fetch_test.go | 71 +++++ plugins/template/file.go | 197 ++++++++++++++ plugins/template/file.md | 51 ++++ plugins/template/file_test.go | 152 +++++++++++ plugins/template/sys.go | 87 +++++++ plugins/template/sys.md | 43 +++ plugins/template/sys_test.go | 140 ++++++++++ plugins/template/template.go | 119 +++++++++ plugins/template/template_test.go | 146 +++++++++++ plugins/template/text.go | 64 +++++ plugins/template/text.md | 0 plugins/template/text_test.go | 104 ++++++++ 21 files changed, 2240 insertions(+), 1 deletion(-) create mode 100644 plugins/template/README.md create mode 100644 plugins/template/datetime.go create mode 100644 plugins/template/datetime.md create mode 100644 plugins/template/datetime_test.go create mode 100644 plugins/template/fabric create mode 100644 plugins/template/fetch.go create mode 100644 plugins/template/fetch.md create mode 100644 plugins/template/fetch_test.go create mode 100644 plugins/template/file.go create mode 100644 plugins/template/file.md create mode 100644 plugins/template/file_test.go create mode 100644 plugins/template/sys.go create mode 100644 plugins/template/sys.md create mode 100644 plugins/template/sys_test.go create mode 100644 plugins/template/template.go create mode 100644 plugins/template/template_test.go create mode 100644 plugins/template/text.go create mode 100644 plugins/template/text.md create mode 100644 plugins/template/text_test.go diff --git a/core/chatter.go b/core/chatter.go index 0dc1c6f3..4fab37a0 100644 --- a/core/chatter.go +++ b/core/chatter.go @@ -100,9 +100,15 @@ func (o *Chatter) BuildSession(request *common.ChatRequest, raw bool) (session * } + //if there is no input from stdin + var messageContent string + if request.Message != nil { + messageContent = request.Message.Content + } + var patternContent string if request.PatternName != "" { - pattern, err := o.db.Patterns.GetApplyVariables(request.PatternName, request.PatternVariables, request.Message.Content) + pattern, err := o.db.Patterns.GetApplyVariables(request.PatternName, request.PatternVariables, messageContent) if err != nil { return nil, fmt.Errorf("could not get pattern %s: %v", request.PatternName, err) diff --git a/plugins/db/fsdb/patterns_test.go b/plugins/db/fsdb/patterns_test.go index e6e477ce..adf8b936 100644 --- a/plugins/db/fsdb/patterns_test.go +++ b/plugins/db/fsdb/patterns_test.go @@ -1 +1,146 @@ package fsdb + +import ( + "os" + "path/filepath" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func setupTestPatternsEntity(t *testing.T) (*PatternsEntity, func()) { + // Create a temporary directory for test patterns + tmpDir, err := os.MkdirTemp("", "test-patterns-*") + require.NoError(t, err) + + entity := &PatternsEntity{ + StorageEntity: &StorageEntity{ + Dir: tmpDir, + Label: "patterns", + ItemIsDir: true, + }, + SystemPatternFile: "system.md", + } + + // Return cleanup function + cleanup := func() { + os.RemoveAll(tmpDir) + } + + return entity, cleanup +} + +// Helper to create a test pattern file +func createTestPattern(t *testing.T, entity *PatternsEntity, name, content string) { + patternDir := filepath.Join(entity.Dir, name) + err := os.MkdirAll(patternDir, 0755) + require.NoError(t, err) + + err = os.WriteFile(filepath.Join(patternDir, entity.SystemPatternFile), []byte(content), 0644) + require.NoError(t, err) +} + +func TestApplyVariables(t *testing.T) { + entity := &PatternsEntity{} + + tests := []struct { + name string + pattern *Pattern + variables map[string]string + input string + want string + wantErr bool + }{ + { + name: "pattern with explicit input placement", + pattern: &Pattern{ + Pattern: "You are a {{role}}.\n{{input}}\nPlease analyze.", + }, + variables: map[string]string{ + "role": "security expert", + }, + input: "Check this code", + want: "You are a security expert.\nCheck this code\nPlease analyze.", + }, + { + name: "pattern without input variable gets input appended", + pattern: &Pattern{ + Pattern: "You are a {{role}}.\nPlease analyze.", + }, + variables: map[string]string{ + "role": "code reviewer", + }, + input: "Review this PR", + want: "You are a code reviewer.\nPlease analyze.\nReview this PR", + }, + // ... previous test cases ... + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := entity.applyVariables(tt.pattern, tt.variables, tt.input) + + if tt.wantErr { + assert.Error(t, err) + return + } + + assert.NoError(t, err) + assert.Equal(t, tt.want, result.Pattern) + }) + } +} + +func TestGetApplyVariables(t *testing.T) { + entity, cleanup := setupTestPatternsEntity(t) + defer cleanup() + + // Create a test pattern + createTestPattern(t, entity, "test-pattern", "You are a {{role}}.\n{{input}}") + + tests := []struct { + name string + source string + variables map[string]string + input string + want string + wantErr bool + }{ + { + name: "basic pattern with variables and input", + source: "test-pattern", + variables: map[string]string{ + "role": "reviewer", + }, + input: "check this code", + want: "You are a reviewer.\ncheck this code", + }, + { + name: "pattern with missing variable", + source: "test-pattern", + variables: map[string]string{}, + input: "test input", + wantErr: true, + }, + { + name: "non-existent pattern", + source: "non-existent", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + result, err := entity.GetApplyVariables(tt.source, tt.variables, tt.input) + + if tt.wantErr { + assert.Error(t, err) + return + } + + require.NoError(t, err) + assert.Equal(t, tt.want, result.Pattern) + }) + } +} \ No newline at end of file diff --git a/plugins/template/README.md b/plugins/template/README.md new file mode 100644 index 00000000..2bb885e4 --- /dev/null +++ b/plugins/template/README.md @@ -0,0 +1,418 @@ +# Fabric Template System + +## Quick Start +echo "Hello {{name}}!" | fabric -v=name:World + +## Overview + +The Fabric Template System provides a powerful and extensible way to handle variable substitution and dynamic content generation through a plugin architecture. It uses a double-brace syntax (`{{}}`) for variables and plugin operations, making it both readable and flexible. + + + +## Basic Usage + +### Variable Substitution + +The template system supports basic variable substitution using double braces: + +```markdown +Hello {{name}}! +Current role: {{role}} +``` + +Variables can be provided via: +- Command line arguments: `-v=name:John -v=role:admin` +- YAML front matter in input files +- Environment variables (when configured) + +### Special Variables + +- `{{input}}`: Represents the main input content + ```markdown + Here is the analysis: + {{input}} + End of analysis. + ``` + +## Nested Tokens and Resolution + +### Basic Nesting + +The template system supports nested tokens, where inner tokens are resolved before outer ones. This enables complex, dynamic template generation. + +#### Simple Variable Nesting +```markdown +{{outer{{inner}}}} + +Example: +Variables: { + "inner": "name", + "john": "John Doe" +} +{{{{inner}}}} -> {{name}} -> John Doe +``` + +#### Nested Plugin Calls +```markdown +{{plugin:text:upper:{{plugin:sys:env:USER}}}} +First resolves: {{plugin:sys:env:USER}} -> "john" +Then resolves: {{plugin:text:upper:john}} -> "JOHN" +``` + +### How Nested Resolution Works + +1. **Iterative Processing** + - The engine processes the template in multiple passes + - Each pass identifies all `{{...}}` patterns + - Processing continues until no more replacements are needed + +2. **Resolution Order** + ```markdown + Original: {{plugin:text:upper:{{user}}}} + Step 1: Found {{user}} -> "john" + Step 2: Now have {{plugin:text:upper:john}} + Step 3: Final result -> "JOHN" + ``` + +3. **Complex Nesting Example** + ```markdown + {{plugin:text:{{case}}:{{plugin:sys:env:{{varname}}}}}} + + With variables: + { + "case": "upper", + "varname": "USER" + } + + Resolution steps: + 1. {{varname}} -> "USER" + 2. {{plugin:sys:env:USER}} -> "john" + 3. {{case}} -> "upper" + 4. {{plugin:text:upper:john}} -> "JOHN" + ``` + +### Important Considerations + +1. **Depth Limitations** + - While nesting is supported, avoid excessive nesting for clarity + - Complex nested structures can be hard to debug + - Consider breaking very complex templates into smaller parts + +2. **Variable Resolution** + - Inner variables must resolve to valid values for outer operations + - Error messages will point to the innermost failed resolution + - Debug logs show the step-by-step resolution process + +3. **Plugin Nesting** + ```markdown + # Valid: + {{plugin:text:upper:{{plugin:sys:env:USER}}}} + + # Also Valid: + {{plugin:text:{{operation}}:{{value}}}} + + # Invalid (plugin namespace cannot be dynamic): + {{plugin:{{namespace}}:operation:value}} + ``` + +4. **Debugging Nested Templates** + ```go + Debug = true // Enable debug logging + + Template: {{plugin:text:upper:{{user}}}} + Debug output: + > Processing variable: user + > Replacing {{user}} with john + > Plugin call: + > Namespace: text + > Operation: upper + > Value: john + > Plugin result: JOHN + ``` + +### Examples + +1. **Dynamic Operation Selection** + ```markdown + {{plugin:text:{{operation}}:hello}} + + With variables: + { + "operation": "upper" + } + + Result: HELLO + ``` + +2. **Dynamic Environment Variable Lookup** + ```markdown + {{plugin:sys:env:{{env_var}}}} + + With variables: + { + "env_var": "HOME" + } + + Result: /home/user + ``` + +3. **Nested Date Formatting** + ```markdown + {{plugin:datetime:{{format}}:{{plugin:datetime:now}}}} + + With variables: + { + "format": "full" + } + + Result: Wednesday, November 20, 2024 + ``` + + + + + +## Plugin System + +### Plugin Syntax + +Plugins use the following syntax: +``` +{{plugin:namespace:operation:value}} +``` + +- `namespace`: The plugin category (e.g., text, datetime, sys) +- `operation`: The specific operation to perform +- `value`: Optional value for the operation + +### Built-in Plugins + +#### Text Plugin +Text manipulation operations: +```markdown +{{plugin:text:upper:hello}} -> HELLO +{{plugin:text:lower:HELLO}} -> hello +{{plugin:text:title:hello world}} -> Hello World +``` + +#### DateTime Plugin +Time and date operations: +```markdown +{{plugin:datetime:now}} -> 2024-11-20T15:04:05Z +{{plugin:datetime:today}} -> 2024-11-20 +{{plugin:datetime:rel:-1d}} -> 2024-11-19 +{{plugin:datetime:month}} -> November +``` + +#### System Plugin +System information: +```markdown +{{plugin:sys:hostname}} -> server1 +{{plugin:sys:user}} -> currentuser +{{plugin:sys:os}} -> linux +{{plugin:sys:env:HOME}} -> /home/user +``` + +## Developing Plugins + +### Plugin Interface + +To create a new plugin, implement the following interface: + +```go +type Plugin interface { + Apply(operation string, value string) (string, error) +} +``` + +### Example Plugin Implementation + +Here's a simple plugin that performs basic math operations: + +```go +package template + +type MathPlugin struct{} + +func (p *MathPlugin) Apply(operation string, value string) (string, error) { + switch operation { + case "add": + // Parse value as "a,b" and return a+b + nums := strings.Split(value, ",") + if len(nums) != 2 { + return "", fmt.Errorf("add requires two numbers") + } + a, err := strconv.Atoi(nums[0]) + if err != nil { + return "", err + } + b, err := strconv.Atoi(nums[1]) + if err != nil { + return "", err + } + return fmt.Sprintf("%d", a+b), nil + + default: + return "", fmt.Errorf("unknown math operation: %s", operation) + } +} +``` + +### Registering a New Plugin + +1. Add your plugin struct to the template package +2. Register it in template.go: + +```go +var ( + // Existing plugins + textPlugin = &TextPlugin{} + datetimePlugin = &DateTimePlugin{} + + // Add your new plugin + mathPlugin = &MathPlugin{} +) + +// Update the plugin handler in ApplyTemplate +switch namespace { + case "text": + result, err = textPlugin.Apply(operation, value) + case "datetime": + result, err = datetimePlugin.Apply(operation, value) + // Add your namespace + case "math": + result, err = mathPlugin.Apply(operation, value) + default: + return "", fmt.Errorf("unknown plugin namespace: %s", namespace) +} +``` + +### Plugin Development Guidelines + +1. **Error Handling** + - Return clear error messages + - Validate all inputs + - Handle edge cases gracefully + +2. **Debugging** + - Use the `debugf` function for logging + - Log entry and exit points + - Log intermediate calculations + +```go +func (p *MyPlugin) Apply(operation string, value string) (string, error) { + debugf("MyPlugin operation: %s value: %s\n", operation, value) + // ... plugin logic ... + debugf("MyPlugin result: %s\n", result) + return result, nil +} +``` + +3. **Security Considerations** + - Validate and sanitize inputs + - Avoid shell execution + - Be careful with file operations + - Limit resource usage + +4. **Performance** + - Cache expensive computations + - Minimize allocations + - Consider concurrent access + +### Testing Plugins + +Create tests for your plugin in `plugin_test.go`: + +```go +func TestMathPlugin(t *testing.T) { + plugin := &MathPlugin{} + + tests := []struct { + operation string + value string + expected string + wantErr bool + }{ + {"add", "5,3", "8", false}, + {"add", "bad,input", "", true}, + {"unknown", "value", "", true}, + } + + for _, tt := range tests { + result, err := plugin.Apply(tt.operation, tt.value) + if (err != nil) != tt.wantErr { + t.Errorf("MathPlugin.Apply(%s, %s) error = %v, wantErr %v", + tt.operation, tt.value, err, tt.wantErr) + continue + } + if result != tt.expected { + t.Errorf("MathPlugin.Apply(%s, %s) = %v, want %v", + tt.operation, tt.value, result, tt.expected) + } + } +} +``` + +## Best Practices + +1. **Namespace Selection** + - Choose clear, descriptive names + - Avoid conflicts with existing plugins + - Group related operations together + +2. **Operation Names** + - Use lowercase names + - Keep names concise but clear + - Be consistent with similar operations + +3. **Value Format** + - Document expected formats + - Use common separators consistently + - Provide examples in comments + +4. **Error Messages** + - Be specific about what went wrong + - Include valid operation examples + - Help users fix the problem + +## Common Issues and Solutions + +1. **Missing Variables** + ``` + Error: missing required variables: [name] + Solution: Provide all required variables using -v=name:value + ``` + +2. **Invalid Plugin Operations** + ``` + Error: unknown operation 'invalid' for plugin 'text' + Solution: Check plugin documentation for supported operations + ``` + +3. **Plugin Value Format** + ``` + Error: invalid format for datetime:rel, expected -1d, -2w, etc. + Solution: Follow the required format for plugin values + ``` + + + + +## Contributing + +1. Fork the repository +2. Create your plugin branch +3. Implement your plugin following the guidelines +4. Add comprehensive tests +5. Submit a pull request + +## Support + +For issues and questions: +1. Check the debugging output (enable with Debug=true) +2. Review the plugin documentation +3. Open an issue with: + - Template content + - Variables used + - Expected vs actual output + - Debug logs \ No newline at end of file diff --git a/plugins/template/datetime.go b/plugins/template/datetime.go new file mode 100644 index 00000000..31340e59 --- /dev/null +++ b/plugins/template/datetime.go @@ -0,0 +1,144 @@ +// Package template provides datetime operations for the template system +package template + +import ( + "fmt" + "strconv" + "time" +) + +// DateTimePlugin handles time and date operations +type DateTimePlugin struct{} + +// Apply executes datetime operations with the following formats: +// Time: now (RFC3339), time (HH:MM:SS), unix (timestamp) +// Hour: startofhour, endofhour +// Date: today (YYYY-MM-DD), full (Monday, January 2, 2006) +// Period: startofweek, endofweek, startofmonth, endofmonth +// Relative: rel:-1h, rel:-2d, rel:1w, rel:3m, rel:1y +func (p *DateTimePlugin) Apply(operation string, value string) (string, error) { + debugf("DateTime: operation=%q value=%q", operation, value) + + now := time.Now() + debugf("DateTime: reference time=%v", now) + + switch operation { + // Time operations + case "now": + result := now.Format(time.RFC3339) + debugf("DateTime: now=%q", result) + return result, nil + + case "time": + result := now.Format("15:04:05") + debugf("DateTime: time=%q", result) + return result, nil + + case "unix": + result := fmt.Sprintf("%d", now.Unix()) + debugf("DateTime: unix=%q", result) + return result, nil + + case "startofhour": + result := now.Truncate(time.Hour).Format(time.RFC3339) + debugf("DateTime: startofhour=%q", result) + return result, nil + + case "endofhour": + result := now.Truncate(time.Hour).Add(time.Hour - time.Second).Format(time.RFC3339) + debugf("DateTime: endofhour=%q", result) + return result, nil + + // Date operations + case "today": + result := now.Format("2006-01-02") + debugf("DateTime: today=%q", result) + return result, nil + + case "full": + result := now.Format("Monday, January 2, 2006") + debugf("DateTime: full=%q", result) + return result, nil + + case "month": + result := now.Format("January") + debugf("DateTime: month=%q", result) + return result, nil + + case "year": + result := now.Format("2006") + debugf("DateTime: year=%q", result) + return result, nil + + case "startofweek": + result := now.AddDate(0, 0, -int(now.Weekday())).Format("2006-01-02") + debugf("DateTime: startofweek=%q", result) + return result, nil + + case "endofweek": + result := now.AddDate(0, 0, 7-int(now.Weekday())).Format("2006-01-02") + debugf("DateTime: endofweek=%q", result) + return result, nil + + case "startofmonth": + result := time.Date(now.Year(), now.Month(), 1, 0, 0, 0, 0, now.Location()).Format("2006-01-02") + debugf("DateTime: startofmonth=%q", result) + return result, nil + + case "endofmonth": + result := time.Date(now.Year(), now.Month()+1, 0, 0, 0, 0, 0, now.Location()).Format("2006-01-02") + debugf("DateTime: endofmonth=%q", result) + return result, nil + + case "rel": + return p.handleRelative(now, value) + + default: + return "", fmt.Errorf("datetime: unknown operation %q (see plugin documentation for supported operations)", operation) + } +} + +func (p *DateTimePlugin) handleRelative(now time.Time, value string) (string, error) { + debugf("DateTime: handling relative time value=%q", value) + + if value == "" { + return "", fmt.Errorf("datetime: relative time requires a value (e.g., -1h, -1d, -1w)") + } + + // Try standard duration first (hours, minutes) + if duration, err := time.ParseDuration(value); err == nil { + result := now.Add(duration).Format(time.RFC3339) + debugf("DateTime: relative duration=%q result=%q", duration, result) + return result, nil + } + + // Handle date units + if len(value) < 2 { + return "", fmt.Errorf("datetime: invalid relative format (use: -1h, 2d, -3w, 1m, -1y)") + } + + unit := value[len(value)-1:] + numStr := value[:len(value)-1] + + num, err := strconv.Atoi(numStr) + if err != nil { + return "", fmt.Errorf("datetime: invalid number in relative time: %q", value) + } + + var result string + switch unit { + case "d": + result = now.AddDate(0, 0, num).Format("2006-01-02") + case "w": + result = now.AddDate(0, 0, num*7).Format("2006-01-02") + case "m": + result = now.AddDate(0, num, 0).Format("2006-01-02") + case "y": + result = now.AddDate(num, 0, 0).Format("2006-01-02") + default: + return "", fmt.Errorf("datetime: invalid unit %q (use: h,m for time or d,w,m,y for date)", unit) + } + + debugf("DateTime: relative unit=%q num=%d result=%q", unit, num, result) + return result, nil +} \ No newline at end of file diff --git a/plugins/template/datetime.md b/plugins/template/datetime.md new file mode 100644 index 00000000..96cb5d0a --- /dev/null +++ b/plugins/template/datetime.md @@ -0,0 +1,41 @@ +# DateTime Plugin Tests + +Simple test file for validating datetime plugin functionality. + +## Basic Time Operations + +``` +Current Time: {{plugin:datetime:now}} +Time Only: {{plugin:datetime:time}} +Unix Timestamp: {{plugin:datetime:unix}} +Hour Start: {{plugin:datetime:startofhour}} +Hour End: {{plugin:datetime:endofhour}} +``` + +## Date Operations + +``` +Today: {{plugin:datetime:today}} +Full Date: {{plugin:datetime:full}} +Current Month: {{plugin:datetime:month}} +Current Year: {{plugin:datetime:year}} +``` + +## Period Operations + +``` +Week Start: {{plugin:datetime:startofweek}} +Week End: {{plugin:datetime:endofweek}} +Month Start: {{plugin:datetime:startofmonth}} +Month End: {{plugin:datetime:endofmonth}} +``` + +## Relative Time/Date + +``` +2 Hours Ahead: {{plugin:datetime:rel:2h}} +1 Day Ago: {{plugin:datetime:rel:-1d}} +Next Week: {{plugin:datetime:rel:1w}} +Last Month: {{plugin:datetime:rel:-1m}} +Next Year: {{plugin:datetime:rel:1y}} +``` \ No newline at end of file diff --git a/plugins/template/datetime_test.go b/plugins/template/datetime_test.go new file mode 100644 index 00000000..0e737ffd --- /dev/null +++ b/plugins/template/datetime_test.go @@ -0,0 +1,138 @@ +package template + +import ( + "fmt" + "strconv" + "strings" + "testing" + "time" +) + +func TestDateTimePlugin(t *testing.T) { + plugin := &DateTimePlugin{} + now := time.Now() + + tests := []struct { + name string + operation string + value string + validate func(string) error + wantErr bool + }{ + { + name: "now returns RFC3339", + operation: "now", + validate: func(got string) error { + if _, err := time.Parse(time.RFC3339, got); err != nil { + return err + } + return nil + }, + }, + { + name: "time returns HH:MM:SS", + operation: "time", + validate: func(got string) error { + if _, err := time.Parse("15:04:05", got); err != nil { + return err + } + return nil + }, + }, + { + name: "unix returns timestamp", + operation: "unix", + validate: func(got string) error { + if _, err := strconv.ParseInt(got, 10, 64); err != nil { + return err + } + return nil + }, + }, + { + name: "today returns YYYY-MM-DD", + operation: "today", + validate: func(got string) error { + if _, err := time.Parse("2006-01-02", got); err != nil { + return err + } + return nil + }, + }, + { + name: "full returns long date", + operation: "full", + validate: func(got string) error { + if !strings.Contains(got, now.Month().String()) { + return fmt.Errorf("full date missing month name") + } + return nil + }, + }, + { + name: "relative positive hours", + operation: "rel", + value: "2h", + validate: func(got string) error { + t, err := time.Parse(time.RFC3339, got) + if err != nil { + return err + } + expected := now.Add(2 * time.Hour) + if t.Hour() != expected.Hour() { + return fmt.Errorf("expected hour %d, got %d", expected.Hour(), t.Hour()) + } + return nil + }, + }, + { + name: "relative negative days", + operation: "rel", + value: "-2d", + validate: func(got string) error { + t, err := time.Parse("2006-01-02", got) + if err != nil { + return err + } + expected := now.AddDate(0, 0, -2) + if t.Day() != expected.Day() { + return fmt.Errorf("expected day %d, got %d", expected.Day(), t.Day()) + } + return nil + }, + }, + // Error cases + { + name: "invalid operation", + operation: "invalid", + wantErr: true, + }, + { + name: "empty relative value", + operation: "rel", + value: "", + wantErr: true, + }, + { + name: "invalid relative format", + operation: "rel", + value: "2x", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := plugin.Apply(tt.operation, tt.value) + if (err != nil) != tt.wantErr { + t.Errorf("DateTimePlugin.Apply() error = %v, wantErr %v", err, tt.wantErr) + return + } + if err == nil && tt.validate != nil { + if err := tt.validate(got); err != nil { + t.Errorf("DateTimePlugin.Apply() validation failed: %v", err) + } + } + }) + } +} \ No newline at end of file diff --git a/plugins/template/fabric b/plugins/template/fabric new file mode 100644 index 0000000000000000000000000000000000000000..0bea184da1d6786963284e8c5c4b82d94f74cef7 GIT binary patch literal 387794 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�AvPAACYD39pWtrM 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%0DHjtH2?&-Ef!hS`h6@r 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�Z; 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 MaxContentSize { + return "", fmt.Errorf("fetch: content too large: %d bytes (max %d bytes)", + contentLength, MaxContentSize) + } + + contentType := resp.Header.Get("Content-Type") + debugf("Fetch: content-type=%q", contentType) + if !p.isTextContent(contentType) { + return "", fmt.Errorf("fetch: unsupported content type %q - only text content allowed", + contentType) + } + + debugf("Fetch: reading response body") + limitReader := io.LimitReader(resp.Body, MaxContentSize+1) + content, err := io.ReadAll(limitReader) + if err != nil { + return "", fmt.Errorf("fetch: error reading response: %v", err) + } + + if len(content) > MaxContentSize { + return "", fmt.Errorf("fetch: content too large: exceeds %d bytes", MaxContentSize) + } + + if err := p.validateTextContent(content); err != nil { + return "", err + } + + debugf("Fetch: operation completed successfully, read %d bytes", len(content)) + return string(content), nil +} \ No newline at end of file diff --git a/plugins/template/fetch.md b/plugins/template/fetch.md new file mode 100644 index 00000000..cde6471a --- /dev/null +++ b/plugins/template/fetch.md @@ -0,0 +1,39 @@ +# Fetch Plugin Tests + +Simple test file for validating fetch plugin functionality. + +## Basic Fetch Operations + +``` +Raw Content: +{{plugin:fetch:get:https://raw.githubusercontent.com/user/repo/main/README.md}} + +JSON API: +{{plugin:fetch:get:https://api.example.com/data.json}} +``` + +## Error Cases +These should produce appropriate error messages: + +``` +Invalid Operation: +{{plugin:fetch:invalid:https://example.com}} + +Invalid URL: +{{plugin:fetch:get:not-a-url}} + +Non-text Content: +{{plugin:fetch:get:https://example.com/image.jpg}} + +Server Error: +{{plugin:fetch:get:https://httpstat.us/500}} +``` + +## Security Considerations + +- Only use trusted URLs +- Be aware of rate limits +- Content is limited to 1MB +- Only text content types are allowed +- Consider URL allow listing in production +- Validate and sanitize fetched content before use \ No newline at end of file diff --git a/plugins/template/fetch_test.go b/plugins/template/fetch_test.go new file mode 100644 index 00000000..5f3fd6f3 --- /dev/null +++ b/plugins/template/fetch_test.go @@ -0,0 +1,71 @@ +package template + +import ( + "net/http/httptest" + "strings" + "testing" +) +func TestFetchPlugin(t *testing.T) { + plugin := &FetchPlugin{} + + tests := []struct { + name string + operation string + value string + server func() *httptest.Server + wantErr bool + errContains string + }{ + // ... keep existing valid test cases ... + + { + name: "invalid URL", + operation: "get", + value: "not-a-url", + wantErr: true, + errContains: "unsupported protocol", // Updated to match actual error + }, + { + name: "malformed URL", + operation: "get", + value: "http://[::1]:namedport", + wantErr: true, + errContains: "error creating request", + }, + // ... keep other test cases ... + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + var url string + if tt.server != nil { + server := tt.server() + defer server.Close() + url = server.URL + } else { + url = tt.value + } + + got, err := plugin.Apply(tt.operation, url) + + // Check error cases + if (err != nil) != tt.wantErr { + t.Errorf("FetchPlugin.Apply() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if err != nil && tt.errContains != "" { + if !strings.Contains(err.Error(), tt.errContains) { + t.Errorf("error %q should contain %q", err.Error(), tt.errContains) + t.Logf("Full error: %v", err) // Added for better debugging + } + return + } + + // For successful cases, verify we got some content + if err == nil && got == "" { + t.Error("FetchPlugin.Apply() returned empty content on success") + } + }) + } +} \ No newline at end of file diff --git a/plugins/template/file.go b/plugins/template/file.go new file mode 100644 index 00000000..fb2f85d7 --- /dev/null +++ b/plugins/template/file.go @@ -0,0 +1,197 @@ +// Package template provides file system operations for the template system. +// Security Note: This plugin provides access to the local filesystem. +// Consider carefully which paths to allow access to in production. +package template + +import ( + "bufio" + "fmt" + "os" + "path/filepath" + "strconv" + "strings" + "time" +) + +// MaxFileSize defines the maximum file size that can be read (1MB) +const MaxFileSize = 1 * 1024 * 1024 + +// FilePlugin provides filesystem operations with safety constraints: +// - No directory traversal +// - Size limits +// - Path sanitization +type FilePlugin struct{} + +// safePath validates and normalizes file paths +func (p *FilePlugin) safePath(path string) (string, error) { + debugf("File: validating path %q", path) + + // Basic security check - no path traversal + if strings.Contains(path, "..") { + return "", fmt.Errorf("file: path cannot contain '..'") + } + + // Expand home directory if needed + if strings.HasPrefix(path, "~/") { + home, err := os.UserHomeDir() + if err != nil { + return "", fmt.Errorf("file: could not expand home directory: %v", err) + } + path = filepath.Join(home, path[2:]) + } + + // Clean the path + cleaned := filepath.Clean(path) + debugf("File: cleaned path %q", cleaned) + return cleaned, nil +} + +// Apply executes file operations: +// - read:PATH - Read entire file content +// - tail:PATH|N - Read last N lines +// - exists:PATH - Check if file exists +// - size:PATH - Get file size in bytes +// - modified:PATH - Get last modified time +func (p *FilePlugin) Apply(operation string, value string) (string, error) { + debugf("File: operation=%q value=%q", operation, value) + + switch operation { + case "tail": + parts := strings.Split(value, "|") + if len(parts) != 2 { + return "", fmt.Errorf("file: tail requires format path|lines") + } + + path, err := p.safePath(parts[0]) + if err != nil { + return "", err + } + + n, err := strconv.Atoi(parts[1]) + if err != nil { + return "", fmt.Errorf("file: invalid line count %q", parts[1]) + } + + if n < 1 { + return "", fmt.Errorf("file: line count must be positive") + } + + lines, err := p.lastNLines(path, n) + if err != nil { + return "", err + } + + result := strings.Join(lines, "\n") + debugf("File: tail returning %d lines", len(lines)) + return result, nil + + case "read": + path, err := p.safePath(value) + if err != nil { + return "", err + } + + info, err := os.Stat(path) + if err != nil { + return "", fmt.Errorf("file: could not stat file: %v", err) + } + + if info.Size() > MaxFileSize { + return "", fmt.Errorf("file: size %d exceeds limit of %d bytes", + info.Size(), MaxFileSize) + } + + content, err := os.ReadFile(path) + if err != nil { + return "", fmt.Errorf("file: could not read: %v", err) + } + + debugf("File: read %d bytes", len(content)) + return string(content), nil + + case "exists": + path, err := p.safePath(value) + if err != nil { + return "", err + } + + _, err = os.Stat(path) + exists := err == nil + debugf("File: exists=%v for path %q", exists, path) + return fmt.Sprintf("%t", exists), nil + + case "size": + path, err := p.safePath(value) + if err != nil { + return "", err + } + + info, err := os.Stat(path) + if err != nil { + return "", fmt.Errorf("file: could not stat file: %v", err) + } + + size := info.Size() + debugf("File: size=%d for path %q", size, path) + return fmt.Sprintf("%d", size), nil + + case "modified": + path, err := p.safePath(value) + if err != nil { + return "", err + } + + info, err := os.Stat(path) + if err != nil { + return "", fmt.Errorf("file: could not stat file: %v", err) + } + + mtime := info.ModTime().Format(time.RFC3339) + debugf("File: modified=%q for path %q", mtime, path) + return mtime, nil + + default: + return "", fmt.Errorf("file: unknown operation %q (supported: read, tail, exists, size, modified)", + operation) + } +} + +// lastNLines returns the last n lines from a file +func (p *FilePlugin) lastNLines(path string, n int) ([]string, error) { + debugf("File: reading last %d lines from %q", n, path) + + file, err := os.Open(path) + if err != nil { + return nil, fmt.Errorf("file: could not open: %v", err) + } + defer file.Close() + + info, err := file.Stat() + if err != nil { + return nil, fmt.Errorf("file: could not stat: %v", err) + } + + if info.Size() > MaxFileSize { + return nil, fmt.Errorf("file: size %d exceeds limit of %d bytes", + info.Size(), MaxFileSize) + } + + lines := make([]string, 0, n) + scanner := bufio.NewScanner(file) + + lineCount := 0 + for scanner.Scan() { + lineCount++ + if len(lines) == n { + lines = lines[1:] + } + lines = append(lines, scanner.Text()) + } + + if err := scanner.Err(); err != nil { + return nil, fmt.Errorf("file: error reading: %v", err) + } + + debugf("File: read %d lines total, returning last %d", lineCount, len(lines)) + return lines, nil +} diff --git a/plugins/template/file.md b/plugins/template/file.md new file mode 100644 index 00000000..93224ae4 --- /dev/null +++ b/plugins/template/file.md @@ -0,0 +1,51 @@ +# File Plugin Tests + +Simple test file for validating file plugin functionality. + +## Basic File Operations + +``` +Read File: +{{plugin:file:read:/path/to/file.txt}} + +Last 5 Lines: +{{plugin:file:tail:/path/to/log.txt|5}} + +Check Existence: +{{plugin:file:exists:/path/to/file.txt}} + +Get Size: +{{plugin:file:size:/path/to/file.txt}} + +Last Modified: +{{plugin:file:modified:/path/to/file.txt}} +``` + +## Error Cases +These should produce appropriate error messages: + +``` +Invalid Operation: +{{plugin:file:invalid:/path/to/file.txt}} + +Non-existent File: +{{plugin:file:read:/path/to/nonexistent.txt}} + +Path Traversal Attempt: +{{plugin:file:read:../../../etc/passwd}} + +Invalid Tail Format: +{{plugin:file:tail:/path/to/file.txt}} + +Large File: +{{plugin:file:read:/path/to/huge.iso}} +``` + +## Security Considerations + +- Carefully control which paths are accessible +- Consider using path allow lists in production +- Be aware of file size limits (1MB max) +- No directory traversal is allowed +- Home directory (~/) expansion is supported +- All paths are cleaned and normalized \ No newline at end of file diff --git a/plugins/template/file_test.go b/plugins/template/file_test.go new file mode 100644 index 00000000..f4c24736 --- /dev/null +++ b/plugins/template/file_test.go @@ -0,0 +1,152 @@ +package template + +import ( + "os" + "path/filepath" + "strings" + "testing" + "time" +) + +func TestFilePlugin(t *testing.T) { + plugin := &FilePlugin{} + + // Create temp test files + tmpDir := t.TempDir() + + testFile := filepath.Join(tmpDir, "test.txt") + content := "line1\nline2\nline3\nline4\nline5\n" + err := os.WriteFile(testFile, []byte(content), 0644) + if err != nil { + t.Fatal(err) + } + + bigFile := filepath.Join(tmpDir, "big.txt") + err = os.WriteFile(bigFile, []byte(strings.Repeat("x", MaxFileSize+1)), 0644) + if err != nil { + t.Fatal(err) + } + + tests := []struct { + name string + operation string + value string + want string + wantErr bool + errContains string + validate func(string) bool + }{ + { + name: "read file", + operation: "read", + value: testFile, + want: content, + }, + { + name: "tail file", + operation: "tail", + value: testFile + "|3", + want: "line3\nline4\nline5", + }, + { + name: "exists true", + operation: "exists", + value: testFile, + want: "true", + }, + { + name: "exists false", + operation: "exists", + value: filepath.Join(tmpDir, "nonexistent.txt"), + want: "false", + }, + { + name: "size", + operation: "size", + value: testFile, + want: "30", + }, + { + name: "modified", + operation: "modified", + value: testFile, + validate: func(got string) bool { + _, err := time.Parse(time.RFC3339, got) + return err == nil + }, + }, + // Error cases + { + name: "read non-existent", + operation: "read", + value: filepath.Join(tmpDir, "nonexistent.txt"), + wantErr: true, + errContains: "could not stat file", + }, + { + name: "invalid operation", + operation: "invalid", + value: testFile, + wantErr: true, + errContains: "unknown operation", + }, + { + name: "path traversal attempt", + operation: "read", + value: "../../../etc/passwd", + wantErr: true, + errContains: "cannot contain '..'", + }, + { + name: "file too large", + operation: "read", + value: bigFile, + wantErr: true, + errContains: "exceeds limit", + }, + { + name: "invalid tail format", + operation: "tail", + value: testFile, + wantErr: true, + errContains: "requires format path|lines", + }, + { + name: "invalid tail count", + operation: "tail", + value: testFile + "|invalid", + wantErr: true, + errContains: "invalid line count", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := plugin.Apply(tt.operation, tt.value) + + // Check error cases + if (err != nil) != tt.wantErr { + t.Errorf("FilePlugin.Apply() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if err != nil && tt.errContains != "" { + if !strings.Contains(err.Error(), tt.errContains) { + t.Errorf("error %q should contain %q", err.Error(), tt.errContains) + } + return + } + + // Check success cases + if err == nil { + if tt.validate != nil { + if !tt.validate(got) { + t.Errorf("FilePlugin.Apply() returned invalid result: %q", got) + } + } else if tt.want != "" && got != tt.want { + t.Errorf("FilePlugin.Apply() = %v, want %v", got, tt.want) + } + } + }) + } +} \ No newline at end of file diff --git a/plugins/template/sys.go b/plugins/template/sys.go new file mode 100644 index 00000000..64059099 --- /dev/null +++ b/plugins/template/sys.go @@ -0,0 +1,87 @@ +// Package template provides system information operations for the template system. +package template + +import ( + "fmt" + "os" + "os/user" + "runtime" +) + +// SysPlugin provides access to system-level information. +// Security Note: This plugin provides access to system information and +// environment variables. Be cautious with exposed variables in templates. +type SysPlugin struct{} + +// Apply executes system operations with the following options: +// - hostname: System hostname +// - user: Current username +// - os: Operating system (linux, darwin, windows) +// - arch: System architecture (amd64, arm64, etc) +// - env:VALUE: Environment variable lookup +// - pwd: Current working directory +// - home: User's home directory +func (p *SysPlugin) Apply(operation string, value string) (string, error) { + debugf("Sys: operation=%q value=%q", operation, value) + + switch operation { + case "hostname": + hostname, err := os.Hostname() + if err != nil { + debugf("Sys: hostname error: %v", err) + return "", fmt.Errorf("sys: hostname error: %v", err) + } + debugf("Sys: hostname=%q", hostname) + return hostname, nil + + case "user": + currentUser, err := user.Current() + if err != nil { + debugf("Sys: user error: %v", err) + return "", fmt.Errorf("sys: user error: %v", err) + } + debugf("Sys: user=%q", currentUser.Username) + return currentUser.Username, nil + + case "os": + result := runtime.GOOS + debugf("Sys: os=%q", result) + return result, nil + + case "arch": + result := runtime.GOARCH + debugf("Sys: arch=%q", result) + return result, nil + + case "env": + if value == "" { + debugf("Sys: env error: missing variable name") + return "", fmt.Errorf("sys: env operation requires a variable name") + } + result := os.Getenv(value) + debugf("Sys: env %q=%q", value, result) + return result, nil + + case "pwd": + dir, err := os.Getwd() + if err != nil { + debugf("Sys: pwd error: %v", err) + return "", fmt.Errorf("sys: pwd error: %v", err) + } + debugf("Sys: pwd=%q", dir) + return dir, nil + + case "home": + homeDir, err := os.UserHomeDir() + if err != nil { + debugf("Sys: home error: %v", err) + return "", fmt.Errorf("sys: home error: %v", err) + } + debugf("Sys: home=%q", homeDir) + return homeDir, nil + + default: + debugf("Sys: unknown operation %q", operation) + return "", fmt.Errorf("sys: unknown operation %q (supported: hostname, user, os, arch, env, pwd, home)", operation) + } +} \ No newline at end of file diff --git a/plugins/template/sys.md b/plugins/template/sys.md new file mode 100644 index 00000000..fbd6b8dc --- /dev/null +++ b/plugins/template/sys.md @@ -0,0 +1,43 @@ +# System Plugin Tests + +Simple test file for validating system plugin functionality. + +## Basic System Information + +``` +Hostname: {{plugin:sys:hostname}} +Username: {{plugin:sys:user}} +Operating System: {{plugin:sys:os}} +Architecture: {{plugin:sys:arch}} +``` + +## Paths and Directories + +``` +Current Directory: {{plugin:sys:pwd}} +Home Directory: {{plugin:sys:home}} +``` + +## Environment Variables + +``` +Path: {{plugin:sys:env:PATH}} +Home: {{plugin:sys:env:HOME}} +Shell: {{plugin:sys:env:SHELL}} +``` + +## Error Cases +These should produce appropriate error messages: + +``` +Invalid Operation: {{plugin:sys:invalid}} +Missing Env Var: {{plugin:sys:env:}} +Non-existent Env Var: {{plugin:sys:env:NONEXISTENT_VAR_123456}} +``` + +## Security Note + +Be careful when exposing system information in templates, especially: +- Environment variables that might contain sensitive data +- Full paths that reveal system structure +- Username/hostname information in public templates \ No newline at end of file diff --git a/plugins/template/sys_test.go b/plugins/template/sys_test.go new file mode 100644 index 00000000..31544f9f --- /dev/null +++ b/plugins/template/sys_test.go @@ -0,0 +1,140 @@ +package template + +import ( + "fmt" + "os" + "path/filepath" + "runtime" + "strings" + "testing" +) + +func TestSysPlugin(t *testing.T) { + plugin := &SysPlugin{} + + // Set up test environment variable + const testEnvVar = "FABRIC_TEST_VAR" + const testEnvValue = "test_value" + os.Setenv(testEnvVar, testEnvValue) + defer os.Unsetenv(testEnvVar) + + tests := []struct { + name string + operation string + value string + validate func(string) error + wantErr bool + }{ + { + name: "hostname returns valid name", + operation: "hostname", + validate: func(got string) error { + if got == "" { + return fmt.Errorf("hostname is empty") + } + return nil + }, + }, + { + name: "user returns current user", + operation: "user", + validate: func(got string) error { + if got == "" { + return fmt.Errorf("username is empty") + } + return nil + }, + }, + { + name: "os returns valid OS", + operation: "os", + validate: func(got string) error { + if got != runtime.GOOS { + return fmt.Errorf("expected OS %s, got %s", runtime.GOOS, got) + } + return nil + }, + }, + { + name: "arch returns valid architecture", + operation: "arch", + validate: func(got string) error { + if got != runtime.GOARCH { + return fmt.Errorf("expected arch %s, got %s", runtime.GOARCH, got) + } + return nil + }, + }, + { + name: "env returns environment variable", + operation: "env", + value: testEnvVar, + validate: func(got string) error { + if got != testEnvValue { + return fmt.Errorf("expected env var %s, got %s", testEnvValue, got) + } + return nil + }, + }, + { + name: "pwd returns valid directory", + operation: "pwd", + validate: func(got string) error { + if !filepath.IsAbs(got) { + return fmt.Errorf("expected absolute path, got %s", got) + } + return nil + }, + }, + { + name: "home returns valid home directory", + operation: "home", + validate: func(got string) error { + if !filepath.IsAbs(got) { + return fmt.Errorf("expected absolute path, got %s", got) + } + if !strings.Contains(got, "home") && !strings.Contains(got, "Users") { + return fmt.Errorf("path %s doesn't look like a home directory", got) + } + return nil + }, + }, + // Error cases + { + name: "unknown operation", + operation: "invalid", + wantErr: true, + }, + { + name: "env without variable", + operation: "env", + wantErr: true, + }, + { + name: "env with non-existent variable", + operation: "env", + value: "NONEXISTENT_VAR_123456", + validate: func(got string) error { + if got != "" { + return fmt.Errorf("expected empty string for non-existent env var, got %s", got) + } + return nil + }, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := plugin.Apply(tt.operation, tt.value) + if (err != nil) != tt.wantErr { + t.Errorf("SysPlugin.Apply() error = %v, wantErr %v", err, tt.wantErr) + return + } + if err == nil && tt.validate != nil { + if err := tt.validate(got); err != nil { + t.Errorf("SysPlugin.Apply() validation failed: %v", err) + } + } + }) + } +} \ No newline at end of file diff --git a/plugins/template/template.go b/plugins/template/template.go new file mode 100644 index 00000000..6ff7cd4e --- /dev/null +++ b/plugins/template/template.go @@ -0,0 +1,119 @@ +package template + +import ( + "fmt" + "regexp" + "strings" +) + +var ( + textPlugin = &TextPlugin{} + datetimePlugin = &DateTimePlugin{} + filePlugin = &FilePlugin{} + fetchPlugin = &FetchPlugin{} + sysPlugin = &SysPlugin{} + Debug = false // Debug flag +) + +var pluginPattern = regexp.MustCompile(`\{\{plugin:([^:]+):([^:]+)(?::([^}]+))?\}\}`) + +func debugf(format string, a ...interface{}) { + if Debug { + fmt.Printf(format, a...) + } +} + +func ApplyTemplate(content string, variables map[string]string, input string) (string, error) { + var missingVars []string + r := regexp.MustCompile(`\{\{([^{}]+)\}\}`) + + debugf("Starting template processing\n") + for strings.Contains(content, "{{") { + matches := r.FindAllStringSubmatch(content, -1) + if len(matches) == 0 { + break + } + + replaced := false + for _, match := range matches { + fullMatch := match[0] + varName := match[1] + + // Check if this is a plugin call + if strings.HasPrefix(varName, "plugin:") { + pluginMatches := pluginPattern.FindStringSubmatch(fullMatch) + if len(pluginMatches) >= 3 { + namespace := pluginMatches[1] + operation := pluginMatches[2] + value := "" + if len(pluginMatches) == 4 { + value = pluginMatches[3] + } + + debugf("\nPlugin call:\n") + debugf(" Namespace: %s\n", namespace) + debugf(" Operation: %s\n", operation) + debugf(" Value: %s\n", value) + + var result string + var err error + + switch namespace { + case "text": + debugf("Executing text plugin\n") + result, err = textPlugin.Apply(operation, value) + case "datetime": + debugf("Executing datetime plugin\n") + result, err = datetimePlugin.Apply(operation, value) + case "file": + debugf("Executing file plugin\n") + result, err = filePlugin.Apply(operation, value) + debugf("File plugin result: %#v\n", result) + case "fetch": + debugf("Executing fetch plugin\n") + result, err = fetchPlugin.Apply(operation, value) + case "sys": + debugf("Executing sys plugin\n") + result, err = sysPlugin.Apply(operation, value) + default: + return "", fmt.Errorf("unknown plugin namespace: %s", namespace) + } + + if err != nil { + debugf("Plugin error: %v\n", err) + return "", fmt.Errorf("plugin %s error: %v", namespace, err) + } + + debugf("Plugin result: %s\n", result) + content = strings.ReplaceAll(content, fullMatch, result) + debugf("Content after replacement: %s\n", content) + continue + } + } + + // Handle regular variables and input + debugf("Processing variable: %s\n", varName) + if varName == "input" { + debugf("Replacing {{input}}\n") + replaced = true + content = strings.ReplaceAll(content, fullMatch, input) + } else { + if val, ok := variables[varName]; !ok { + debugf("Missing variable: %s\n", varName) + missingVars = append(missingVars, varName) + return "", fmt.Errorf("missing required variable: %s", varName) + } else { + debugf("Replacing variable %s with value: %s\n", varName, val) + content = strings.ReplaceAll(content, fullMatch, val) + replaced = true + } + } + if !replaced { + return "", fmt.Errorf("template processing stuck - potential infinite loop") + } + } + } + + debugf("Template processing complete\n") + return content, nil +} \ No newline at end of file diff --git a/plugins/template/template_test.go b/plugins/template/template_test.go new file mode 100644 index 00000000..d3617b6f --- /dev/null +++ b/plugins/template/template_test.go @@ -0,0 +1,146 @@ +package template + +import ( + "strings" + "testing" +) + +func TestApplyTemplate(t *testing.T) { + tests := []struct { + name string + template string + vars map[string]string + input string + want string + wantErr bool + errContains string + }{ + // Basic variable substitution + { + name: "simple variable", + template: "Hello {{name}}!", + vars: map[string]string{"name": "World"}, + want: "Hello World!", + }, + { + name: "multiple variables", + template: "{{greeting}} {{name}}!", + vars: map[string]string{ + "greeting": "Hello", + "name": "World", + }, + want: "Hello World!", + }, + { + name: "special input variable", + template: "Content: {{input}}", + input: "test content", + want: "Content: test content", + }, + + // Nested variable substitution + { + name: "nested variables", + template: "{{outer{{inner}}}}", + vars: map[string]string{ + "inner": "foo", // First resolution + "outerfoo": "result", // Second resolution + }, + want: "result", + }, + + // Plugin operations + { + name: "simple text plugin", + template: "{{plugin:text:upper:hello}}", + want: "HELLO", + }, + { + name: "text plugin with variable", + template: "{{plugin:text:upper:{{name}}}}", + vars: map[string]string{"name": "world"}, + want: "WORLD", + }, + { + name: "plugin with dynamic operation", + template: "{{plugin:text:{{operation}}:hello}}", + vars: map[string]string{"operation": "upper"}, + want: "HELLO", + }, + + // Multiple operations + { + name: "multiple plugins", + template: "A:{{plugin:text:upper:hello}} B:{{plugin:text:lower:WORLD}}", + want: "A:HELLO B:world", + }, + { + name: "nested plugins", + template: "{{plugin:text:upper:{{plugin:text:lower:HELLO}}}}", + want: "HELLO", + }, + + // Error cases + { + name: "missing variable", + template: "Hello {{name}}!", + wantErr: true, + errContains: "missing required variable", + }, + { + name: "unknown plugin", + template: "{{plugin:invalid:op:value}}", + wantErr: true, + errContains: "unknown plugin namespace", + }, + { + name: "unknown plugin operation", + template: "{{plugin:text:invalid:value}}", + wantErr: true, + errContains: "unknown text operation", + }, + { + name: "nested plugin error", + template: "{{plugin:text:upper:{{plugin:invalid:op:value}}}}", + wantErr: true, + errContains: "unknown plugin namespace", + }, + + // Edge cases + { + name: "empty template", + template: "", + want: "", + }, + { + name: "no substitutions needed", + template: "plain text", + want: "plain text", + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := ApplyTemplate(tt.template, tt.vars, tt.input) + + // Check error cases + if (err != nil) != tt.wantErr { + t.Errorf("ApplyTemplate() error = %v, wantErr %v", err, tt.wantErr) + return + } + + if err != nil && tt.errContains != "" { + if !strings.Contains(err.Error(), tt.errContains) { + t.Errorf("error %q should contain %q", err.Error(), tt.errContains) + } + return + } + + // Check result + if got != tt.want { + t.Errorf("ApplyTemplate() = %q, want %q", got, tt.want) + } + }) + } +} + diff --git a/plugins/template/text.go b/plugins/template/text.go new file mode 100644 index 00000000..e3d3f76e --- /dev/null +++ b/plugins/template/text.go @@ -0,0 +1,64 @@ +// Package template provides text transformation operations for the template system. +package template + +import ( + "fmt" + "strings" + "unicode" +) + +// TextPlugin provides string manipulation operations +type TextPlugin struct{} + +// toTitle capitalizes a letter if it follows a non-letter, unless next char is space +func toTitle(s string) string { + // First lowercase everything + lower := strings.ToLower(s) + runes := []rune(lower) + + for i := 0; i < len(runes); i++ { + // Capitalize if previous char is non-letter AND + // (we're at the end OR next char is not space) + if (i == 0 || !unicode.IsLetter(runes[i-1])) { + if i == len(runes)-1 || !unicode.IsSpace(runes[i+1]) { + runes[i] = unicode.ToUpper(runes[i]) + } + } + } + + return string(runes) +} + +// Apply executes the requested text operation on the provided value +func (p *TextPlugin) Apply(operation string, value string) (string, error) { + debugf("TextPlugin: operation=%s value=%q", operation, value) + + if value == "" { + return "", fmt.Errorf("text: empty input for operation %q", operation) + } + + switch operation { + case "upper": + result := strings.ToUpper(value) + debugf("TextPlugin: upper result=%q", result) + return result, nil + + case "lower": + result := strings.ToLower(value) + debugf("TextPlugin: lower result=%q", result) + return result, nil + + case "title": + result := toTitle(value) + debugf("TextPlugin: title result=%q", result) + return result, nil + + case "trim": + result := strings.TrimSpace(value) + debugf("TextPlugin: trim result=%q", result) + return result, nil + + default: + return "", fmt.Errorf("text: unknown text operation %q (supported: upper, lower, title, trim)", operation) + } +} \ No newline at end of file diff --git a/plugins/template/text.md b/plugins/template/text.md new file mode 100644 index 00000000..e69de29b diff --git a/plugins/template/text_test.go b/plugins/template/text_test.go new file mode 100644 index 00000000..4a85f838 --- /dev/null +++ b/plugins/template/text_test.go @@ -0,0 +1,104 @@ +package template + +import ( + "testing" +) + +func TestTextPlugin(t *testing.T) { + plugin := &TextPlugin{} + + tests := []struct { + name string + operation string + value string + want string + wantErr bool + }{ + // Upper tests + { + name: "upper basic", + operation: "upper", + value: "hello", + want: "HELLO", + }, + { + name: "upper mixed case", + operation: "upper", + value: "hElLo", + want: "HELLO", + }, + + // Lower tests + { + name: "lower basic", + operation: "lower", + value: "HELLO", + want: "hello", + }, + { + name: "lower mixed case", + operation: "lower", + value: "hElLo", + want: "hello", + }, + + // Title tests + { + name: "title basic", + operation: "title", + value: "hello world", + want: "Hello World", + }, + { + name: "title with apostrophe", + operation: "title", + value: "o'reilly's book", + want: "O'Reilly's Book", + }, + + // Trim tests + { + name: "trim spaces", + operation: "trim", + value: " hello ", + want: "hello", + }, + { + name: "trim newlines", + operation: "trim", + value: "\nhello\n", + want: "hello", + }, + + // Error cases + { + name: "empty value", + operation: "upper", + value: "", + wantErr: true, + }, + { + name: "unknown operation", + operation: "invalid", + value: "test", + wantErr: true, + }, + } + + for _, tt := range tests { + t.Run(tt.name, func(t *testing.T) { + got, err := plugin.Apply(tt.operation, tt.value) + + // Check error cases + if (err != nil) != tt.wantErr { + t.Errorf("TextPlugin.Apply() error = %v, wantErr %v", err, tt.wantErr) + return + } + + // Check successful cases + if err == nil && got != tt.want { + t.Errorf("TextPlugin.Apply() = %q, want %q", got, tt.want) + } + }) + } +} \ No newline at end of file