summaryrefslogtreecommitdiff
path: root/internal
diff options
context:
space:
mode:
authorBenjamin Chausse <benjamin.chausse@goto.com>2025-03-26 10:52:14 -0400
committerBenjamin Chausse <benjamin.chausse@goto.com>2025-03-26 10:52:14 -0400
commit04213e975c46b0d6bfecc8695801b85c3f3dd0ab (patch)
tree73d39c5f614c83baad5eb6ad8669a27750770e2a /internal
parentcd9338e0d6cf582f9ea8028661ac3729e408f3bf (diff)
charmbracelet/glamour for help + man page generate
Diffstat (limited to 'internal')
-rw-r--r--internal/app/app.go67
-rw-r--r--internal/app/flags.go21
-rw-r--r--internal/app/keybindings.md24
-rw-r--r--internal/documentation/documentation.go44
-rw-r--r--internal/logging/discard.go25
-rw-r--r--internal/logging/logging.go8
6 files changed, 182 insertions, 7 deletions
diff --git a/internal/app/app.go b/internal/app/app.go
new file mode 100644
index 0000000..b17f32a
--- /dev/null
+++ b/internal/app/app.go
@@ -0,0 +1,67 @@
+package app
+
+import (
+ "context"
+ _ "embed"
+ "fmt"
+ "io"
+ "log/slog"
+
+ "github.com/ChausseBenjamin/termpicker/internal/logging"
+ "github.com/ChausseBenjamin/termpicker/internal/switcher"
+ tea "github.com/charmbracelet/bubbletea"
+ "github.com/charmbracelet/glamour"
+ docs "github.com/urfave/cli-docs/v3"
+ "github.com/urfave/cli/v3"
+)
+
+//go:embed keybindings.md
+var KeybindingDocs string
+
+// Set by the build system
+var version = "compiled"
+
+func AppAction(ctx context.Context, cmd *cli.Command) error {
+ logfile := logging.Setup(cmd.String("logfile"))
+ defer logfile.Close()
+
+ slog.Info("Starting Termpicker")
+
+ sw := switcher.New()
+
+ if colorStr := cmd.String("color"); colorStr != "" {
+ sw.NewNotice(sw.SetColorFromText(colorStr))
+ }
+
+ p := tea.NewProgram(sw)
+ if _, err := p.Run(); err != nil {
+ return err
+ }
+ return nil
+}
+
+func Command() *cli.Command {
+ cmd := &cli.Command{
+ Name: "termpicker",
+ Usage: "A terminal-based color picker",
+ Action: AppAction,
+ Authors: []any{"Benjamin Chausse <benjamin@chausse.xyz>"},
+ Version: version,
+ Flags: AppFlags,
+ EnableShellCompletion: true,
+ }
+
+ cli.HelpPrinter = func(w io.Writer, _ string, _ any) {
+ docs.MarkdownDocTemplate = fmt.Sprintf("%s\n‎\n\n%s",
+ docs.MarkdownDocTemplate,
+ KeybindingDocs,
+ )
+
+ helpRaw, _ := docs.ToMarkdown(cmd)
+ helpCute, _ := glamour.Render(helpRaw, "dark")
+
+ w.Write([]byte(helpCute))
+ }
+
+ return cmd
+}
diff --git a/internal/app/flags.go b/internal/app/flags.go
new file mode 100644
index 0000000..fae403b
--- /dev/null
+++ b/internal/app/flags.go
@@ -0,0 +1,21 @@
+package app
+
+import "github.com/urfave/cli/v3"
+
+const (
+ flagLogfile = "logfile"
+)
+
+var AppFlags []cli.Flag = []cli.Flag{
+ &cli.StringFlag{
+ Name: flagLogfile,
+ Aliases: []string{"l"},
+ Usage: "Log file",
+ },
+ &cli.StringFlag{
+ Name: "color",
+ Aliases: []string{"c"},
+ Usage: "Initial color",
+ Value: "",
+ },
+}
diff --git a/internal/app/keybindings.md b/internal/app/keybindings.md
new file mode 100644
index 0000000..03f8201
--- /dev/null
+++ b/internal/app/keybindings.md
@@ -0,0 +1,24 @@
+# KEYBINDINGS
+
+**Normal mode**:
+
+- `h`,`l`: coarse decrease/increase the current slider by 5%
+- `H`,`L`: fine decrease/increase the current slider by 1
+- `j`,`k`: select the slider below/above
+- `<Tab>`,`<S-Tab>`: move to the next/previous tab
+- `f`,`b`: copy the color as an ANSI escape code for the foreground/background
+- `x`,`r`,`s`,`c`: copy the color as a hex/rgb/hsl/cmyk
+- `?`: expand/shrink the help menu
+- `i`,`<cmd>`: enter Insert mode
+- `q`/`<C-c>`: quit the application
+
+**Insert mode**:
+
+Manually type a color. Pressing <Esc> will cancel/leave insert mode.
+Anything in the following formats will be used as a color input when
+pressing enter:
+
+- Hex values: `#rrggbb`
+- RGB values: `rgb( r, g, b)`
+- CMYK values: `cmyk(c, m, y, k)`
+- HSL values: `hsl(h, s, l)`
diff --git a/internal/documentation/documentation.go b/internal/documentation/documentation.go
new file mode 100644
index 0000000..af4b302
--- /dev/null
+++ b/internal/documentation/documentation.go
@@ -0,0 +1,44 @@
+/*
+ * This package isn't the actual termpicker app.
+ * To avoid importing packages which aren't needed at runtime,
+ * some auto-generation functionnalities is offloaded to here so
+ * it can be done with access to the rest of the code-base but
+ * without bloating the final binary. For example,
+ * generating bash+zsh auto-completion scripts isn't needed in
+ * the final binary if those script are generated before hand.
+ * Same gose for manpages. This file is meant to be run automatically
+ * to easily package new releases. Same goes for manpages which is the
+ * only feature currently in here.
+ */
+package main
+
+import (
+ _ "embed"
+ "log/slog"
+ "os"
+ "strings"
+
+ "github.com/ChausseBenjamin/termpicker/internal/app"
+ docs "github.com/urfave/cli-docs/v3"
+)
+
+func main() {
+ a := app.Command()
+
+ docs.MarkdownDocTemplate = strings.Join(
+ []string{
+ docs.MarkdownDocTemplate,
+ app.KeybindingDocs,
+ },
+ "\n",
+ )
+
+ man, err := docs.ToManWithSection(a, 1)
+ if err != nil {
+ slog.Error("failed to generate man page",
+ slog.Any("error_message", err),
+ )
+ os.Exit(1)
+ }
+ os.Stdout.Write([]byte(man))
+}
diff --git a/internal/logging/discard.go b/internal/logging/discard.go
new file mode 100644
index 0000000..e827287
--- /dev/null
+++ b/internal/logging/discard.go
@@ -0,0 +1,25 @@
+package logging
+
+import (
+ "context"
+ "log/slog"
+)
+
+// DiscardHandler discards all log output. DiscardHandler.Enabled returns false for all Levels.
+type DiscardHandler struct{}
+
+func (d DiscardHandler) Enabled(ctx context.Context, level slog.Level) bool {
+ return false
+}
+
+func (d DiscardHandler) Handle(ctx context.Context, record slog.Record) error {
+ return nil
+}
+
+func (d DiscardHandler) WithAttrs(attrs []slog.Attr) slog.Handler {
+ return d
+}
+
+func (d DiscardHandler) WithGroup(name string) slog.Handler {
+ return d
+}
diff --git a/internal/logging/logging.go b/internal/logging/logging.go
index 71f0334..054b303 100644
--- a/internal/logging/logging.go
+++ b/internal/logging/logging.go
@@ -7,12 +7,6 @@ import (
"github.com/ChausseBenjamin/termpicker/internal/util"
)
-type logSink struct{}
-
-func (l logSink) Write(p []byte) (n int, err error) {
- return len(p), nil
-}
-
func Setup(filepath string) *os.File {
if filepath != "" {
logFile, err := os.Create(filepath)
@@ -28,7 +22,7 @@ func Setup(filepath string) *os.File {
} else {
// Since app is a TUI, logging to stdout/stderr would break the UI
// So we disable it by default
- handler := slog.NewJSONHandler(logSink{}, nil)
+ handler := DiscardHandler{}
slog.SetDefault(slog.New(handler))
return nil
}