Files
gibidify/cli/ui.go
Ismo Vuorinen 95b7ef6dd3 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
2025-12-10 19:07:11 +02:00

212 lines
5.0 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

// Package cli provides command-line interface functionality for gibidify.
package cli
import (
"fmt"
"io"
"os"
"time"
"github.com/fatih/color"
"github.com/schollz/progressbar/v3"
"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
}
// NewUIManager creates a new UI manager.
func NewUIManager() *UIManager {
return &UIManager{
enableColors: isColorTerminal(),
enableProgress: isInteractiveTerminal(),
output: os.Stderr, // Progress and colors go to stderr
}
}
// SetColorOutput enables or disables colored output.
func (ui *UIManager) SetColorOutput(enabled bool) {
ui.enableColors = enabled
color.NoColor = !enabled
}
// SetProgressOutput enables or disables progress bars.
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
}
ui.progressBar = progressbar.NewOptions(
total,
progressbar.OptionSetWriter(ui.output),
progressbar.OptionSetDescription(description),
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")
},
),
progressbar.OptionSetRenderBlankState(true),
)
}
// UpdateProgress increments the progress bar.
func (ui *UIManager) UpdateProgress(increment int) {
if ui.progressBar != nil {
_ = ui.progressBar.Add(increment)
}
}
// FinishProgress completes the progress bar.
func (ui *UIManager) FinishProgress() {
if ui.progressBar != nil {
_ = ui.progressBar.Finish()
ui.progressBar = nil
}
}
// 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 {
ui.printf("✓ "+format+"\n", 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...)
}
}
// 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...)
}
}
// 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 ...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...)
}
}
// isColorTerminal checks if the terminal supports colors.
func isColorTerminal() bool {
// Check common environment variables
term := os.Getenv("TERM")
if term == "" || term == "dumb" {
return false
}
// Check for CI environments that typically don't support colors
if os.Getenv("CI") != "" {
// GitHub Actions supports colors
if os.Getenv("GITHUB_ACTIONS") == shared.LiteralTrue {
return true
}
// Most other CI systems don't
return false
}
// Check if NO_COLOR is set (https://no-color.org/)
if os.Getenv("NO_COLOR") != "" {
return false
}
// 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.
func isInteractiveTerminal() bool {
// Check if stderr is a terminal (where we output progress/colors)
fileInfo, err := os.Stderr.Stat()
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 ...any) {
_, _ = fmt.Fprintf(ui.output, format, args...)
}