chore: modernize workflows, security scanning, and linting configuration (#50)

* build: update Go 1.25, CI workflows, and build tooling

- Upgrade to Go 1.25
- Add benchmark targets to Makefile
- Implement parallel gosec execution
- Lock tool versions for reproducibility
- Add shellcheck directives to scripts
- Update CI workflows with improved caching

* refactor: migrate from golangci-lint to revive

- Replace golangci-lint with revive for linting
- Configure comprehensive revive rules
- Fix all EditorConfig violations
- Add yamllint and yamlfmt support
- Remove deprecated .golangci.yml

* refactor: rename utils to shared and deduplicate code

- Rename utils package to shared
- Add shared constants package
- Deduplicate constants across packages
- Address CodeRabbit review feedback

* fix: resolve SonarQube issues and add safety guards

- Fix all 73 SonarQube OPEN issues
- Add nil guards for resourceMonitor, backpressure, metricsCollector
- Implement io.Closer for headerFileReader
- Propagate errors from processing helpers
- Add metrics and templates packages
- Improve error handling across codebase

* test: improve test infrastructure and coverage

- Add benchmarks for cli, fileproc, metrics
- Improve test coverage for cli, fileproc, config
- Refactor tests with helper functions
- Add shared test constants
- Fix test function naming conventions
- Reduce cognitive complexity in benchmark tests

* docs: update documentation and configuration examples

- Update CLAUDE.md with current project state
- Refresh README with new features
- Add usage and configuration examples
- Add SonarQube project configuration
- Consolidate config.example.yaml

* fix: resolve shellcheck warnings in scripts

- Use ./*.go instead of *.go to prevent dash-prefixed filenames
  from being interpreted as options (SC2035)
- Remove unreachable return statement after exit (SC2317)
- Remove obsolete gibidiutils/ directory reference

* chore(deps): upgrade go dependencies

* chore(lint): megalinter fixes

* fix: improve test coverage and fix file descriptor leaks

- Add defer r.Close() to fix pipe file descriptor leaks in benchmark tests
- Refactor TestProcessorConfigureFileTypes with helper functions and assertions
- Refactor TestProcessorLogFinalStats with output capture and keyword verification
- Use shared constants instead of literal strings (TestFilePNG, FormatMarkdown, etc.)
- Reduce cognitive complexity by extracting helper functions

* fix: align test comments with function names

Remove underscores from test comments to match actual function names:
- benchmark/benchmark_test.go (2 fixes)
- fileproc/filetypes_config_test.go (4 fixes)
- fileproc/filetypes_registry_test.go (6 fixes)
- fileproc/processor_test.go (6 fixes)
- fileproc/resource_monitor_types_test.go (4 fixes)
- fileproc/writer_test.go (3 fixes)

* fix: various test improvements and bug fixes

- Remove duplicate maxCacheSize check in filetypes_registry_test.go
- Shorten long comment in processor_test.go to stay under 120 chars
- Remove flaky time.Sleep in collector_test.go, use >= 0 assertion
- Close pipe reader in benchmark_test.go to fix file descriptor leak
- Use ContinueOnError in flags_test.go to match ResetFlags behavior
- Add nil check for p.ui in processor_workers.go before UpdateProgress
- Fix resource_monitor_validation_test.go by setting hardMemoryLimitBytes directly

* chore(yaml): add missing document start markers

Add --- document start to YAML files to satisfy yamllint:
- .github/workflows/codeql.yml
- .github/workflows/build-test-publish.yml
- .github/workflows/security.yml
- .github/actions/setup/action.yml

* fix: guard nil resourceMonitor and fix test deadlock

- Guard resourceMonitor before CreateFileProcessingContext call
- Add ui.UpdateProgress on emergency stop and path error returns
- Fix potential deadlock in TestProcessFile using wg.Go with defer close
This commit is contained in:
2025-12-10 19:07:11 +02:00
committed by GitHub
parent ea4a39a360
commit 95b7ef6dd3
149 changed files with 22990 additions and 8976 deletions

138
cli/ui.go
View File

@@ -1,3 +1,4 @@
// Package cli provides command-line interface functionality for gibidify.
package cli
import (
@@ -9,13 +10,14 @@ import (
"github.com/fatih/color"
"github.com/schollz/progressbar/v3"
"github.com/ivuorinen/gibidify/gibidiutils"
"github.com/ivuorinen/gibidify/shared"
)
// UIManager handles CLI user interface elements.
type UIManager struct {
enableColors bool
enableProgress bool
silentMode bool
progressBar *progressbar.ProgressBar
output io.Writer
}
@@ -40,43 +42,42 @@ func (ui *UIManager) SetProgressOutput(enabled bool) {
ui.enableProgress = enabled
}
// SetSilentMode enables or disables all UI output.
func (ui *UIManager) SetSilentMode(silent bool) {
ui.silentMode = silent
if silent {
ui.output = io.Discard
} else {
ui.output = os.Stderr
}
}
// StartProgress initializes a progress bar for file processing.
func (ui *UIManager) StartProgress(total int, description string) {
if !ui.enableProgress || total <= 0 {
return
}
// Set progress bar theme based on color support
var theme progressbar.Theme
if ui.enableColors {
theme = progressbar.Theme{
Saucer: color.GreenString("█"),
SaucerHead: color.GreenString("█"),
SaucerPadding: " ",
BarStart: "[",
BarEnd: "]",
}
} else {
theme = progressbar.Theme{
Saucer: "█",
SaucerHead: "█",
SaucerPadding: " ",
BarStart: "[",
BarEnd: "]",
}
}
ui.progressBar = progressbar.NewOptions(
total,
progressbar.OptionSetWriter(ui.output),
progressbar.OptionSetDescription(description),
progressbar.OptionSetTheme(theme),
progressbar.OptionSetTheme(
progressbar.Theme{
Saucer: color.GreenString(shared.UIProgressBarChar),
SaucerHead: color.GreenString(shared.UIProgressBarChar),
SaucerPadding: " ",
BarStart: "[",
BarEnd: "]",
},
),
progressbar.OptionShowCount(),
progressbar.OptionShowIts(),
progressbar.OptionSetWidth(40),
progressbar.OptionThrottle(100*time.Millisecond),
progressbar.OptionOnCompletion(
func() {
//nolint:errcheck // UI output, errors don't affect processing
_, _ = fmt.Fprint(ui.output, "\n")
},
),
@@ -99,49 +100,62 @@ func (ui *UIManager) FinishProgress() {
}
}
// writeMessage writes a formatted message with optional colorization.
// It handles color enablement, formatting, writing to output, and error logging.
func (ui *UIManager) writeMessage(
icon, methodName, format string,
colorFunc func(string, ...interface{}) string,
args ...interface{},
) {
msg := icon + " " + format
var output string
if ui.enableColors && colorFunc != nil {
output = colorFunc(msg, args...)
// PrintSuccess prints a success message in green.
func (ui *UIManager) PrintSuccess(format string, args ...any) {
if ui.silentMode {
return
}
if ui.enableColors {
color.Green("✓ "+format, args...)
} else {
output = fmt.Sprintf(msg, args...)
}
if _, err := fmt.Fprintf(ui.output, "%s\n", output); err != nil {
gibidiutils.LogError(fmt.Sprintf("UIManager.%s: failed to write to output", methodName), err)
ui.printf("✓ "+format+"\n", args...)
}
}
// PrintSuccess prints a success message in green (to ui.output if set).
func (ui *UIManager) PrintSuccess(format string, args ...interface{}) {
ui.writeMessage(gibidiutils.IconSuccess, "PrintSuccess", format, color.GreenString, args...)
// PrintError prints an error message in red.
func (ui *UIManager) PrintError(format string, args ...any) {
if ui.silentMode {
return
}
if ui.enableColors {
color.Red("✗ "+format, args...)
} else {
ui.printf("✗ "+format+"\n", args...)
}
}
// PrintError prints an error message in red (to ui.output if set).
func (ui *UIManager) PrintError(format string, args ...interface{}) {
ui.writeMessage(gibidiutils.IconError, "PrintError", format, color.RedString, args...)
// PrintWarning prints a warning message in yellow.
func (ui *UIManager) PrintWarning(format string, args ...any) {
if ui.silentMode {
return
}
if ui.enableColors {
color.Yellow("⚠ "+format, args...)
} else {
ui.printf("⚠ "+format+"\n", args...)
}
}
// PrintWarning prints a warning message in yellow (to ui.output if set).
func (ui *UIManager) PrintWarning(format string, args ...interface{}) {
ui.writeMessage(gibidiutils.IconWarning, "PrintWarning", format, color.YellowString, args...)
}
// PrintInfo prints an info message in blue (to ui.output if set).
func (ui *UIManager) PrintInfo(format string, args ...interface{}) {
ui.writeMessage(gibidiutils.IconInfo, "PrintInfo", format, color.BlueString, args...)
// PrintInfo prints an info message in blue.
func (ui *UIManager) PrintInfo(format string, args ...any) {
if ui.silentMode {
return
}
if ui.enableColors {
//nolint:errcheck // UI output, errors don't affect processing
color.Blue(" "+format, args...)
} else {
ui.printf(" "+format+"\n", args...)
}
}
// PrintHeader prints a header message in bold.
func (ui *UIManager) PrintHeader(format string, args ...interface{}) {
func (ui *UIManager) PrintHeader(format string, args ...any) {
if ui.silentMode {
return
}
if ui.enableColors {
//nolint:errcheck // UI output, errors don't affect processing
_, _ = color.New(color.Bold).Fprintf(ui.output, format+"\n", args...)
} else {
ui.printf(format+"\n", args...)
@@ -150,11 +164,6 @@ func (ui *UIManager) PrintHeader(format string, args ...interface{}) {
// isColorTerminal checks if the terminal supports colors.
func isColorTerminal() bool {
// Check if FORCE_COLOR is set
if os.Getenv("FORCE_COLOR") != "" {
return true
}
// Check common environment variables
term := os.Getenv("TERM")
if term == "" || term == "dumb" {
@@ -164,7 +173,7 @@ func isColorTerminal() bool {
// Check for CI environments that typically don't support colors
if os.Getenv("CI") != "" {
// GitHub Actions supports colors
if os.Getenv("GITHUB_ACTIONS") == "true" {
if os.Getenv("GITHUB_ACTIONS") == shared.LiteralTrue {
return true
}
// Most other CI systems don't
@@ -176,7 +185,13 @@ func isColorTerminal() bool {
return false
}
return true
// Check if FORCE_COLOR is set
if os.Getenv("FORCE_COLOR") != "" {
return true
}
// Default to true for interactive terminals
return isInteractiveTerminal()
}
// isInteractiveTerminal checks if we're running in an interactive terminal.
@@ -186,10 +201,11 @@ func isInteractiveTerminal() bool {
if err != nil {
return false
}
return (fileInfo.Mode() & os.ModeCharDevice) != 0
}
// printf is a helper that ignores printf errors (for UI output).
func (ui *UIManager) printf(format string, args ...interface{}) {
func (ui *UIManager) printf(format string, args ...any) {
_, _ = fmt.Fprintf(ui.output, format, args...)
}