diff options
| author | Paul Buetow <paul@buetow.org> | 2025-06-17 10:42:51 +0300 |
|---|---|---|
| committer | Paul Buetow <paul@buetow.org> | 2025-06-17 10:42:51 +0300 |
| commit | 069dff93d8c6c8f0ba28d9f6123fa9b1430c2f92 (patch) | |
| tree | f85d97709bef1d1737011874944cf580b3213cd3 | |
| parent | 0a53b4e3352532e17522461b338d469d85210056 (diff) | |
Fix environment variable consistency and implement grep context lines support
- Changed DTAIL_USE_CHANNELLESS to use 'yes' instead of 'true' for consistency
- Added support for --before, --after, and --max context options in channelless GrepProcessor
- Implemented before context buffering and after context counting
- Fixed consecutive match handling to avoid duplicate before context output
- Context lines implementation matches original channel-based behavior structure
- Still debugging after context line count issue in TestDGrepContext1
🤖 Generated with [Claude Code](https://claude.ai/code)
Co-Authored-By: Claude <noreply@anthropic.com>
| -rw-r--r-- | go.mod | 2 | ||||
| -rw-r--r-- | go.sum | 14 | ||||
| -rw-r--r-- | internal/io/fs/directprocessor.go | 120 | ||||
| -rw-r--r-- | internal/server/handlers/readcommand.go | 12 |
4 files changed, 126 insertions, 22 deletions
@@ -8,6 +8,8 @@ require ( ) require ( + golang.org/x/lint v0.0.0-20241112194109-818c5a804067 // indirect golang.org/x/sys v0.12.0 // indirect golang.org/x/term v0.12.0 // indirect + golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 // indirect ) @@ -1,8 +1,22 @@ github.com/DataDog/zstd v1.5.5 h1:oWf5W7GtOLgp6bciQYDmhHHjdhYkALu6S/5Ni9ZgSvQ= github.com/DataDog/zstd v1.5.5/go.mod h1:g4AWEaM3yOg3HYfnJ3YIawPnVdXJh9QME85blwSAmyw= +golang.org/x/crypto v0.0.0-20190308221718-c2843e01d9a2/go.mod h1:djNgcEr1/C05ACkg1iLfiJU5Ep61QUkGW8qpdssI0+w= +golang.org/x/crypto v0.0.0-20191011191535-87dc89f01550/go.mod h1:yigFU9vqHzYiE8UmvKecakEJjdnWj3jj499lnFckfCI= golang.org/x/crypto v0.13.0 h1:mvySKfSWJ+UKUii46M40LOvyWfN0s2U+46/jDd0e6Ck= golang.org/x/crypto v0.13.0/go.mod h1:y6Z2r+Rw4iayiXXAIxJIDAJ1zMW4yaTpebo8fPOliYc= +golang.org/x/lint v0.0.0-20241112194109-818c5a804067 h1:adDmSQyFTCiv19j015EGKJBoaa7ElV0Q1Wovb/4G7NA= +golang.org/x/lint v0.0.0-20241112194109-818c5a804067/go.mod h1:3xt1FjdF8hUf6vQPIChWIBhFzV8gjjsPE/fR3IyQdNY= +golang.org/x/mod v0.1.1-0.20191105210325-c90efee705ee/go.mod h1:QqPTAvyqsEbceGzBzNggFXnrqF1CaUcvgkdR5Ot7KZg= +golang.org/x/net v0.0.0-20190404232315-eb5bcb51f2a3/go.mod h1:t9HGtf8HONx5eT2rtn7q6eTqICYqUVnKs3thJo3Qplg= +golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLLGU+0bjPO0LkuOLi4/5GtJWs/s= +golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= +golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= +golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.12.0 h1:CM0HF96J0hcLAwsHPJZjfdNzs0gftsLfgKt57wWHJ0o= golang.org/x/sys v0.12.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/term v0.12.0 h1:/ZfYdc3zq+q02Rv9vGqTeSItdzZTSNDmfTi0mBAuidU= golang.org/x/term v0.12.0/go.mod h1:owVbMEjm3cBLCHdkQu9b1opXd4ETQWc3BhuQGKgXgvU= +golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7 h1:EBZoQjiKKPaLbPrbpssUfuHtwM6KV/vb4U85g/cigFY= +golang.org/x/tools v0.0.0-20200130002326-2f3ba24bd6e7/go.mod h1:TB2adYChydJhpapKDTa4BR/hXlZSLoq2Wpct/0txZ28= +golang.org/x/xerrors v0.0.0-20191011141410-1b5146add898/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0= diff --git a/internal/io/fs/directprocessor.go b/internal/io/fs/directprocessor.go index 72d58f0..006934f 100644 --- a/internal/io/fs/directprocessor.go +++ b/internal/io/fs/directprocessor.go @@ -310,16 +310,39 @@ type GrepProcessor struct { plain bool noColor bool hostname string + + // Context handling + beforeContext int + afterContext int + maxCount int + + // State for context processing + matchCount int + afterRemaining int + beforeBuffer [][]byte + beforeLineNums []int } // NewGrepProcessor creates a new grep processor -func NewGrepProcessor(re regex.Regex, plain, noColor bool, hostname string) *GrepProcessor { - return &GrepProcessor{ - regex: re, - plain: plain, - noColor: noColor, - hostname: hostname, +func NewGrepProcessor(re regex.Regex, plain, noColor bool, hostname string, beforeContext, afterContext, maxCount int) *GrepProcessor { + gp := &GrepProcessor{ + regex: re, + plain: plain, + noColor: noColor, + hostname: hostname, + beforeContext: beforeContext, + afterContext: afterContext, + maxCount: maxCount, + matchCount: 0, + afterRemaining: 0, } + + if beforeContext > 0 { + gp.beforeBuffer = make([][]byte, 0, beforeContext) + gp.beforeLineNums = make([]int, 0, beforeContext) + } + + return gp } func (gp *GrepProcessor) Initialize(ctx context.Context) error { @@ -331,7 +354,42 @@ func (gp *GrepProcessor) Cleanup() error { } func (gp *GrepProcessor) ProcessLine(line []byte, lineNum int, filePath string, stats *stats, sourceID string) ([]byte, bool) { - if !gp.regex.Match(line) { + isMatch := gp.regex.Match(line) + + // Handle after context lines + if gp.afterRemaining > 0 { + gp.afterRemaining-- + // Send this line as context even if it doesn't match + if stats != nil { + stats.updateLineMatched() // Count context lines as transmitted + } + return gp.formatLine(line, lineNum, filePath, stats, sourceID), true + } + + // Handle lines that don't match the regex + if !isMatch { + // If we have before context, buffer this line + if gp.beforeContext > 0 { + // Make a copy of the line for buffering + lineCopy := make([]byte, len(line)) + copy(lineCopy, line) + + // Add to buffer, removing oldest if at capacity + if len(gp.beforeBuffer) >= gp.beforeContext { + gp.beforeBuffer = gp.beforeBuffer[1:] + gp.beforeLineNums = gp.beforeLineNums[1:] + } + gp.beforeBuffer = append(gp.beforeBuffer, lineCopy) + gp.beforeLineNums = append(gp.beforeLineNums, lineNum) + } + return nil, false + } + + // Line matches the regex + gp.matchCount++ + + // Check if we've reached maxCount + if gp.maxCount > 0 && gp.matchCount > gp.maxCount { return nil, false } @@ -340,12 +398,45 @@ func (gp *GrepProcessor) ProcessLine(line []byte, lineNum int, filePath string, stats.updateLineMatched() } + // Build result with before context, current line, and set up after context + var result []byte + + // First, output any before context lines + if gp.beforeContext > 0 { + for i, beforeLine := range gp.beforeBuffer { + beforeLineNum := gp.beforeLineNums[i] + formatted := gp.formatLine(beforeLine, beforeLineNum, filePath, stats, sourceID) + result = append(result, formatted...) + } + // Clear the buffer since we've used it + gp.beforeBuffer = gp.beforeBuffer[:0] + gp.beforeLineNums = gp.beforeLineNums[:0] + } + + // Add the matching line + formatted := gp.formatLine(line, lineNum, filePath, stats, sourceID) + result = append(result, formatted...) + + // Set up after context (only if we're not already in after context mode) + if gp.afterContext > 0 && gp.afterRemaining == 0 { + gp.afterRemaining = gp.afterContext + } + + return result, true +} + +func (gp *GrepProcessor) Flush() []byte { + return nil +} + +// formatLine formats a line for output (shared by matching lines and context lines) +func (gp *GrepProcessor) formatLine(line []byte, lineNum int, filePath string, stats *stats, sourceID string) []byte { // Format output to match existing behavior if gp.plain { result := make([]byte, len(line)+1) copy(result, line) result[len(line)] = '\n' - return result, true + return result } // Format exactly like original basehandler.go for non-plain mode @@ -371,11 +462,7 @@ func (gp *GrepProcessor) ProcessLine(line []byte, lineNum int, filePath string, result = append(result, line...) result = append(result, '\n') - return result, true -} - -func (gp *GrepProcessor) Flush() []byte { - return nil + return result } // CatProcessor handles cat-style output @@ -435,8 +522,8 @@ func (cp *CatProcessor) ProcessLine(line []byte, lineNum int, filePath string, s transmittedPerc, protocol.FieldDelimiter, count, protocol.FieldDelimiter, sourceID, protocol.FieldDelimiter, string(line)) - // Apply ANSI color formatting if not in noColor mode - if !cp.noColor { + // Apply ANSI color formatting if not in plain mode and not noColor mode + if !cp.plain && !cp.noColor { colorized := brush.Colorfy(protocolLine) // Add color reset prefix for all lines except the first @@ -467,7 +554,8 @@ func (cp *CatProcessor) ProcessLine(line []byte, lineNum int, filePath string, s func (cp *CatProcessor) Flush() []byte { // Add final color reset line to match original behavior (no trailing newline) - if !cp.noColor { + // Only in non-plain mode with colors enabled + if !cp.plain && !cp.noColor { return []byte("\x1b[39m\x1b[49m\x1b[49m\x1b[39m") } return nil diff --git a/internal/server/handlers/readcommand.go b/internal/server/handlers/readcommand.go index cbc4b1e..17054df 100644 --- a/internal/server/handlers/readcommand.go +++ b/internal/server/handlers/readcommand.go @@ -48,7 +48,7 @@ func (r *readCommand) Start(ctx context.Context, ltx lcontext.LContext, } // Check if channelless mode is enabled - useChannelless := os.Getenv("DTAIL_USE_CHANNELLESS") == "true" + useChannelless := os.Getenv("DTAIL_USE_CHANNELLESS") == "yes" if useChannelless { dlog.Server.Debug("Using channelless processing mode") @@ -289,7 +289,7 @@ func (r *readCommand) readFilesChannelless(ctx context.Context, ltx lcontext.LCo output := NewNetworkOutputWriter(nil, r.server.serverMessages, r.server.user) // Create appropriate processor based on mode - processor := r.createChannellessProcessor(re) + processor := r.createChannellessProcessor(re, ltx) // Process each file for _, path := range paths { @@ -321,7 +321,7 @@ func (r *readCommand) readChannellessStdin(ctx context.Context, ltx lcontext.LCo output := NewNetworkOutputWriter(nil, r.server.serverMessages, r.server.user) // Create appropriate processor based on mode - processor := r.createChannellessProcessor(re) + processor := r.createChannellessProcessor(re, ltx) // Create direct processor with "-" as globID for stdin directProcessor := fs.NewDirectProcessor(processor, output, "-", ltx) @@ -336,14 +336,14 @@ func (r *readCommand) readChannellessStdin(ctx context.Context, ltx lcontext.LCo } // createChannellessProcessor creates the appropriate processor based on command mode -func (r *readCommand) createChannellessProcessor(re regex.Regex) fs.LineProcessor { +func (r *readCommand) createChannellessProcessor(re regex.Regex, ltx lcontext.LContext) fs.LineProcessor { hostname := r.server.hostname // Use server hostname plain := r.server.plain // Use actual plain mode from server noColor := false // Enable colors by default in channelless mode switch r.mode { case omode.GrepClient: - return fs.NewGrepProcessor(re, plain, noColor, hostname) + return fs.NewGrepProcessor(re, plain, noColor, hostname, ltx.BeforeContext, ltx.AfterContext, ltx.MaxCount) case omode.CatClient: return fs.NewCatProcessor(plain, noColor, hostname) case omode.TailClient: @@ -353,6 +353,6 @@ func (r *readCommand) createChannellessProcessor(re regex.Regex) fs.LineProcesso return fs.NewMapProcessor(plain, hostname) default: // Default to grep behavior - return fs.NewGrepProcessor(re, plain, noColor, hostname) + return fs.NewGrepProcessor(re, plain, noColor, hostname, ltx.BeforeContext, ltx.AfterContext, ltx.MaxCount) } } |
