Skip to content

Commit c3dca76

Browse files
fix: CLI best practices - signal handling, exit codes, ldflags version
1 parent f0e69df commit c3dca76

2 files changed

Lines changed: 47 additions & 23 deletions

File tree

cmd/logt/main.go

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,9 @@ import (
66
"io"
77
"log"
88
"os"
9+
"os/signal"
910
"strings"
11+
"syscall"
1012
"time"
1113

1214
"github.com/charmbracelet/bubbletea"
@@ -18,7 +20,15 @@ import (
1820
"github.com/turkprogrammer/logt/internal/ui"
1921
)
2022

21-
var version = "0.5.0"
23+
// version устанавливается при сборке через -ldflags "-X main.version=..."
24+
var version = "dev"
25+
26+
// Коды выхода (Unix convention).
27+
const (
28+
exitOK = 0
29+
exitRuntime = 1
30+
exitUsage = 2
31+
)
2232

2333
func main() {
2434
// Обработка subcommands (должно быть до парсинга flags)
@@ -57,8 +67,8 @@ func showVersion(cfg *config.Config) {
5767
}
5868

5969
func runWithPaths(paths []string, cfg *config.Config) {
60-
ctx, cancel := context.WithCancel(context.Background())
61-
defer cancel()
70+
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
71+
defer stop()
6272

6373
var fileProvider provider.Provider
6474
mp := provider.NewMultiProvider()
@@ -74,26 +84,29 @@ func runWithPaths(paths []string, cfg *config.Config) {
7484

7585
expandedPaths := provider.ExpandPaths(paths)
7686
if len(expandedPaths) == 0 {
77-
log.Fatalf("No files found matching: %v", paths)
87+
fmt.Fprintf(os.Stderr, "No files found matching: %v\n", paths)
88+
os.Exit(exitUsage)
7889
}
7990

8091
if err := fileProvider.Watch(ctx, expandedPaths); err != nil {
81-
log.Fatalf("Failed to watch files: %v", err)
92+
fmt.Fprintf(os.Stderr, "Failed to watch files: %v\n", err)
93+
os.Exit(exitRuntime)
8294
}
8395

8496
run(ctx, mp, cfg)
8597
}
8698

8799
func runStdin(cfg *config.Config) {
88-
ctx, cancel := context.WithCancel(context.Background())
89-
defer cancel()
100+
ctx, stop := signal.NotifyContext(context.Background(), os.Interrupt, syscall.SIGTERM)
101+
defer stop()
90102

91103
stdinProvider := provider.NewStdinProvider()
92104
mp := provider.NewMultiProvider()
93105
mp.AddProvider(stdinProvider)
94106

95107
if err := stdinProvider.Start(ctx); err != nil {
96-
log.Fatalf("Failed to start stdin provider: %v", err)
108+
fmt.Fprintf(os.Stderr, "Failed to start stdin provider: %v\n", err)
109+
os.Exit(exitRuntime)
97110
}
98111

99112
run(ctx, mp, cfg)
@@ -145,7 +158,8 @@ func run(ctx context.Context, mp *provider.MultiProvider, cfg *config.Config) {
145158
)
146159

147160
if _, err := p.Run(); err != nil {
148-
log.Fatalf("Failed to run UI: %v", err)
161+
fmt.Fprintf(os.Stderr, "Failed to run UI: %v\n", err)
162+
os.Exit(exitRuntime)
149163
}
150164
}
151165

internal/config/config.go

Lines changed: 24 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -88,20 +88,30 @@ func Load() (*Config, error) {
8888
pflag.CommandLine.BoolP("version", "v", false, "Версия")
8989
pflag.CommandLine.BoolP("help", "h", false, "Помощь")
9090

91-
viper.BindPFlag("path", pflag.CommandLine.Lookup("path"))
92-
viper.BindPFlag("level", pflag.CommandLine.Lookup("level"))
93-
viper.BindPFlag("buffer-size", pflag.CommandLine.Lookup("buffer"))
94-
viper.BindPFlag("buffer-max", pflag.CommandLine.Lookup("max-buffer"))
95-
viper.BindPFlag("theme", pflag.CommandLine.Lookup("theme"))
96-
viper.BindPFlag("forward", pflag.CommandLine.Lookup("forward"))
97-
viper.BindPFlag("since", pflag.CommandLine.Lookup("since"))
98-
viper.BindPFlag("until", pflag.CommandLine.Lookup("until"))
99-
viper.BindPFlag("json-filter", pflag.CommandLine.Lookup("json"))
100-
viper.BindPFlag("headless", pflag.CommandLine.Lookup("headless"))
101-
viper.BindPFlag("tail", pflag.CommandLine.Lookup("tail"))
102-
viper.BindPFlag("stats", pflag.CommandLine.Lookup("stats"))
103-
viper.BindPFlag("export", pflag.CommandLine.Lookup("export"))
104-
viper.BindPFlag("color", pflag.CommandLine.Lookup("color"))
91+
bindFlags := []struct {
92+
key string
93+
flag string
94+
}{
95+
{"path", "path"},
96+
{"level", "level"},
97+
{"buffer-size", "buffer"},
98+
{"buffer-max", "max-buffer"},
99+
{"theme", "theme"},
100+
{"forward", "forward"},
101+
{"since", "since"},
102+
{"until", "until"},
103+
{"json-filter", "json"},
104+
{"headless", "headless"},
105+
{"tail", "tail"},
106+
{"stats", "stats"},
107+
{"export", "export"},
108+
{"color", "color"},
109+
}
110+
for _, b := range bindFlags {
111+
if err := viper.BindPFlag(b.key, pflag.CommandLine.Lookup(b.flag)); err != nil {
112+
return nil, fmt.Errorf("bind flag %q: %w", b.flag, err)
113+
}
114+
}
105115

106116
pflag.Parse()
107117

0 commit comments

Comments
 (0)