diff --git a/.goreleaser.yaml b/.goreleaser.yaml index b02be81..8500da4 100644 --- a/.goreleaser.yaml +++ b/.goreleaser.yaml @@ -12,7 +12,7 @@ builds: ldflags: - -s - -w - - -X main.version=v{{.Version}} + - -X main.version={{.Version}} goarch: - amd64 - arm diff --git a/cmd/cmd.go b/cmd/cmd.go index 1e6ee05..4d12077 100644 --- a/cmd/cmd.go +++ b/cmd/cmd.go @@ -13,13 +13,14 @@ import ( "gabe565.com/castsponsorskip/internal/config" "gabe565.com/castsponsorskip/internal/device" "gabe565.com/castsponsorskip/internal/youtube" + "gabe565.com/utils/cobrax" "github.com/spf13/cobra" ) //go:embed description.md var long string -func New(opts ...Option) *cobra.Command { +func New(opts ...cobrax.Option) *cobra.Command { cmd := &cobra.Command{ Use: "castsponsorskip", Short: "Skip sponsored YouTube segments on local Cast devices", @@ -37,7 +38,6 @@ func New(opts ...Option) *cobra.Command { config.InitLog(cmd.ErrOrStderr(), slog.LevelInfo, config.FormatAuto) config.RegisterFlags(cmd) config.RegisterCompletions(cmd) - CompletionFlag(cmd) for _, opt := range opts { opt(cmd) @@ -57,15 +57,9 @@ func preRun(cmd *cobra.Command, _ []string) error { } func run(cmd *cobra.Command, _ []string) error { - if shell, err := cmd.Flags().GetString("completion"); err != nil { - panic(err) - } else if shell != "" { - return completion(cmd, shell) - } - conf := config.FromContext(cmd.Context()) - slog.Info("CastSponsorSkip", "version", cmd.Annotations[VersionKey], "commit", cmd.Annotations[CommitKey]) + slog.Info("CastSponsorSkip", "version", cobrax.GetVersion(cmd), "commit", cobrax.GetCommit(cmd)) ctx, cancel := signal.NotifyContext(cmd.Context(), os.Interrupt, syscall.SIGTERM, syscall.SIGQUIT) defer cancel() diff --git a/cmd/cmd_test.go b/cmd/cmd_test.go index 969392d..5d905f4 100644 --- a/cmd/cmd_test.go +++ b/cmd/cmd_test.go @@ -1,7 +1,6 @@ package cmd import ( - "bytes" "math/rand" "net" "strconv" @@ -133,27 +132,3 @@ func TestSBCEnvs(t *testing.T) { assert.Equal(t, []string{"a", "b", "c"}, conf.Categories) assert.Equal(t, "AIzaSyDaGmWKa4JsXZ-HjGw7ISLn_3namBGewQe", conf.YouTubeAPIKey) } - -func TestCompletionFlag(t *testing.T) { - tests := []struct { - shell string - wantErr require.ErrorAssertionFunc - }{ - {"bash", require.NoError}, - {"zsh", require.NoError}, - {"fish", require.NoError}, - {"powershell", require.NoError}, - {"invalid", require.Error}, - } - for _, tt := range tests { - t.Run(tt.shell, func(t *testing.T) { - cmd := New() - cmd.SetArgs([]string{"--completion", tt.shell}) - - var buf bytes.Buffer - cmd.SetOut(&buf) - tt.wantErr(t, cmd.Execute()) - assert.NotZero(t, buf.Bytes()) - }) - } -} diff --git a/cmd/flag_completion.go b/cmd/flag_completion.go deleted file mode 100644 index 0510120..0000000 --- a/cmd/flag_completion.go +++ /dev/null @@ -1,46 +0,0 @@ -package cmd - -import ( - "errors" - "fmt" - - "github.com/spf13/cobra" -) - -func CompletionFlag(cmd *cobra.Command) { - cmd.PersistentFlags().String("completion", "", "Output command-line completion code for the specified shell. Can be 'bash', 'zsh', 'fish', or 'powershell'.") - err := cmd.RegisterFlagCompletionFunc("completion", completionCompletion) - if err != nil { - panic(err) - } -} - -func completionCompletion(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { - return []string{"bash", "zsh", "fish", "powershell"}, cobra.ShellCompDirectiveNoFileComp -} - -var ErrInvalidShell = errors.New("invalid shell") - -func completion(cmd *cobra.Command, shell string) error { - switch shell { - case "bash": - if err := cmd.Root().GenBashCompletion(cmd.OutOrStdout()); err != nil { - return err - } - case "zsh": - if err := cmd.Root().GenZshCompletion(cmd.OutOrStdout()); err != nil { - return err - } - case "fish": - if err := cmd.Root().GenFishCompletion(cmd.OutOrStdout(), true); err != nil { - return err - } - case "powershell": - if err := cmd.Root().GenPowerShellCompletionWithDesc(cmd.OutOrStdout()); err != nil { - return err - } - default: - return fmt.Errorf("%w: %s", ErrInvalidShell, shell) - } - return nil -} diff --git a/cmd/options.go b/cmd/options.go deleted file mode 100644 index 92d90be..0000000 --- a/cmd/options.go +++ /dev/null @@ -1,22 +0,0 @@ -package cmd - -import "github.com/spf13/cobra" - -type Option func(cmd *cobra.Command) - -const ( - VersionKey = "version" - CommitKey = "commit" -) - -func WithVersion(version string) Option { - return func(cmd *cobra.Command) { - if cmd.Annotations == nil { - cmd.Annotations = make(map[string]string) - } - cmd.Annotations[VersionKey] = version - cmd.Version, cmd.Annotations[CommitKey] = buildVersion(version) - cmd.SetVersionTemplate("CastSponsorSkip {{ .Version }}\n") - cmd.InitDefaultVersionFlag() - } -} diff --git a/cmd/version.go b/cmd/version.go deleted file mode 100644 index ce57a5c..0000000 --- a/cmd/version.go +++ /dev/null @@ -1,31 +0,0 @@ -package cmd - -import "runtime/debug" - -func buildVersion(version string) (string, string) { - var commit string - var modified bool - if info, ok := debug.ReadBuildInfo(); ok { - for _, setting := range info.Settings { - switch setting.Key { - case "vcs.revision": - commit = setting.Value - case "vcs.modified": - if setting.Value == "true" { - modified = true - } - } - } - } - - if commit != "" { - if len(commit) > 8 { - commit = commit[:8] - } - if modified { - commit = "*" + commit - } - version += " (" + commit + ")" - } - return version, commit -} diff --git a/docs/castsponsorskip.md b/docs/castsponsorskip.md index 154026d..6414200 100644 --- a/docs/castsponsorskip.md +++ b/docs/castsponsorskip.md @@ -25,7 +25,7 @@ castsponsorskip [flags] ``` --action-types strings SponsorBlock action types to handle. Shorter segments that overlap with content can be muted instead of skipped. (default [skip,mute]) -c, --categories strings Comma-separated list of SponsorBlock categories to skip (default [sponsor]) - --completion string Output command-line completion code for the specified shell. Can be 'bash', 'zsh', 'fish', or 'powershell'. + --completion string Generate the autocompletion script for the specified shell (one of bash, zsh, fish, powershell) --config string Config file path --devices strings Comma-separated list of device addresses. This will disable discovery and is not recommended unless discovery fails --discover-interval duration Interval to restart the DNS discovery client (default 5m0s) diff --git a/go.mod b/go.mod index c3b1690..0189f63 100644 --- a/go.mod +++ b/go.mod @@ -3,6 +3,7 @@ module gabe565.com/castsponsorskip go 1.23.4 require ( + gabe565.com/utils v0.0.0-20241213205714-152b8de1d3fe github.com/buger/jsonparser v1.1.1 github.com/dmarkham/enumer v1.5.10 github.com/jedib0t/go-pretty/v6 v6.6.5 @@ -13,7 +14,6 @@ require ( github.com/knadh/koanf/providers/structs v0.1.0 github.com/knadh/koanf/v2 v2.1.2 github.com/lmittmann/tint v1.0.5 - github.com/mattn/go-isatty v0.0.20 github.com/spf13/cobra v1.8.1 github.com/spf13/pflag v1.0.5 github.com/stretchr/testify v1.10.0 @@ -44,6 +44,7 @@ require ( github.com/inconshreveable/mousetrap v1.1.0 // indirect github.com/knadh/koanf/maps v0.1.1 // indirect github.com/kr/pretty v0.3.1 // indirect + github.com/mattn/go-isatty v0.0.20 // indirect github.com/mattn/go-runewidth v0.0.16 // indirect github.com/miekg/dns v1.1.62 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect diff --git a/go.sum b/go.sum index ea94786..9acf53e 100644 --- a/go.sum +++ b/go.sum @@ -4,6 +4,8 @@ cloud.google.com/go/auth/oauth2adapt v0.2.6 h1:V6a6XDu2lTwPZWOawrAa9HUK+DB2zfJyT cloud.google.com/go/auth/oauth2adapt v0.2.6/go.mod h1:AlmsELtlEBnaNTL7jCj8VQFLy6mbZv0s4Q7NGBeQ5E8= cloud.google.com/go/compute/metadata v0.5.2 h1:UxK4uu/Tn+I3p2dYWTfiX4wva7aYlKixAHn3fyqngqo= cloud.google.com/go/compute/metadata v0.5.2/go.mod h1:C66sj2AluDcIqakBq/M8lw8/ybHgOZqin2obFxa/E5k= +gabe565.com/utils v0.0.0-20241213205714-152b8de1d3fe h1:/QXzT2uIjZJBhJ8eADVxXMPZT716LpRVLj4TeZm+Qz0= +gabe565.com/utils v0.0.0-20241213205714-152b8de1d3fe/go.mod h1:6FZP8OeF0k3hTsPClyDGEILcBg3hvGcSp4u4pmjMusY= github.com/buger/jsonparser v1.1.1 h1:2PnMjfWD7wBILjqQbt530v576A/cAbQvEW9gGIpYMUs= github.com/buger/jsonparser v1.1.1/go.mod h1:6RYKKt7H4d4+iWqouImQ9R2FZql3VbhNgx27UK13J/0= github.com/cenkalti/backoff v2.2.1+incompatible h1:tNowT99t7UNflLxfYYSlKYsBpXdEet03Pg2g16Swow4= @@ -11,6 +13,8 @@ github.com/cenkalti/backoff v2.2.1+incompatible/go.mod h1:90ReRw6GdpyfrHakVjL/QH github.com/cpuguy83/go-md2man/v2 v2.0.4 h1:wfIWP927BUkWJb2NmU/kNDYIBTh/ziUX91+lVfRxZq4= github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o= github.com/creack/pty v1.1.9/go.mod h1:oKZEueFk5CKHvIhNR5MUki03XCEU+Q6VDXinZuGJ33E= +github.com/creack/pty v1.1.24 h1:bJrF4RRfyJnbTJqzRLHzcGaZK1NeM5kTC9jGgovnR1s= +github.com/creack/pty v1.1.24/go.mod h1:08sCNb52WyoAwi2QDyzUCTgcvVFhUzewun7wtTfvcwE= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= diff --git a/internal/config/completions.go b/internal/config/completions.go index 189db8b..2cdf3bc 100644 --- a/internal/config/completions.go +++ b/internal/config/completions.go @@ -3,44 +3,40 @@ package config import ( "context" "encoding/json" - "errors" "net" "net/http" "strings" "time" "gabe565.com/castsponsorskip/internal/config/names" + "gabe565.com/utils/must" "github.com/spf13/cobra" ) func RegisterCompletions(cmd *cobra.Command) { - if err := errors.Join( - cmd.RegisterFlagCompletionFunc(names.FlagLogLevel, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { - return []string{"debug", "info", "warn", "error", "none"}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveKeepOrder - }), - cmd.RegisterFlagCompletionFunc(names.FlagNetworkInterface, completeNetworkInterface), - cmd.RegisterFlagCompletionFunc(names.FlagDiscoverInterval, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { - return []string{"5m", "10m", "15m"}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveKeepOrder - }), - cmd.RegisterFlagCompletionFunc(names.FlagPausedInterval, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { - return []string{"1m", "2m", "5m", "10m", "30m", "1h"}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveKeepOrder - }), - cmd.RegisterFlagCompletionFunc(names.FlagPlayingInterval, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { - return []string{"1s", "2s"}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveKeepOrder - }), - cmd.RegisterFlagCompletionFunc(names.FlagSkipDelay, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { - return []string{"500ms", "1s", "2s", "3s", "5s", "10s"}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveKeepOrder - }), - cmd.RegisterFlagCompletionFunc(names.FlagIgnoreSegmentDuration, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { - return []string{"30s", "1m", "2m", "5m"}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveKeepOrder - }), - cmd.RegisterFlagCompletionFunc(names.FlagCategories, completeCategories), - cmd.RegisterFlagCompletionFunc(names.FlagActionTypes, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { - return []string{"skip", "mute"}, cobra.ShellCompDirectiveNoFileComp - }), - ); err != nil { - panic(err) - } + must.Must(cmd.RegisterFlagCompletionFunc(names.FlagLogLevel, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { + return []string{"debug", "info", "warn", "error", "none"}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveKeepOrder + })) + must.Must(cmd.RegisterFlagCompletionFunc(names.FlagNetworkInterface, completeNetworkInterface)) + must.Must(cmd.RegisterFlagCompletionFunc(names.FlagDiscoverInterval, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { + return []string{"5m", "10m", "15m"}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveKeepOrder + })) + must.Must(cmd.RegisterFlagCompletionFunc(names.FlagPausedInterval, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { + return []string{"1m", "2m", "5m", "10m", "30m", "1h"}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveKeepOrder + })) + must.Must(cmd.RegisterFlagCompletionFunc(names.FlagPlayingInterval, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { + return []string{"1s", "2s"}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveKeepOrder + })) + must.Must(cmd.RegisterFlagCompletionFunc(names.FlagSkipDelay, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { + return []string{"500ms", "1s", "2s", "3s", "5s", "10s"}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveKeepOrder + })) + must.Must(cmd.RegisterFlagCompletionFunc(names.FlagIgnoreSegmentDuration, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { + return []string{"30s", "1m", "2m", "5m"}, cobra.ShellCompDirectiveNoFileComp | cobra.ShellCompDirectiveKeepOrder + })) + must.Must(cmd.RegisterFlagCompletionFunc(names.FlagCategories, completeCategories)) + must.Must(cmd.RegisterFlagCompletionFunc(names.FlagActionTypes, func(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { + return []string{"skip", "mute"}, cobra.ShellCompDirectiveNoFileComp + })) } func completeNetworkInterface(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) { diff --git a/internal/config/config.go b/internal/config/config.go index b04e87e..e35d1fe 100644 --- a/internal/config/config.go +++ b/internal/config/config.go @@ -7,6 +7,8 @@ import ( "time" "gabe565.com/castsponsorskip/internal/config/names" + "gabe565.com/utils/cobrax" + "gabe565.com/utils/must" "github.com/spf13/cobra" castdns "github.com/vishen/go-chromecast/dns" ) @@ -55,6 +57,7 @@ func New() *Config { func RegisterFlags(cmd *cobra.Command) { fs := cmd.Flags() c := New() + must.Must(cobrax.RegisterCompletionFlag(cmd)) fs.String(names.FlagConfig, "", "Config file path") fs.String(names.FlagLogLevel, c.LogLevel, "Log level (one of: debug, info, warn, error, none)") diff --git a/internal/config/load.go b/internal/config/load.go index 5d29a71..c560db8 100644 --- a/internal/config/load.go +++ b/internal/config/load.go @@ -12,6 +12,7 @@ import ( "gabe565.com/castsponsorskip/internal/config/names" "gabe565.com/castsponsorskip/internal/config/sponsorblockcast" + "gabe565.com/utils/must" "github.com/knadh/koanf/parsers/yaml" "github.com/knadh/koanf/providers/env" "github.com/knadh/koanf/providers/file" @@ -38,9 +39,7 @@ func Load(cmd *cobra.Command) (*Config, error) { // Find config file cfgFiles := make([]string, 0, 4) var fileRequired bool - if cfgFile, err := cmd.Flags().GetString(names.FlagConfig); err != nil { - panic(err) - } else if cfgFile != "" { + if cfgFile := must.Must2(cmd.Flags().GetString(names.FlagConfig)); cfgFile != "" { cfgFiles = append(cfgFiles, cfgFile) fileRequired = true } else { diff --git a/internal/config/log.go b/internal/config/log.go index ee153b4..894b427 100644 --- a/internal/config/log.go +++ b/internal/config/log.go @@ -3,11 +3,10 @@ package config import ( "io" "log/slog" - "os" "time" + "gabe565.com/utils/termx" "github.com/lmittmann/tint" - "github.com/mattn/go-isatty" ) //go:generate go run github.com/dmarkham/enumer -type LogFormat -trimprefix Format -transform lower -text @@ -51,9 +50,7 @@ func InitLog(w io.Writer, level slog.Level, format LogFormat) { var color bool switch format { case FormatAuto: - if f, ok := w.(*os.File); ok { - color = isatty.IsTerminal(f.Fd()) || isatty.IsCygwinTerminal(f.Fd()) - } + color = termx.IsColor(w) case FormatColor: color = true } diff --git a/internal/generate/docs/main.go b/internal/generate/docs/main.go index 9e32f9d..54ff14a 100644 --- a/internal/generate/docs/main.go +++ b/internal/generate/docs/main.go @@ -13,6 +13,7 @@ import ( "gabe565.com/castsponsorskip/cmd" "gabe565.com/castsponsorskip/internal/config" "gabe565.com/castsponsorskip/internal/config/names" + "gabe565.com/utils/cobrax" "github.com/jedib0t/go-pretty/v6/table" "github.com/spf13/cobra" "github.com/spf13/cobra/doc" @@ -32,7 +33,7 @@ func main() { os.Exit(1) } - root := cmd.New(cmd.WithVersion("beta")) + root := cmd.New(cobrax.WithVersion("beta")) if err := errors.Join( generateFlagDoc(root, output), diff --git a/main.go b/main.go index 29d23c1..a738c41 100644 --- a/main.go +++ b/main.go @@ -5,12 +5,13 @@ import ( "os" "gabe565.com/castsponsorskip/cmd" + "gabe565.com/utils/cobrax" ) var version = "beta" func main() { - rootCmd := cmd.New(cmd.WithVersion(version)) + rootCmd := cmd.New(cobrax.WithVersion(version)) if err := rootCmd.Execute(); err != nil { slog.Error(err.Error()) os.Exit(1)