Initial commit
This commit is contained in:
57
cmd/clear.go
Normal file
57
cmd/clear.go
Normal file
@@ -0,0 +1,57 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/cobra"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/app"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/color"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/filesystem"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/icon"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/util"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/where"
|
||||
)
|
||||
|
||||
type clearTarget struct {
|
||||
name string
|
||||
clear func() error
|
||||
}
|
||||
|
||||
// Specify what can be cleared
|
||||
var clearTargets = []clearTarget{
|
||||
{"cache", func() error {
|
||||
return filesystem.Api().RemoveAll(where.Cache())
|
||||
}},
|
||||
{"logs", func() error {
|
||||
return filesystem.Api().RemoveAll(where.Logs())
|
||||
}},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(clearCmd)
|
||||
for _, n := range clearTargets {
|
||||
clearCmd.Flags().BoolP(n.name, string(n.name[0]), false, "clear "+n.name)
|
||||
}
|
||||
}
|
||||
|
||||
var clearCmd = &cobra.Command{
|
||||
Use: "clear",
|
||||
Short: "Clears sidelined files produced by the " + app.Name,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
successStyle := lipgloss.NewStyle().Foreground(color.Green).Render
|
||||
var didSomething bool
|
||||
for _, n := range clearTargets {
|
||||
if lo.Must(cmd.Flags().GetBool(n.name)) {
|
||||
handleErr(n.clear())
|
||||
fmt.Printf("%s %s cleared\n", successStyle(icon.Check), util.Capitalize(n.name))
|
||||
didSomething = true
|
||||
}
|
||||
}
|
||||
|
||||
if !didSomething {
|
||||
_ = cmd.Help()
|
||||
}
|
||||
},
|
||||
}
|
||||
284
cmd/config.go
Normal file
284
cmd/config.go
Normal file
@@ -0,0 +1,284 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"os"
|
||||
"path/filepath"
|
||||
"slices"
|
||||
"sort"
|
||||
"strconv"
|
||||
"unsafe"
|
||||
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/style"
|
||||
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/app"
|
||||
|
||||
levenshtein "github.com/ka-weihe/fast-levenshtein"
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/cobra"
|
||||
"github.com/spf13/viper"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/color"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/config"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/filesystem"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/icon"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/where"
|
||||
)
|
||||
|
||||
// errUnknownKey will generate error for key that was not found and will provide a hint
|
||||
func errUnknownKey(key string) error {
|
||||
closest := lo.MinBy(lo.Keys(config.Default), func(a string, b string) bool {
|
||||
return levenshtein.Distance(key, a) < levenshtein.Distance(key, b)
|
||||
})
|
||||
msg := fmt.Sprintf(
|
||||
"unknown key %s, did you mean %s?",
|
||||
lipgloss.NewStyle().Foreground(color.Red).Render(key),
|
||||
lipgloss.NewStyle().Foreground(color.Yellow).Render(closest),
|
||||
)
|
||||
|
||||
return errors.New(msg)
|
||||
}
|
||||
|
||||
func completionConfigKeys(_ *cobra.Command, _ []string, _ string) ([]string, cobra.ShellCompDirective) {
|
||||
return lo.Keys(config.Default), cobra.ShellCompDirectiveNoFileComp
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(configCmd)
|
||||
}
|
||||
|
||||
var configCmd = &cobra.Command{
|
||||
Use: "config",
|
||||
Short: "Various config commands",
|
||||
}
|
||||
|
||||
func init() {
|
||||
configCmd.AddCommand(configInfoCmd)
|
||||
configInfoCmd.Flags().StringP("key", "k", "", "The key to get the value for")
|
||||
_ = configInfoCmd.RegisterFlagCompletionFunc("key", completionConfigKeys)
|
||||
}
|
||||
|
||||
var configInfoCmd = &cobra.Command{
|
||||
Use: "info",
|
||||
Short: "Show the info for each config field with description",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var (
|
||||
key = lo.Must(cmd.Flags().GetString("key"))
|
||||
fields = lo.Values(config.Default)
|
||||
)
|
||||
|
||||
if key != "" {
|
||||
if field, ok := config.Default[key]; ok {
|
||||
fields = []*config.Field{field}
|
||||
} else {
|
||||
handleErr(errUnknownKey(key))
|
||||
}
|
||||
}
|
||||
|
||||
sort.Slice(fields, func(i, j int) bool {
|
||||
return fields[i].Key < fields[j].Key
|
||||
})
|
||||
|
||||
for i, field := range fields {
|
||||
fmt.Println(field.Pretty())
|
||||
|
||||
if i < len(fields)-1 {
|
||||
fmt.Println()
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
configCmd.AddCommand(configSetCmd)
|
||||
configSetCmd.Flags().StringP("key", "k", "", "The key to set the value for")
|
||||
lo.Must0(configSetCmd.MarkFlagRequired("key"))
|
||||
_ = configSetCmd.RegisterFlagCompletionFunc("key", completionConfigKeys)
|
||||
|
||||
configSetCmd.Flags().StringP("value", "v", "", "The value to set. Leave empty to use default")
|
||||
}
|
||||
|
||||
var configSetCmd = &cobra.Command{
|
||||
Use: "set",
|
||||
Short: "Set a config value",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var (
|
||||
key = lo.Must(cmd.Flags().GetString("key"))
|
||||
value = lo.Must(cmd.Flags().GetString("value"))
|
||||
)
|
||||
|
||||
if _, ok := config.Default[key]; !ok {
|
||||
handleErr(errUnknownKey(key))
|
||||
}
|
||||
|
||||
var v any
|
||||
|
||||
if lo.IsEmpty(value) {
|
||||
v = config.Default[key].DefaultValue
|
||||
goto write
|
||||
}
|
||||
|
||||
switch config.Default[key].DefaultValue.(type) {
|
||||
case string:
|
||||
v = value
|
||||
case int:
|
||||
parsedInt, err := strconv.ParseInt(value, 10, 64)
|
||||
if err != nil {
|
||||
handleErr(fmt.Errorf("invalid integer value: %s", value))
|
||||
}
|
||||
|
||||
v = int(parsedInt)
|
||||
case bool:
|
||||
parsedBool, err := strconv.ParseBool(value)
|
||||
if err != nil {
|
||||
handleErr(fmt.Errorf("invalid boolean value: %s", value))
|
||||
}
|
||||
|
||||
v = parsedBool
|
||||
}
|
||||
|
||||
write:
|
||||
viper.Set(key, v)
|
||||
switch err := viper.WriteConfig(); err.(type) {
|
||||
case viper.ConfigFileNotFoundError:
|
||||
handleErr(viper.SafeWriteConfig())
|
||||
default:
|
||||
handleErr(err)
|
||||
}
|
||||
|
||||
fmt.Printf(
|
||||
"%s set %s to %s\n",
|
||||
style.Success(icon.Check),
|
||||
lipgloss.NewStyle().Foreground(color.Purple).Render(key),
|
||||
lipgloss.NewStyle().Foreground(color.Yellow).Render(fmt.Sprint(v)),
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
configCmd.AddCommand(configGetCmd)
|
||||
configGetCmd.Flags().StringP("key", "k", "", "The key to get the value for")
|
||||
lo.Must0(configGetCmd.MarkFlagRequired("key"))
|
||||
_ = configGetCmd.RegisterFlagCompletionFunc("key", completionConfigKeys)
|
||||
}
|
||||
|
||||
var configGetCmd = &cobra.Command{
|
||||
Use: "get",
|
||||
Short: "Get a config value",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var (
|
||||
key = lo.Must(cmd.Flags().GetString("key"))
|
||||
)
|
||||
|
||||
if _, ok := config.Default[key]; !ok {
|
||||
handleErr(errUnknownKey(key))
|
||||
}
|
||||
|
||||
fmt.Println(viper.Get(key))
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
configCmd.AddCommand(configEnvCmd)
|
||||
}
|
||||
|
||||
func fastBoolConv(b bool) int {
|
||||
return int(*(*byte)(unsafe.Pointer(&b)))
|
||||
}
|
||||
|
||||
var configEnvCmd = &cobra.Command{
|
||||
Use: "env",
|
||||
Short: "Show the env for each config field",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
fields := lo.Values(config.Default)
|
||||
fields = append(fields, &config.Field{Key: where.EnvConfigPath})
|
||||
|
||||
slices.SortStableFunc(fields, func(a *config.Field, b *config.Field) int {
|
||||
return fastBoolConv(a.Key < b.Key)
|
||||
})
|
||||
|
||||
for _, field := range fields {
|
||||
envValue, isSet := os.LookupEnv(field.Env())
|
||||
|
||||
var value string
|
||||
|
||||
if isSet {
|
||||
value = envValue
|
||||
} else {
|
||||
value = lipgloss.NewStyle().Faint(true).Render("unset")
|
||||
}
|
||||
|
||||
_, err := fmt.Fprintf(cmd.OutOrStdout(), "%s=%s\n", field.Env(), value)
|
||||
handleErr(err)
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
configCmd.AddCommand(configWriteCmd)
|
||||
configWriteCmd.Flags().BoolP("force", "f", false, "Force overwrite of existing config file")
|
||||
}
|
||||
|
||||
var configWriteCmd = &cobra.Command{
|
||||
Use: "write",
|
||||
Short: "Write current config to the file",
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
var (
|
||||
force = lo.Must(cmd.Flags().GetBool("force"))
|
||||
configFilePath = filepath.Join(
|
||||
where.Config(),
|
||||
fmt.Sprintf("%s.%s", app.Name, config.ConfigFormat),
|
||||
)
|
||||
)
|
||||
|
||||
if force {
|
||||
exists, err := filesystem.Api().Exists(configFilePath)
|
||||
handleErr(err)
|
||||
|
||||
if exists {
|
||||
err := filesystem.Api().Remove(configFilePath)
|
||||
handleErr(err)
|
||||
}
|
||||
}
|
||||
|
||||
handleErr(viper.SafeWriteConfig())
|
||||
fmt.Printf(
|
||||
"%s wrote config to %s\n",
|
||||
style.Success(icon.Check),
|
||||
configFilePath,
|
||||
)
|
||||
},
|
||||
}
|
||||
|
||||
func init() {
|
||||
configCmd.AddCommand(configDeleteCmd)
|
||||
}
|
||||
|
||||
var configDeleteCmd = &cobra.Command{
|
||||
Use: "delete",
|
||||
Short: "Delete the config file",
|
||||
Aliases: []string{"remove"},
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
configFilePath := filepath.Join(where.Config(), fmt.Sprintf("%s.%s", app.Name, config.ConfigFormat))
|
||||
|
||||
exists, err := filesystem.Api().Exists(configFilePath)
|
||||
handleErr(err)
|
||||
|
||||
if !exists {
|
||||
fmt.Printf(
|
||||
"%s nothing to delete\n",
|
||||
style.Success(icon.Check),
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
err = filesystem.Api().Remove(configFilePath)
|
||||
|
||||
handleErr(err)
|
||||
fmt.Printf(
|
||||
"%s deleted config\n",
|
||||
style.Success(icon.Check),
|
||||
)
|
||||
},
|
||||
}
|
||||
75
cmd/root.go
Normal file
75
cmd/root.go
Normal file
@@ -0,0 +1,75 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/charmbracelet/log"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/style"
|
||||
"os"
|
||||
"strings"
|
||||
|
||||
cc "github.com/ivanpirog/coloredcobra"
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/cobra"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/app"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/filesystem"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/icon"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/where"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.Flags().BoolP("version", "v", false, app.Name+" version")
|
||||
}
|
||||
|
||||
// rootCmd represents the base command when called without any subcommands
|
||||
var rootCmd = &cobra.Command{
|
||||
Use: strings.ToLower(app.Name),
|
||||
Short: app.DescriptionShort,
|
||||
Long: app.DescriptionLong,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if lo.Must(cmd.Flags().GetBool("version")) {
|
||||
versionCmd.Run(versionCmd, args)
|
||||
} else {
|
||||
_ = cmd.Help()
|
||||
}
|
||||
},
|
||||
}
|
||||
|
||||
func Execute() {
|
||||
// Setup colored cobra
|
||||
cc.Init(&cc.Config{
|
||||
RootCmd: rootCmd,
|
||||
Headings: cc.HiCyan + cc.Bold + cc.Underline,
|
||||
Commands: cc.HiYellow + cc.Bold,
|
||||
Example: cc.Italic,
|
||||
ExecName: cc.Bold,
|
||||
Flags: cc.Bold,
|
||||
FlagsDataType: cc.Italic + cc.HiBlue,
|
||||
NoExtraNewlines: true,
|
||||
NoBottomNewline: true,
|
||||
})
|
||||
|
||||
// Clears temp files on each run.
|
||||
// It should not affect startup time since it's being run as goroutine.
|
||||
go func() {
|
||||
_ = filesystem.Api().RemoveAll(where.Temp())
|
||||
}()
|
||||
|
||||
_ = rootCmd.Execute()
|
||||
}
|
||||
|
||||
// handleErr will stop program execution and logger error to the stderr
|
||||
// if err is not nil
|
||||
func handleErr(err error) {
|
||||
if err == nil {
|
||||
return
|
||||
}
|
||||
|
||||
log.Error(err)
|
||||
_, _ = fmt.Fprintf(
|
||||
os.Stderr,
|
||||
"%s %s\n",
|
||||
style.Failure(icon.Cross),
|
||||
strings.Trim(err.Error(), " \n"),
|
||||
)
|
||||
os.Exit(1)
|
||||
}
|
||||
58
cmd/version.go
Normal file
58
cmd/version.go
Normal file
@@ -0,0 +1,58 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"html/template"
|
||||
"runtime"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/app"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/color"
|
||||
|
||||
"github.com/spf13/cobra"
|
||||
)
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(versionCmd)
|
||||
|
||||
versionCmd.Flags().BoolP("short", "s", false, "print the version number only")
|
||||
}
|
||||
|
||||
var versionCmd = &cobra.Command{
|
||||
Use: "version",
|
||||
Short: "Print the version number of the " + app.Name,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
if lo.Must(cmd.Flags().GetBool("short")) {
|
||||
_, err := cmd.OutOrStdout().Write([]byte(app.Version + "\n"))
|
||||
handleErr(err)
|
||||
return
|
||||
}
|
||||
|
||||
versionInfo := struct {
|
||||
Version string
|
||||
OS string
|
||||
Arch string
|
||||
App string
|
||||
Compiler string
|
||||
}{
|
||||
Version: app.Version,
|
||||
App: app.Name,
|
||||
OS: runtime.GOOS,
|
||||
Arch: runtime.GOARCH,
|
||||
Compiler: runtime.Compiler,
|
||||
}
|
||||
|
||||
t, err := template.New("version").Funcs(map[string]any{
|
||||
"faint": lipgloss.NewStyle().Faint(true).Render,
|
||||
"bold": lipgloss.NewStyle().Bold(true).Render,
|
||||
"magenta": lipgloss.NewStyle().Foreground(color.Purple).Render,
|
||||
}).Parse(`{{ magenta "▇▇▇" }} {{ magenta .App }}
|
||||
|
||||
{{ faint "Version" }} {{ bold .Version }}
|
||||
{{ faint "Platform" }} {{ bold .OS }}/{{ bold .Arch }}
|
||||
{{ faint "Compiler" }} {{ bold .Compiler }}
|
||||
`)
|
||||
handleErr(err)
|
||||
handleErr(t.Execute(cmd.OutOrStdout(), versionInfo))
|
||||
},
|
||||
}
|
||||
68
cmd/where.go
Normal file
68
cmd/where.go
Normal file
@@ -0,0 +1,68 @@
|
||||
package cmd
|
||||
|
||||
import (
|
||||
"github.com/charmbracelet/lipgloss"
|
||||
"os"
|
||||
|
||||
"github.com/samber/lo"
|
||||
"github.com/spf13/cobra"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/app"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/color"
|
||||
"hub.cybercinch.nz/guisea/go-template/internal/where"
|
||||
)
|
||||
|
||||
type whereTarget struct {
|
||||
name string
|
||||
where func() string
|
||||
|
||||
argShort, argLong string
|
||||
}
|
||||
|
||||
// Specify what paths to show
|
||||
var wherePaths = []whereTarget{
|
||||
{"Config", where.Config, "c", "config"},
|
||||
{"Logs", where.Logs, "l", "logs"},
|
||||
}
|
||||
|
||||
func init() {
|
||||
rootCmd.AddCommand(whereCmd)
|
||||
|
||||
for _, n := range wherePaths {
|
||||
if n.argShort != "" {
|
||||
whereCmd.Flags().BoolP(n.argLong, n.argShort, false, n.name+" path")
|
||||
} else {
|
||||
whereCmd.Flags().Bool(n.argLong, false, n.name+" path")
|
||||
}
|
||||
}
|
||||
|
||||
whereCmd.MarkFlagsMutuallyExclusive(lo.Map(wherePaths, func(t whereTarget, _ int) string {
|
||||
return t.argLong
|
||||
})...)
|
||||
|
||||
whereCmd.SetOut(os.Stdout)
|
||||
}
|
||||
|
||||
var whereCmd = &cobra.Command{
|
||||
Use: "where",
|
||||
Short: "Show the paths for a files related to the " + app.Name,
|
||||
Run: func(cmd *cobra.Command, args []string) {
|
||||
headerStyle := lipgloss.NewStyle().Foreground(color.HiPurple).Bold(true).Render
|
||||
argStyle := lipgloss.NewStyle().Foreground(color.Yellow).Render
|
||||
|
||||
for _, n := range wherePaths {
|
||||
if lo.Must(cmd.Flags().GetBool(n.argLong)) {
|
||||
cmd.Println(n.where())
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
for i, n := range wherePaths {
|
||||
cmd.Printf("%s %s\n", headerStyle(n.name+"?"), argStyle("--"+n.argLong))
|
||||
cmd.Println(n.where())
|
||||
|
||||
if i < len(wherePaths)-1 {
|
||||
cmd.Println()
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Reference in New Issue
Block a user