From 3ac71fdab4eb2b4ae19d146a6804cd12aebe8456 Mon Sep 17 00:00:00 2001 From: TyostoKarry <114697841+TyostoKarry@users.noreply.github.com> Date: Sun, 22 Mar 2026 14:37:52 +0200 Subject: [PATCH 1/2] Add custom help output for pflag Replace the default pflag help text with a cleaner custom usage screen. Group flags by mode, options, and misc commands, and add examples to make the CLI easier to understand. --- internal/help/help.go | 56 +++++++++++++++++++++++++++++++++++++++++++ main.go | 6 ++++- 2 files changed, 61 insertions(+), 1 deletion(-) create mode 100644 internal/help/help.go diff --git a/internal/help/help.go b/internal/help/help.go new file mode 100644 index 0000000..698d596 --- /dev/null +++ b/internal/help/help.go @@ -0,0 +1,56 @@ +package help + +import ( + "fmt" + "os" + "path/filepath" + + "github.com/spf13/pflag" +) + +func SetupCustomHelp() { + fs := pflag.CommandLine + fs.SortFlags = false + + pflag.Usage = func() { + name := filepath.Base(os.Args[0]) + out := fs.Output() + if out == nil { + out = os.Stderr + } + + fmt.Fprintf(out, "%s — sleep cycle calculator\n\n", name) + + fmt.Fprintf(out, "Usage:\n") + fmt.Fprintf(out, " %s [mode] [options]\n\n", name) + + fmt.Fprintf(out, "Choose exactly one mode:\n") + fmt.Fprintf(out, " -n, --now Calculate wake times from the current time\n") + fmt.Fprintf(out, " -w, --wake HH:MM Calculate bedtimes for a target wake time\n") + fmt.Fprintf(out, " -s, --sleep HH:MM Calculate wake times for a target sleep time\n") + fmt.Fprintf(out, " -f, --from HH:MM Start of sleep window. Use together with --to\n") + fmt.Fprintf(out, " -t, --to HH:MM End of sleep window. Use together with --from\n\n") + + fmt.Fprintf(out, "Options:\n") + fmt.Fprintf(out, " -b, --buffer int Minutes to fall asleep (default: 15)\n") + fmt.Fprintf(out, " -m, --cycles-min int Minimum cycles to show (default: 4)\n") + fmt.Fprintf(out, " -x, --cycles-max int Maximum cycles to show (default: 6)\n\n") + + fmt.Fprintf(out, "Other:\n") + fmt.Fprintf(out, " -g, --good-night Print random good night art\n") + fmt.Fprintf(out, " -v, --version Print version\n") + fmt.Fprintf(out, " -h, --help Show help\n\n") + + fmt.Fprintf(out, "Examples:\n") + fmt.Fprintf(out, " %s --now\n", name) + fmt.Fprintf(out, " %s --wake 07:00\n", name) + fmt.Fprintf(out, " %s --sleep 22:30\n", name) + fmt.Fprintf(out, " %s --from 22:00 --to 07:00\n", name) + fmt.Fprintf(out, " %s --wake 07:00 --buffer 20 --cycles-min 5 --cycles-max 6\n\n", name) + + fmt.Fprintf(out, "Notes:\n") + fmt.Fprintf(out, " - Time format is 24-hour HH:MM\n") + fmt.Fprintf(out, " - Short hours like 7:00 are accepted\n") + fmt.Fprintf(out, " - Modes cannot be combined\n") + } +} diff --git a/main.go b/main.go index ee4a5a2..5a6e572 100644 --- a/main.go +++ b/main.go @@ -5,12 +5,15 @@ import ( "os" "github.com/TyostoKarry/sleepycli/internal/goodnight" + "github.com/TyostoKarry/sleepycli/internal/help" "github.com/spf13/pflag" ) const version = "0.1.0" func main() { + help.SetupCustomHelp() + var ( nowFlag bool wakeFlag string @@ -32,7 +35,7 @@ func main() { pflag.IntVarP(&bufferFlag, "buffer", "b", 15, "Fall asleep buffer in minutes") pflag.IntVarP(&cyclesMinFlag, "cycles-min", "m", 4, "Minimum cycles to show") pflag.IntVarP(&cyclesMaxFlag, "cycles-max", "x", 6, "Maximum cycles to show") - pflag.BoolVarP(&goodNightFlag, "good-night", "", false, "Display a random good night art") + pflag.BoolVarP(&goodNightFlag, "good-night", "g", false, "Display a random good night art") pflag.BoolVarP(&versionFlag, "version", "v", false, "Print version") pflag.Parse() @@ -49,6 +52,7 @@ func main() { if err := validateAndSelectMode(nowFlag, wakeFlag, sleepFlag, fromFlag, toFlag, bufferFlag, cyclesMinFlag, cyclesMaxFlag); err != nil { fmt.Fprintln(os.Stderr, "Error:", err) + fmt.Fprintln(os.Stderr, "Run 'sleepycli --help' for usage.") os.Exit(1) } } From 28ecb7742a91e09dee514f80b506381393adc9d0 Mon Sep 17 00:00:00 2001 From: TyostoKarry <114697841+TyostoKarry@users.noreply.github.com> Date: Sun, 22 Mar 2026 15:03:25 +0200 Subject: [PATCH 2/2] Handle help output write errors properly Build the custom help text in memory and write it once so output errors are handled cleanly and golangci-lint errcheck passes without suppressing returned errors on every fmt.Fprintf call. --- internal/help/help.go | 78 +++++++++++++++++++++++++------------------ 1 file changed, 45 insertions(+), 33 deletions(-) diff --git a/internal/help/help.go b/internal/help/help.go index 698d596..fb6d7f1 100644 --- a/internal/help/help.go +++ b/internal/help/help.go @@ -1,7 +1,9 @@ package help import ( + "bytes" "fmt" + "io" "os" "path/filepath" @@ -19,38 +21,48 @@ func SetupCustomHelp() { out = os.Stderr } - fmt.Fprintf(out, "%s — sleep cycle calculator\n\n", name) - - fmt.Fprintf(out, "Usage:\n") - fmt.Fprintf(out, " %s [mode] [options]\n\n", name) - - fmt.Fprintf(out, "Choose exactly one mode:\n") - fmt.Fprintf(out, " -n, --now Calculate wake times from the current time\n") - fmt.Fprintf(out, " -w, --wake HH:MM Calculate bedtimes for a target wake time\n") - fmt.Fprintf(out, " -s, --sleep HH:MM Calculate wake times for a target sleep time\n") - fmt.Fprintf(out, " -f, --from HH:MM Start of sleep window. Use together with --to\n") - fmt.Fprintf(out, " -t, --to HH:MM End of sleep window. Use together with --from\n\n") - - fmt.Fprintf(out, "Options:\n") - fmt.Fprintf(out, " -b, --buffer int Minutes to fall asleep (default: 15)\n") - fmt.Fprintf(out, " -m, --cycles-min int Minimum cycles to show (default: 4)\n") - fmt.Fprintf(out, " -x, --cycles-max int Maximum cycles to show (default: 6)\n\n") - - fmt.Fprintf(out, "Other:\n") - fmt.Fprintf(out, " -g, --good-night Print random good night art\n") - fmt.Fprintf(out, " -v, --version Print version\n") - fmt.Fprintf(out, " -h, --help Show help\n\n") - - fmt.Fprintf(out, "Examples:\n") - fmt.Fprintf(out, " %s --now\n", name) - fmt.Fprintf(out, " %s --wake 07:00\n", name) - fmt.Fprintf(out, " %s --sleep 22:30\n", name) - fmt.Fprintf(out, " %s --from 22:00 --to 07:00\n", name) - fmt.Fprintf(out, " %s --wake 07:00 --buffer 20 --cycles-min 5 --cycles-max 6\n\n", name) - - fmt.Fprintf(out, "Notes:\n") - fmt.Fprintf(out, " - Time format is 24-hour HH:MM\n") - fmt.Fprintf(out, " - Short hours like 7:00 are accepted\n") - fmt.Fprintf(out, " - Modes cannot be combined\n") + if err := writeHelp(out, name); err != nil { + _, _ = fmt.Fprintf(os.Stderr, "failed to write help output: %v\n", err) + } } } + +func writeHelp(w io.Writer, name string) error { + var b bytes.Buffer + + fmt.Fprintf(&b, "%s — sleep cycle calculator\n\n", name) + b.WriteString("Usage:\n") + fmt.Fprintf(&b, " %s [mode] [options]\n\n", name) + + b.WriteString("Choose exactly one mode:\n") + b.WriteString(" -n, --now Calculate wake times from the current time\n") + b.WriteString(" -w, --wake HH:MM Calculate bedtimes for a target wake time\n") + b.WriteString(" -s, --sleep HH:MM Calculate wake times for a target sleep time\n") + b.WriteString(" -f, --from HH:MM Start of sleep window. Use together with --to\n") + b.WriteString(" -t, --to HH:MM End of sleep window. Use together with --from\n\n") + + b.WriteString("Options:\n") + b.WriteString(" -b, --buffer int Minutes to fall asleep (default: 15)\n") + b.WriteString(" -m, --cycles-min int Minimum cycles to show (default: 4)\n") + b.WriteString(" -x, --cycles-max int Maximum cycles to show (default: 6)\n\n") + + b.WriteString("Other:\n") + b.WriteString(" -g, --good-night Print random good night art\n") + b.WriteString(" -v, --version Print version\n") + b.WriteString(" -h, --help Show help\n\n") + + b.WriteString("Examples:\n") + fmt.Fprintf(&b, " %s --now\n", name) + fmt.Fprintf(&b, " %s --wake 07:00\n", name) + fmt.Fprintf(&b, " %s --sleep 22:30\n", name) + fmt.Fprintf(&b, " %s --from 22:00 --to 07:00\n", name) + fmt.Fprintf(&b, " %s --wake 07:00 --buffer 20 --cycles-min 5 --cycles-max 6\n\n", name) + + b.WriteString("Notes:\n") + b.WriteString(" - Time format is 24-hour HH:MM\n") + b.WriteString(" - Short hours like 7:00 are accepted\n") + b.WriteString(" - Modes cannot be combined\n") + + _, err := io.WriteString(w, b.String()) + return err +}