(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 {
return
}
if KeyboardManager != nil {
KeyboardManager.ClearKeyboardInfo()
}
p := l.getPresenter(container)
timestamp := time.Now().Format(jsonmessage.RFC3339NanoFixed)
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)
}
}
if KeyboardManager != nil {
KeyboardManager.PrintKeyboardInfo()
}
}
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))
}
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"
"math"
"os"
"reflect"
"syscall"
"time"
@ -71,11 +70,11 @@ func (ke *KeyboardError) error() string {
type KeyboardWatch struct {
Watching bool
Watcher Toggle
IsConfigured bool
Watcher Feature
}
type Toggle interface {
// Feature is an compose feature that can be started/stopped by a menu command
type Feature interface {
Start(context.Context) error
Stop() error
}
@ -90,31 +89,26 @@ const (
type LogKeyboard struct {
kError KeyboardError
Watch KeyboardWatch
Watch *KeyboardWatch
IsDockerDesktopActive bool
logLevel KEYBOARD_LOG_LEVEL
signalChannel chan<- os.Signal
}
// FIXME(ndeloof) we should avoid use of such a global reference. see use in logConsumer
var KeyboardManager *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(),
},
func NewKeyboardManager(isDockerDesktopActive bool, sc chan<- os.Signal) *LogKeyboard {
return &LogKeyboard{
IsDockerDesktopActive: isDockerDesktopActive,
logLevel: INFO,
signalChannel: sc,
}
return KeyboardManager
}
func (lk *LogKeyboard) ClearKeyboardInfo() {
lk.clearNavigationMenu()
func (lk *LogKeyboard) Decorate(l api.LogConsumer) api.LogConsumer {
return logDecorator{
decorated: l,
Before: lk.clearNavigationMenu,
After: lk.PrintKeyboardInfo,
}
}
func (lk *LogKeyboard) PrintKeyboardInfo() {
@ -185,7 +179,7 @@ func (lk *LogKeyboard) navigationMenu() string {
watchInfo = navColor(" ")
}
isEnabled := " Enable"
if lk.Watch.Watching {
if lk.Watch != nil && lk.Watch.Watching {
isEnabled = " Disable"
}
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) {
if !lk.Watch.IsConfigured {
if lk.Watch == nil {
return
}
if lk.Watch.Watching {
@ -299,7 +293,7 @@ func (lk *LogKeyboard) HandleKeyEvents(ctx context.Context, event keyboard.KeyEv
case 'v':
lk.openDockerDesktop(ctx, project)
case 'w':
if !lk.Watch.IsConfigured {
if lk.Watch == nil {
// we try to open watch docs if DD is installed
if lk.IsDockerDesktopActive {
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) {
for i := 0; i < lines; i++ {
ClearLine()

View File

@ -22,15 +22,12 @@ import (
"go.opentelemetry.io/otel/attribute"
)
func KeyboardMetrics(ctx context.Context, enabled, isDockerDesktopActive, isWatchConfigured bool) {
func KeyboardMetrics(ctx context.Context, enabled, isDockerDesktopActive bool) {
commandAvailable := []string{}
if isDockerDesktopActive {
commandAvailable = append(commandAvailable, "gui")
commandAvailable = append(commandAvailable, "gui/composeview")
}
if isWatchConfigured {
commandAvailable = append(commandAvailable, "watch")
}
AddAttributeToSpan(ctx,
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)
defer signal.Stop(signalChan)
var isTerminated atomic.Bool
printer := newLogPrinter(options.Start.Attach)
watcher, err := NewWatcher(project, options, s.watch)
if err != nil && options.Start.Watch {
return err
}
var navigationMenu *formatter.LogKeyboard
var kEvents <-chan keyboard.KeyEvent
var (
logConsumer = options.Start.Attach
navigationMenu *formatter.LogKeyboard
kEvents <-chan keyboard.KeyEvent
)
if options.Start.NavigationMenu {
kEvents, err = keyboard.GetKeys(100)
if err != nil {
@ -87,11 +84,23 @@ func (s *composeService) Up(ctx context.Context, project *types.Project, options
} else {
defer keyboard.Close() //nolint:errcheck
isDockerDesktopActive := s.isDesktopIntegrationActive()
tracing.KeyboardMetrics(ctx, options.Start.NavigationMenu, isDockerDesktopActive, watcher != nil)
navigationMenu = formatter.NewKeyboardManager(isDockerDesktopActive, signalChan, options.Start.Watch, watcher)
tracing.KeyboardMetrics(ctx, options.Start.NavigationMenu, isDockerDesktopActive)
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)
eg.Go(func() error {
first := true

View File

@ -55,7 +55,7 @@ type Watcher struct {
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 {
service := project.Services[i]
@ -65,7 +65,7 @@ func NewWatcher(project *types.Project, options api.UpOptions, w WatchFunc) (*Wa
return &Watcher{
project: project,
options: api.WatchOptions{
LogTo: options.Start.Attach,
LogTo: consumer,
Build: build,
},
watchFn: w,