(reactoring) avoid a global variable by introducing logConsumer decorator

Signed-off-by: Nicolas De Loof <nicolas.deloof@gmail.com>
This commit is contained in:
Nicolas De Loof 2025-06-16 11:38:07 +02:00 committed by Guillaume Lours
parent 2c69fc3d4d
commit 6fa173124a
5 changed files with 73 additions and 46 deletions

View File

@ -118,10 +118,6 @@ func (l *logConsumer) write(w io.Writer, container, message string) {
if l.ctx.Err() != nil { if l.ctx.Err() != nil {
return return
} }
if KeyboardManager != nil {
KeyboardManager.ClearKeyboardInfo()
}
p := l.getPresenter(container) p := l.getPresenter(container)
timestamp := time.Now().Format(jsonmessage.RFC3339NanoFixed) timestamp := time.Now().Format(jsonmessage.RFC3339NanoFixed)
for _, line := range strings.Split(message, "\n") { for _, line := range strings.Split(message, "\n") {
@ -131,10 +127,6 @@ func (l *logConsumer) write(w io.Writer, container, message string) {
_, _ = fmt.Fprintf(w, "%s%s\n", p.prefix, line) _, _ = fmt.Fprintf(w, "%s%s\n", p.prefix, line)
} }
} }
if KeyboardManager != nil {
KeyboardManager.PrintKeyboardInfo()
}
} }
func (l *logConsumer) Status(container, msg string) { func (l *logConsumer) Status(container, msg string) {
@ -168,3 +160,31 @@ func (p *presenter) setPrefix(width int) {
} }
p.prefix = p.colors(fmt.Sprintf("%-"+strconv.Itoa(width)+"s | ", p.name)) p.prefix = p.colors(fmt.Sprintf("%-"+strconv.Itoa(width)+"s | ", p.name))
} }
type logDecorator struct {
decorated api.LogConsumer
Before func()
After func()
}
func (l logDecorator) Log(containerName, message string) {
l.Before()
l.decorated.Log(containerName, message)
l.After()
}
func (l logDecorator) Err(containerName, message string) {
l.Before()
l.decorated.Err(containerName, message)
l.After()
}
func (l logDecorator) Status(container, msg string) {
l.Before()
l.decorated.Status(container, msg)
l.After()
}
func (l logDecorator) Register(container string) {
l.decorated.Register(container)
}

View File

@ -22,7 +22,6 @@ import (
"fmt" "fmt"
"math" "math"
"os" "os"
"reflect"
"syscall" "syscall"
"time" "time"
@ -70,12 +69,12 @@ func (ke *KeyboardError) error() string {
} }
type KeyboardWatch struct { type KeyboardWatch struct {
Watching bool Watching bool
Watcher Toggle Watcher Feature
IsConfigured bool
} }
type Toggle interface { // Feature is an compose feature that can be started/stopped by a menu command
type Feature interface {
Start(context.Context) error Start(context.Context) error
Stop() error Stop() error
} }
@ -90,31 +89,26 @@ const (
type LogKeyboard struct { type LogKeyboard struct {
kError KeyboardError kError KeyboardError
Watch KeyboardWatch Watch *KeyboardWatch
IsDockerDesktopActive bool IsDockerDesktopActive bool
logLevel KEYBOARD_LOG_LEVEL logLevel KEYBOARD_LOG_LEVEL
signalChannel chan<- os.Signal signalChannel chan<- os.Signal
} }
// FIXME(ndeloof) we should avoid use of such a global reference. see use in logConsumer func NewKeyboardManager(isDockerDesktopActive bool, sc chan<- os.Signal) *LogKeyboard {
var KeyboardManager *LogKeyboard return &LogKeyboard{
func NewKeyboardManager(isDockerDesktopActive bool, sc chan<- os.Signal, w bool, watcher Toggle) *LogKeyboard {
KeyboardManager = &LogKeyboard{
Watch: KeyboardWatch{
Watching: w,
Watcher: watcher,
IsConfigured: !reflect.ValueOf(watcher).IsNil(),
},
IsDockerDesktopActive: isDockerDesktopActive, IsDockerDesktopActive: isDockerDesktopActive,
logLevel: INFO, logLevel: INFO,
signalChannel: sc, signalChannel: sc,
} }
return KeyboardManager
} }
func (lk *LogKeyboard) ClearKeyboardInfo() { func (lk *LogKeyboard) Decorate(l api.LogConsumer) api.LogConsumer {
lk.clearNavigationMenu() return logDecorator{
decorated: l,
Before: lk.clearNavigationMenu,
After: lk.PrintKeyboardInfo,
}
} }
func (lk *LogKeyboard) PrintKeyboardInfo() { func (lk *LogKeyboard) PrintKeyboardInfo() {
@ -185,7 +179,7 @@ func (lk *LogKeyboard) navigationMenu() string {
watchInfo = navColor(" ") watchInfo = navColor(" ")
} }
isEnabled := " Enable" isEnabled := " Enable"
if lk.Watch.Watching { if lk.Watch != nil && lk.Watch.Watching {
isEnabled = " Disable" isEnabled = " Disable"
} }
watchInfo = watchInfo + shortcutKeyColor("w") + navColor(isEnabled+" Watch") watchInfo = watchInfo + shortcutKeyColor("w") + navColor(isEnabled+" Watch")
@ -268,7 +262,7 @@ func (lk *LogKeyboard) keyboardError(prefix string, err error) {
} }
func (lk *LogKeyboard) ToggleWatch(ctx context.Context, options api.UpOptions) { func (lk *LogKeyboard) ToggleWatch(ctx context.Context, options api.UpOptions) {
if !lk.Watch.IsConfigured { if lk.Watch == nil {
return return
} }
if lk.Watch.Watching { if lk.Watch.Watching {
@ -299,7 +293,7 @@ func (lk *LogKeyboard) HandleKeyEvents(ctx context.Context, event keyboard.KeyEv
case 'v': case 'v':
lk.openDockerDesktop(ctx, project) lk.openDockerDesktop(ctx, project)
case 'w': case 'w':
if !lk.Watch.IsConfigured { if lk.Watch == nil {
// we try to open watch docs if DD is installed // we try to open watch docs if DD is installed
if lk.IsDockerDesktopActive { if lk.IsDockerDesktopActive {
lk.openDDWatchDocs(ctx, project) lk.openDDWatchDocs(ctx, project)
@ -333,6 +327,13 @@ func (lk *LogKeyboard) HandleKeyEvents(ctx context.Context, event keyboard.KeyEv
} }
} }
func (lk *LogKeyboard) EnableWatch(enabled bool, watcher Feature) {
lk.Watch = &KeyboardWatch{
Watching: enabled,
Watcher: watcher,
}
}
func allocateSpace(lines int) { func allocateSpace(lines int) {
for i := 0; i < lines; i++ { for i := 0; i < lines; i++ {
ClearLine() ClearLine()

View File

@ -22,15 +22,12 @@ import (
"go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/attribute"
) )
func KeyboardMetrics(ctx context.Context, enabled, isDockerDesktopActive, isWatchConfigured bool) { func KeyboardMetrics(ctx context.Context, enabled, isDockerDesktopActive bool) {
commandAvailable := []string{} commandAvailable := []string{}
if isDockerDesktopActive { if isDockerDesktopActive {
commandAvailable = append(commandAvailable, "gui") commandAvailable = append(commandAvailable, "gui")
commandAvailable = append(commandAvailable, "gui/composeview") commandAvailable = append(commandAvailable, "gui/composeview")
} }
if isWatchConfigured {
commandAvailable = append(commandAvailable, "watch")
}
AddAttributeToSpan(ctx, AddAttributeToSpan(ctx,
attribute.Bool("navmenu.enabled", enabled), attribute.Bool("navmenu.enabled", enabled),

View File

@ -70,15 +70,12 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM) signal.Notify(signalChan, syscall.SIGINT, syscall.SIGTERM)
defer signal.Stop(signalChan) defer signal.Stop(signalChan)
var isTerminated atomic.Bool var isTerminated atomic.Bool
printer := newLogPrinter(options.Start.Attach)
watcher, err := NewWatcher(project, options, s.watch) var (
if err != nil && options.Start.Watch { logConsumer = options.Start.Attach
return err navigationMenu *formatter.LogKeyboard
} kEvents <-chan keyboard.KeyEvent
)
var navigationMenu *formatter.LogKeyboard
var kEvents <-chan keyboard.KeyEvent
if options.Start.NavigationMenu { if options.Start.NavigationMenu {
kEvents, err = keyboard.GetKeys(100) kEvents, err = keyboard.GetKeys(100)
if err != nil { if err != nil {
@ -87,11 +84,23 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
} else { } else {
defer keyboard.Close() //nolint:errcheck defer keyboard.Close() //nolint:errcheck
isDockerDesktopActive := s.isDesktopIntegrationActive() isDockerDesktopActive := s.isDesktopIntegrationActive()
tracing.KeyboardMetrics(ctx, options.Start.NavigationMenu, isDockerDesktopActive, watcher != nil) tracing.KeyboardMetrics(ctx, options.Start.NavigationMenu, isDockerDesktopActive)
navigationMenu = formatter.NewKeyboardManager(isDockerDesktopActive, signalChan, options.Start.Watch, watcher) navigationMenu = formatter.NewKeyboardManager(isDockerDesktopActive, signalChan)
logConsumer = navigationMenu.Decorate(logConsumer)
} }
} }
watcher, err := NewWatcher(project, options, s.watch, logConsumer)
if err != nil && options.Start.Watch {
return err
}
if navigationMenu != nil && watcher != nil {
navigationMenu.EnableWatch(options.Start.Watch, watcher)
}
printer := newLogPrinter(logConsumer)
doneCh := make(chan bool) doneCh := make(chan bool)
eg.Go(func() error { eg.Go(func() error {
first := true first := true

View File

@ -55,7 +55,7 @@ type Watcher struct {
errCh chan error errCh chan error
} }
func NewWatcher(project *types.Project, options api.UpOptions, w WatchFunc) (*Watcher, error) { func NewWatcher(project *types.Project, options api.UpOptions, w WatchFunc, consumer api.LogConsumer) (*Watcher, error) {
for i := range project.Services { for i := range project.Services {
service := project.Services[i] service := project.Services[i]
@ -65,7 +65,7 @@ func NewWatcher(project *types.Project, options api.UpOptions, w WatchFunc) (*Wa
return &Watcher{ return &Watcher{
project: project, project: project,
options: api.WatchOptions{ options: api.WatchOptions{
LogTo: options.Start.Attach, LogTo: consumer,
Build: build, Build: build,
}, },
watchFn: w, watchFn: w,