Skip to content

Commit 58bbee0

Browse files
committed
refactor: Improve secret detection reporting
Changes secretsFound boolean to secretCount integer to provide more precise information about detected secrets. Now UI and CLI display exact count of secrets found instead of just their presence. This gives users better awareness of potential sensitive data in their code snippets. - Replace boolean flag with integer counter - Update warning/info messages with numeric counts - Fix file write path issues in generator - Improve error handling and message clarity
1 parent dee2f6c commit 58bbee0

6 files changed

Lines changed: 86 additions & 81 deletions

File tree

cmd/grab/main.go

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -163,16 +163,16 @@ func runNonInteractive(rootPath string, filterMgr *filesystem.FilterManager, out
163163

164164
gen.SelectedFiles = selectedFiles
165165

166-
outputFilePath, tokenCount, secretsFound, err := gen.Generate()
166+
outputFilePath, tokenCount, secretCount, err := gen.Generate()
167167
if err != nil {
168168
log.Fatalf("Error generating output: %v\n", err)
169169
}
170170

171171
fmt.Printf("✅ Generated %s (%d tokens)\n", outputFilePath, tokenCount)
172172

173-
if secretsFound && skipRedaction {
174-
fmt.Fprintf(os.Stderr, "⚠️ WARNING: Potential secrets detected in the output and redaction was skipped!\n")
175-
} else if secretsFound && !skipRedaction {
176-
fmt.Fprintf(os.Stderr, "ℹ️ INFO: Potential secrets detected and redacted in the output.\n")
173+
if secretCount > 0 && skipRedaction {
174+
fmt.Fprintf(os.Stderr, "⚠️ WARNING: %d secrets detected in the output and redaction was skipped!\n", secretCount)
175+
} else if secretCount > 0 && !skipRedaction {
176+
fmt.Fprintf(os.Stderr, "ℹ️ INFO: %d secrets detected and redacted in the output.\n", secretCount)
177177
}
178178
}

internal/generator/format.go

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,9 +12,8 @@ type FileData struct {
1212

1313
// TemplateData is injected into the templates
1414
type TemplateData struct {
15-
Structure string
16-
Files []FileData
17-
SecretsFound bool
15+
Structure string
16+
Files []FileData
1817
}
1918

2019
// Format defines the interface for different output formats

internal/generator/generator.go

Lines changed: 36 additions & 30 deletions
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,7 @@ type Generator struct {
2525
UseGitIgnore bool
2626
ShowHidden bool
2727
RedactSecrets bool
28-
secretsFound bool
28+
lastSecretCount int
2929
}
3030

3131
// NewGenerator constructs a generator with default settings
@@ -75,23 +75,23 @@ func (g *Generator) SetRedactionMode(redact bool) {
7575
}
7676

7777
// Generate creates an output file in the specified format
78-
func (g *Generator) Generate() (string, int, bool, error) {
78+
func (g *Generator) Generate() (string, int, int, error) {
7979
if len(g.SelectedFiles) == 0 {
80-
return "", 0, false, fmt.Errorf("no files selected, skipping generation")
80+
return "", 0, 0, fmt.Errorf("no files selected, skipping generation")
8181
}
8282

8383
if g.format == nil {
84-
return "", 0, false, fmt.Errorf("no format set, cannot generate output")
84+
return "", 0, 0, fmt.Errorf("no format set, cannot generate output")
8585
}
8686

8787
data, err := g.PrepareTemplateData()
8888
if err != nil {
89-
return "", 0, false, fmt.Errorf("failed to prepare template data: %w", err)
89+
return "", 0, g.lastSecretCount, fmt.Errorf("failed to prepare template data: %w", err)
9090
}
9191

9292
content, tokenCount, err := g.format.Render(data)
9393
if err != nil {
94-
return "", 0, data.SecretsFound, fmt.Errorf("failed to render %s: %w", g.format.Name(), err)
94+
return "", 0, g.lastSecretCount, fmt.Errorf("failed to render %s: %w", g.format.Name(), err)
9595
}
9696

9797
var outputPath string
@@ -100,12 +100,12 @@ func (g *Generator) Generate() (string, int, bool, error) {
100100
if g.UseTempFile {
101101
tmpFile, err := os.CreateTemp("", fmt.Sprintf("codegrab-*%s", g.format.Extension()))
102102
if err != nil {
103-
return "", 0, data.SecretsFound, fmt.Errorf("failed to create temporary file: %w", err)
103+
return "", 0, g.lastSecretCount, fmt.Errorf("failed to create temporary file: %w", err)
104104
}
105105
defer tmpFile.Close()
106106

107107
if _, err := tmpFile.Write([]byte(content)); err != nil {
108-
return tmpFile.Name(), tokenCount, data.SecretsFound, fmt.Errorf("failed to write to temporary file: %w", err)
108+
return tmpFile.Name(), tokenCount, g.lastSecretCount, fmt.Errorf("failed to write to temporary file: %w", err)
109109
}
110110
outputPath = tmpFile.Name()
111111
displayPath = outputPath
@@ -122,46 +122,46 @@ func (g *Generator) Generate() (string, int, bool, error) {
122122
displayPath = outputPath
123123
absPath, err := filepath.Abs(outputPath)
124124
if err != nil {
125-
return "", tokenCount, data.SecretsFound, fmt.Errorf("failed to get absolute path: %w", err)
125+
return displayPath, tokenCount, g.lastSecretCount, fmt.Errorf("failed to get absolute path: %w", err)
126126
}
127127

128-
if err := os.WriteFile(outputPath, []byte(content), 0644); err != nil {
129-
return "", tokenCount, data.SecretsFound, fmt.Errorf("failed to write to output file: %w", err)
128+
if err := os.WriteFile(absPath, []byte(content), 0644); err != nil {
129+
return displayPath, tokenCount, g.lastSecretCount, fmt.Errorf("failed to write to output file %s: %w", absPath, err)
130130
}
131-
132-
outputPath = absPath
133131
}
134132

135133
if err := utils.CopyFileObject(outputPath); err != nil {
136134
fmt.Fprintf(os.Stderr, "Warning: clipboard copy failed: %v\n", err)
137135
}
138136

139-
return displayPath, tokenCount, data.SecretsFound, nil
137+
return displayPath, tokenCount, g.lastSecretCount, nil
140138
}
141139

142-
// GenerateString returns the rendered content as a string along with its estimated token count
143-
func (g *Generator) GenerateString() (string, int, bool, error) {
140+
// GenerateString returns the rendered content as a string along with counts
141+
func (g *Generator) GenerateString() (string, int, int, error) {
144142
if len(g.SelectedFiles) == 0 {
145-
return "", 0, false, fmt.Errorf("no files selected, skipping generation")
143+
return "", 0, 0, fmt.Errorf("no files selected, skipping generation")
146144
}
147145

148146
if g.format == nil {
149-
return "", 0, false, fmt.Errorf("no format set, cannot generate output")
147+
return "", 0, 0, fmt.Errorf("no format set, cannot generate output")
150148
}
151149

152150
data, err := g.PrepareTemplateData()
153151
if err != nil {
154-
return "", 0, false, fmt.Errorf("failed to prepare template data: %w", err)
152+
return "", 0, g.lastSecretCount, fmt.Errorf("failed to prepare template data: %w", err)
155153
}
156154

157155
content, tokenCount, err := g.format.Render(data)
158-
return content, tokenCount, data.SecretsFound, err
156+
return content, tokenCount, g.lastSecretCount, err
159157
}
160158

161-
// PrepareTemplateData finalizes the selection and builds TemplateData for the template
159+
// PrepareTemplateData finalizes the selection, scans/redacts secrets, and builds TemplateData
162160
func (g *Generator) PrepareTemplateData() (TemplateData, error) {
163-
g.secretsFound = false
161+
g.lastSecretCount = 0
162+
164163
expandedSelection := make(map[string]bool)
164+
165165
for path := range g.SelectedFiles {
166166
fullPath := filepath.Join(g.RootPath, path)
167167
info, err := os.Stat(fullPath)
@@ -210,6 +210,7 @@ func (g *Generator) PrepareTemplateData() (TemplateData, error) {
210210
expandedSelection[path] = true
211211
}
212212
}
213+
213214
g.SelectedFiles = expandedSelection
214215

215216
rootNode := g.buildTree()
@@ -223,18 +224,23 @@ func (g *Generator) PrepareTemplateData() (TemplateData, error) {
223224
var filesData []FileData
224225
collectFiles(rootNode, &filesData)
225226

226-
for i := range filesData {
227-
if len(filesData[i].Findings) > 0 {
228-
g.secretsFound = true
229-
if g.RedactSecrets && g.SecretScanner != nil {
230-
filesData[i].Content = g.SecretScanner.Redact(filesData[i].Content, filesData[i].Findings)
227+
secretCount := 0
228+
229+
if g.SecretScanner != nil {
230+
for i := range filesData {
231+
if len(filesData[i].Findings) > 0 {
232+
secretCount += len(filesData[i].Findings)
233+
if g.RedactSecrets {
234+
filesData[i].Content = g.SecretScanner.Redact(filesData[i].Content, filesData[i].Findings)
235+
}
231236
}
232237
}
233238
}
234239

240+
g.lastSecretCount = secretCount
241+
235242
return TemplateData{
236-
Structure: structureBuilder.String(),
237-
Files: filesData,
238-
SecretsFound: g.secretsFound,
243+
Structure: structureBuilder.String(),
244+
Files: filesData,
239245
}, nil
240246
}

internal/model/init_update.go

Lines changed: 41 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,18 @@ type filesLoadedMsg struct {
1616
}
1717

1818
type outputGeneratedMsg struct {
19-
err error
20-
path string
21-
format string
22-
tokenCount int
23-
secretsFound bool
19+
err error
20+
path string
21+
format string
22+
tokenCount int
23+
secretCount int
2424
}
2525

2626
type clipboardCopiedMsg struct {
27-
err error
28-
format string
29-
tokenCount int
30-
secretsFound bool
27+
err error
28+
format string
29+
tokenCount int
30+
secretCount int
3131
}
3232

3333
func (m Model) Init() tea.Cmd {
@@ -67,10 +67,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
6767
} else {
6868
m.err = nil
6969
m.successMsg = fmt.Sprintf("✅ Generated %s (%d tokens)", msg.path, msg.tokenCount)
70-
if msg.secretsFound && !m.redactSecrets {
71-
m.warningMsg = "⚠️ Secrets detected and NOT redacted"
72-
} else if msg.secretsFound && m.redactSecrets {
73-
m.warningMsg = "ℹ️ Secrets detected and redacted"
70+
if msg.secretCount > 0 && !m.redactSecrets {
71+
m.warningMsg = fmt.Sprintf("⚠️ %d secrets NOT redacted", msg.secretCount)
72+
} else if msg.secretCount > 0 && m.redactSecrets {
73+
m.warningMsg = fmt.Sprintf("ℹ️ %d secrets redacted", msg.secretCount)
7474
} else {
7575
m.warningMsg = ""
7676
}
@@ -87,10 +87,10 @@ func (m Model) Update(msg tea.Msg) (tea.Model, tea.Cmd) {
8787
} else {
8888
m.err = nil
8989
m.successMsg = fmt.Sprintf("✅ %s copied to clipboard! (%d tokens)", msg.format, msg.tokenCount)
90-
if msg.secretsFound && !m.redactSecrets {
91-
m.warningMsg = "⚠️ Secrets detected and NOT redacted"
92-
} else if msg.secretsFound && m.redactSecrets {
93-
m.warningMsg = "ℹ️ Secrets detected and redacted"
90+
if msg.secretCount > 0 && !m.redactSecrets {
91+
m.warningMsg = fmt.Sprintf("⚠️ %d secrets NOT redacted", msg.secretCount)
92+
} else if msg.secretCount > 0 && m.redactSecrets {
93+
m.warningMsg = fmt.Sprintf("ℹ️ %d secrets redacted", msg.secretCount)
9494
} else {
9595
m.warningMsg = ""
9696
}
@@ -327,22 +327,22 @@ func (m Model) generateOutput() tea.Cmd {
327327
return func() tea.Msg {
328328
m.generator.SelectedFiles = m.selected
329329
m.generator.DeselectedFiles = m.deselected
330-
outPath, tokenCount, secretsFound, err := m.generator.Generate()
330+
outPath, tokenCount, secretCount, err := m.generator.Generate()
331331
if err != nil {
332332
return outputGeneratedMsg{
333-
err: fmt.Errorf("failed to generate output: %w", err),
334-
path: "",
335-
tokenCount: 0,
336-
format: m.generator.GetFormatName(),
337-
secretsFound: secretsFound,
333+
err: fmt.Errorf("failed to generate output: %w", err),
334+
path: "",
335+
tokenCount: 0,
336+
format: m.generator.GetFormatName(),
337+
secretCount: secretCount,
338338
}
339339
}
340340
return outputGeneratedMsg{
341-
err: nil,
342-
path: outPath,
343-
tokenCount: tokenCount,
344-
format: m.generator.GetFormatName(),
345-
secretsFound: secretsFound,
341+
err: nil,
342+
path: outPath,
343+
tokenCount: tokenCount,
344+
format: m.generator.GetFormatName(),
345+
secretCount: secretCount,
346346
}
347347
}
348348
}
@@ -353,30 +353,30 @@ func (m Model) copyOutputToClipboard() tea.Cmd {
353353
m.generator.SelectedFiles = m.selected
354354
m.generator.DeselectedFiles = m.deselected
355355

356-
content, tokenCount, secretsFound, err := m.generator.GenerateString()
356+
content, tokenCount, secretCount, err := m.generator.GenerateString()
357357
if err != nil {
358358
return clipboardCopiedMsg{
359-
err: err,
360-
tokenCount: 0,
361-
format: m.generator.GetFormatName(),
362-
secretsFound: secretsFound,
359+
err: err,
360+
tokenCount: 0,
361+
format: m.generator.GetFormatName(),
362+
secretCount: secretCount,
363363
}
364364
}
365365

366366
if err = clipboard.WriteAll(content); err != nil {
367367
return clipboardCopiedMsg{
368-
err: fmt.Errorf("failed to copy to clipboard: %w", err),
369-
tokenCount: tokenCount,
370-
format: m.generator.GetFormatName(),
371-
secretsFound: secretsFound,
368+
err: fmt.Errorf("failed to copy to clipboard: %w", err),
369+
tokenCount: tokenCount,
370+
format: m.generator.GetFormatName(),
371+
secretCount: secretCount,
372372
}
373373
}
374374

375375
return clipboardCopiedMsg{
376-
err: nil,
377-
tokenCount: tokenCount,
378-
format: m.generator.GetFormatName(),
379-
secretsFound: secretsFound,
376+
err: nil,
377+
tokenCount: tokenCount,
378+
format: m.generator.GetFormatName(),
379+
secretCount: secretCount,
380380
}
381381
}
382382
}

internal/ui/help.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@ const UsageText = `Usage:
4040
-g, --glob pattern Include/exclude files and directories
4141
(e.g., --glob="*.{ts,tsx}" --glob="\\!*.spec.ts")
4242
-f, --format format Output format (available: markdown, text, xml)
43-
--skip-redaction Skip automatic secret redaction (Default: false)
43+
-S, --skip-redaction Skip automatic secret redaction (Default: false)
4444
WARNING: This may expose sensitive information!
4545
--theme Set the UI theme (available: catppuccin-latte,
4646
catppuccin-frappe, catppuccin-macchiato,

internal/ui/styles.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -57,7 +57,7 @@ func GetStyleSearchCount() lipgloss.Style {
5757
func GetStyleWarning() lipgloss.Style {
5858
return lipgloss.NewStyle().
5959
Foreground(themes.CurrentTheme.Colors().Warning).
60-
Bold(true)
60+
Bold(false)
6161
}
6262

6363
// GetStyleInfo returns the info style using the current theme

0 commit comments

Comments
 (0)