Initial commit
This commit is contained in:
28
.github/workflows/build.yml
vendored
Normal file
28
.github/workflows/build.yml
vendored
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
name: CI
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- "**"
|
||||||
|
tags:
|
||||||
|
- "!**"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
- uses: actions/setup-go@v5
|
||||||
|
with:
|
||||||
|
go-version: '1.22'
|
||||||
|
- name: Copy go-semantic-release plugins and release
|
||||||
|
run: |
|
||||||
|
/usr/local/bin/semantic-release --version-file \
|
||||||
|
--changelog .generated-go-semantic-release-changelog.md \
|
||||||
|
--hooks goreleaser \
|
||||||
|
--provider=gitea
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.G_TOKEN }}
|
||||||
|
GITEA_HOST: ${{ secrets.G_SERVER_URL}}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
115
.gitignore
vendored
Normal file
115
.gitignore
vendored
Normal file
@@ -0,0 +1,115 @@
|
|||||||
|
#########
|
||||||
|
# macOS #
|
||||||
|
#########
|
||||||
|
|
||||||
|
# General
|
||||||
|
.DS_Store
|
||||||
|
.AppleDouble
|
||||||
|
.LSOverride
|
||||||
|
|
||||||
|
# Thumbnails
|
||||||
|
._*
|
||||||
|
|
||||||
|
# Files that might appear in the root of a volume
|
||||||
|
.DocumentRevisions-V100
|
||||||
|
.fseventsd
|
||||||
|
.Spotlight-V100
|
||||||
|
.TemporaryItems
|
||||||
|
.Trashes
|
||||||
|
.VolumeIcon.icns
|
||||||
|
.com.apple.timemachine.donotpresent
|
||||||
|
|
||||||
|
# Directories potentially created on remote AFP share
|
||||||
|
.AppleDB
|
||||||
|
.AppleDesktop
|
||||||
|
Network Trash Folder
|
||||||
|
Temporary Items
|
||||||
|
.apdisk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
######
|
||||||
|
# Go #
|
||||||
|
######
|
||||||
|
|
||||||
|
# If you prefer the allow list template instead of the deny list, see community template:
|
||||||
|
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore
|
||||||
|
#
|
||||||
|
# Binaries for programs and plugins
|
||||||
|
bin/
|
||||||
|
*.exe
|
||||||
|
*.exe~
|
||||||
|
*.dll
|
||||||
|
*.so
|
||||||
|
*.dylib
|
||||||
|
|
||||||
|
# Test binary, built with `go test -c`
|
||||||
|
*.test
|
||||||
|
|
||||||
|
# Output of the go coverage tool, specifically when used with LiteIDE
|
||||||
|
*.out
|
||||||
|
|
||||||
|
# Dependency directories (remove the comment below to include it)
|
||||||
|
# vendor/
|
||||||
|
|
||||||
|
# Go workspace file
|
||||||
|
go.work
|
||||||
|
|
||||||
|
###########
|
||||||
|
# Windows #
|
||||||
|
###########
|
||||||
|
|
||||||
|
# Windows thumbnail cache files
|
||||||
|
Thumbs.db
|
||||||
|
Thumbs.db:encryptable
|
||||||
|
ehthumbs.db
|
||||||
|
ehthumbs_vista.db
|
||||||
|
|
||||||
|
# Dump file
|
||||||
|
*.stackdump
|
||||||
|
|
||||||
|
# Folder config file
|
||||||
|
[Dd]esktop.ini
|
||||||
|
|
||||||
|
# Recycle Bin used on file shares
|
||||||
|
$RECYCLE.BIN/
|
||||||
|
|
||||||
|
# Windows Installer files
|
||||||
|
*.cab
|
||||||
|
*.msi
|
||||||
|
*.msix
|
||||||
|
*.msm
|
||||||
|
*.msp
|
||||||
|
|
||||||
|
# Windows shortcuts
|
||||||
|
*.lnk
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
#########
|
||||||
|
# Linux #
|
||||||
|
#########
|
||||||
|
|
||||||
|
*~
|
||||||
|
|
||||||
|
# temporary files which can be created if a process still has a handle open of a deleted file
|
||||||
|
.fuse_hidden*
|
||||||
|
|
||||||
|
# KDE directory preferences
|
||||||
|
.directory
|
||||||
|
|
||||||
|
# Linux trash folder which might appear on any partition or disk
|
||||||
|
.Trash-*
|
||||||
|
|
||||||
|
# .nfs files are created when an open file is removed but is still being accessed
|
||||||
|
.nfs*
|
||||||
|
|
||||||
|
#############
|
||||||
|
# JetBrains #
|
||||||
|
#############
|
||||||
|
|
||||||
|
.idea
|
||||||
25
.goreleaser.yaml
Normal file
25
.goreleaser.yaml
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
builds:
|
||||||
|
- env:
|
||||||
|
- CGO_ENABLED=0
|
||||||
|
targets:
|
||||||
|
- linux_amd64
|
||||||
|
- linux_arm64
|
||||||
|
- darwin_amd64
|
||||||
|
- darwin_arm64
|
||||||
|
- linux_arm
|
||||||
|
- windows_amd64
|
||||||
|
main: main.go
|
||||||
|
ldflags:
|
||||||
|
- -extldflags '-static'
|
||||||
|
- -s -w -X hub.cybercinch.nz/guisea/go-template/internal/app/meta.Version={{.Version}}
|
||||||
|
|
||||||
|
gitea_urls:
|
||||||
|
api: https://hub.cybercinch.nz/api/v1
|
||||||
|
download: https://hub.cybercinch.nz
|
||||||
|
|
||||||
|
archives:
|
||||||
|
- format: binary
|
||||||
|
name_template: '{{ .Binary }}_v{{ .Version }}_{{ .Os }}_{{ .Arch }}'
|
||||||
|
|
||||||
|
checksum:
|
||||||
|
name_template: '{{ .ProjectName }}_v{{ .Version }}_checksums.txt'
|
||||||
13
.semrelrc
Normal file
13
.semrelrc
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"plugins": {
|
||||||
|
"provider": {
|
||||||
|
"name": "gitea"
|
||||||
|
},
|
||||||
|
"changelog-generator": {
|
||||||
|
"name": "default",
|
||||||
|
"options": {
|
||||||
|
"emojis": "true"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
85
README.md
Normal file
85
README.md
Normal file
@@ -0,0 +1,85 @@
|
|||||||
|
# Go CLI Project Template ☄️
|
||||||
|
|
||||||
|
Powerful template for Go CLI applications with advanced config management
|
||||||
|
|
||||||
|
<img width="912" alt="Screenshot 2022-10-05 at 11 14 32" src="https://user-images.githubusercontent.com/62389790/194013247-897697ee-4b32-4b5d-9667-462fcc45e161.png">
|
||||||
|
|
||||||
|
|
||||||
|
## Features
|
||||||
|
|
||||||
|
- Advanced config management with [viper](https://github.com/spf13/viper) and
|
||||||
|
useful config commands such as setting config values directly from CLI (like this `config set -k logs.write -v true`),
|
||||||
|
reading env variables and file-based configuration (either TOML or YAML). Also, configuration is self-documented, type `config info` to show every config field available with description for each.
|
||||||
|
|
||||||
|
- Cache & Temp files management with `clear` command
|
||||||
|
|
||||||
|
- Polished CLI experience with [cobra](https://github.com/spf13/cobra) + [coloredcobra](https://github.com/ivanpirog/coloredcobra) to make things look pretty
|
||||||
|
|
||||||
|
- [Afero](https://github.com/spf13/afero) filesystem for various fs utils, abstractions and in-memory fs for testing.
|
||||||
|
For example, instead of `os.Remove("file")` use `filesystem.Api().Remove("file")`
|
||||||
|
|
||||||
|
- Easy to use path management with `where` package
|
||||||
|
|
||||||
|
- Logging to file
|
||||||
|
|
||||||
|
- Icons!
|
||||||
|
|
||||||
|
- Predefined lipgloss colors
|
||||||
|
|
||||||
|
## How to use
|
||||||
|
|
||||||
|
Press this shiny green button on top
|
||||||
|
|
||||||
|
<img width="203" alt="Screenshot 2022-09-30 at 13 37 30" src="https://user-images.githubusercontent.com/62389790/193252456-42b966a7-2679-4868-bf25-d862524733ee.png">
|
||||||
|
|
||||||
|
Then you would probably want to rename go mod name from `hub.cybercinch.nz/guisea/go-template` to something else.
|
||||||
|
To do this you could use your IDE refactor features or run [just](https://github.com/casey/just) target.
|
||||||
|
|
||||||
|
```shell
|
||||||
|
just rename github.com/username/repo
|
||||||
|
```
|
||||||
|
|
||||||
|
This will prompt you to type a new name and will replace every occurence of the old go mod name with the new one.
|
||||||
|
|
||||||
|
## Further usage
|
||||||
|
|
||||||
|
### Changing name of the app
|
||||||
|
|
||||||
|
Change the value of the constant `Name` at [internal/app/meta.go](https://hub.cybercinch.nz/guisea/go-template/src/branch/main/internal/app/meta.go)
|
||||||
|
|
||||||
|
### Changing config file format from TOML from YAML
|
||||||
|
|
||||||
|
Change the value of the constant `ConfigFormat` at [internal/config/init.go](https://hub.cybercinch.nz/guisea/go-template/src/branch/main/internal/config/init.go)
|
||||||
|
|
||||||
|
### Declaring new config fields
|
||||||
|
|
||||||
|
Firstly, declare a field key name as a constant inside [internal/config/key/keys.go](https://hub.cybercinch.nz/guisea/go-template/src/branch/main/internal/config/key/keys.go)
|
||||||
|
|
||||||
|
Then put them inside [config/default.go](https://hub.cybercinch.nz/guisea/go-template/src/branch/main/internal/config/default.go) (take a predefined fields for logging as a reference)
|
||||||
|
|
||||||
|
For example
|
||||||
|
|
||||||
|
```go
|
||||||
|
// key/keys.go
|
||||||
|
|
||||||
|
const IconType = "icon.type"
|
||||||
|
```
|
||||||
|
|
||||||
|
```go
|
||||||
|
// config/default.go
|
||||||
|
|
||||||
|
{
|
||||||
|
constant.IconType, // config field key
|
||||||
|
"emoji", // default value
|
||||||
|
"What type of icons to use", // description
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Accessing config fields
|
||||||
|
|
||||||
|
For the example above it would be `viper.GetString(key.EmojiType)`. See [viper](https://github.com/spf13/viper) for more information
|
||||||
|
|
||||||
|
|
||||||
|
## Something is not clear?
|
||||||
|
|
||||||
|
Please, [open an issue](https://hub.cybercinch.nz/guisea/go-template/issues/new) so I could document it
|
||||||
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()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
}
|
||||||
48
go.mod
Normal file
48
go.mod
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
module hub.cybercinch.nz/guisea/go-template
|
||||||
|
|
||||||
|
go 1.22.1
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/charmbracelet/lipgloss v0.13.0
|
||||||
|
github.com/charmbracelet/log v0.4.0
|
||||||
|
github.com/ivanpirog/coloredcobra v1.0.1
|
||||||
|
github.com/ka-weihe/fast-levenshtein v0.0.0-20201227151214-4c99ee36a1ba
|
||||||
|
github.com/samber/lo v1.47.0
|
||||||
|
github.com/spf13/afero v1.11.0
|
||||||
|
github.com/spf13/cobra v1.8.1
|
||||||
|
github.com/spf13/viper v1.19.0
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 // indirect
|
||||||
|
github.com/charmbracelet/x/ansi v0.2.3 // indirect
|
||||||
|
github.com/go-logfmt/logfmt v0.6.0 // indirect
|
||||||
|
github.com/sagikazarmark/locafero v0.6.0 // indirect
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0 // indirect
|
||||||
|
github.com/sourcegraph/conc v0.3.0 // indirect
|
||||||
|
go.uber.org/multierr v1.11.0 // indirect
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/fatih/color v1.17.0 // indirect
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0 // indirect
|
||||||
|
github.com/hashicorp/hcl v1.0.0 // indirect
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 // indirect
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 // indirect
|
||||||
|
github.com/magiconair/properties v1.8.7 // indirect
|
||||||
|
github.com/mattn/go-colorable v0.1.13 // indirect
|
||||||
|
github.com/mattn/go-isatty v0.0.20 // indirect
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 // indirect
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 // indirect
|
||||||
|
github.com/muesli/termenv v0.15.2 // indirect
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.3 // indirect
|
||||||
|
github.com/rivo/uniseg v0.4.7 // indirect
|
||||||
|
github.com/spf13/cast v1.7.0 // indirect
|
||||||
|
github.com/spf13/pflag v1.0.5 // indirect
|
||||||
|
github.com/subosito/gotenv v1.6.0 // indirect
|
||||||
|
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948
|
||||||
|
golang.org/x/sys v0.24.0 // indirect
|
||||||
|
golang.org/x/text v0.17.0 // indirect
|
||||||
|
gopkg.in/ini.v1 v1.67.0 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
116
go.sum
Normal file
116
go.sum
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
github.com/agnivade/levenshtein v1.1.0 h1:n6qGwyHG61v3ABce1rPVZklEYRT8NFpCMrpZdBUbYGM=
|
||||||
|
github.com/agnivade/levenshtein v1.1.0/go.mod h1:veldBMzWxcCG2ZvUTKD2kJNRdCk5hVbJomOvKkmgYbo=
|
||||||
|
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0 h1:jfIu9sQUG6Ig+0+Ap1h4unLjW6YQJpKZVmUzxsD4E/Q=
|
||||||
|
github.com/arbovm/levenshtein v0.0.0-20160628152529-48b4e1c0c4d0/go.mod h1:t2tdKJDJF9BV14lnkjHmOQgcvEKgtqs5a1N3LNdJhGE=
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1 h1:HwpRHbFMcZLEVr42D4p7XBqjyuxQH5SMiErDT4WkJ2k=
|
||||||
|
github.com/aymanbagabas/go-osc52/v2 v2.0.1/go.mod h1:uYgXzlJ7ZpABp8OJ+exZzJJhRNQ2ASbcXHWsFqH8hp8=
|
||||||
|
github.com/charmbracelet/lipgloss v0.13.0 h1:4X3PPeoWEDCMvzDvGmTajSyYPcZM4+y8sCA/SsA3cjw=
|
||||||
|
github.com/charmbracelet/lipgloss v0.13.0/go.mod h1:nw4zy0SBX/F/eAO1cWdcvy6qnkDUxr8Lw7dvFrAIbbY=
|
||||||
|
github.com/charmbracelet/log v0.4.0 h1:G9bQAcx8rWA2T3pWvx7YtPTPwgqpk7D68BX21IRW8ZM=
|
||||||
|
github.com/charmbracelet/log v0.4.0/go.mod h1:63bXt/djrizTec0l11H20t8FDSvA4CRZJ1KH22MdptM=
|
||||||
|
github.com/charmbracelet/x/ansi v0.2.3 h1:VfFN0NUpcjBRd4DnKfRaIRo53KRgey/nhOoEqosGDEY=
|
||||||
|
github.com/charmbracelet/x/ansi v0.2.3/go.mod h1:dk73KoMTT5AX5BsX0KrqhsTqAnhZZoCBjs7dGWp4Ktw=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.1/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
github.com/cpuguy83/go-md2man/v2 v2.0.4/go.mod h1:tgQtvFlXSQOSOSIRvRPT7W67SCa46tRHOmNcaadrF8o=
|
||||||
|
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=
|
||||||
|
github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/dgryski/trifles v0.0.0-20200323201526-dd97f9abfb48/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
||||||
|
github.com/dgryski/trifles v0.0.0-20200830180326-aaf60a07f6a3 h1:JibukGTEjdN4VMX7YHmXQsLr/gPURUbetlH4E6KvHSU=
|
||||||
|
github.com/dgryski/trifles v0.0.0-20200830180326-aaf60a07f6a3/go.mod h1:if7Fbed8SFyPtHLHbg49SI7NAdJiC5WIA09pe59rfAA=
|
||||||
|
github.com/fatih/color v1.13.0/go.mod h1:kLAiJbzzSOZDVNGyDpeOxJ47H46qBXwg5ILebYFFOfk=
|
||||||
|
github.com/fatih/color v1.17.0 h1:GlRw1BRJxkpqUCBKzKOw098ed57fEsKeNjpTe3cSjK4=
|
||||||
|
github.com/fatih/color v1.17.0/go.mod h1:YZ7TlrGPkiz6ku9fK3TLD/pl3CpsiFyu8N92HLgmosI=
|
||||||
|
github.com/frankban/quicktest v1.14.6 h1:7Xjx+VpznH+oBnejlPUj8oUpdxnVs4f8XU8WnHkI4W8=
|
||||||
|
github.com/frankban/quicktest v1.14.6/go.mod h1:4ptaffx2x8+WTWXmUCuVU6aPUX1/Mz7zb5vbUoiM6w0=
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nosvA=
|
||||||
|
github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM=
|
||||||
|
github.com/go-logfmt/logfmt v0.6.0 h1:wGYYu3uicYdqXVgoYbvnkrPVXkuLM1p1ifugDMEdRi4=
|
||||||
|
github.com/go-logfmt/logfmt v0.6.0/go.mod h1:WYhtIu8zTZfxdn5+rREduYbwxfcBr/Vr6KEVveWlfTs=
|
||||||
|
github.com/google/go-cmp v0.6.0 h1:ofyhxvXcZhMsU5ulbFiLKl/XBFqE1GSq7atu8tAmTRI=
|
||||||
|
github.com/google/go-cmp v0.6.0/go.mod h1:17dUlkBOakJ0+DkrSSNjCkIjxS6bF9zb3elmeNGIjoY=
|
||||||
|
github.com/hashicorp/hcl v1.0.0 h1:0Anlzjpi4vEasTeNFn2mLJgTSwt0+6sfsiTG8qcWGx4=
|
||||||
|
github.com/hashicorp/hcl v1.0.0/go.mod h1:E5yfLk+7swimpb2L/Alb/PJmXilQ/rhwaUYs4T20WEQ=
|
||||||
|
github.com/inconshreveable/mousetrap v1.0.0/go.mod h1:PxqpIevigyE2G7u3NXJIT2ANytuPF1OarO4DADm73n8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0 h1:wN+x4NVGpMsO7ErUn/mUI3vEoE6Jt13X2s0bqwp9tc8=
|
||||||
|
github.com/inconshreveable/mousetrap v1.1.0/go.mod h1:vpF70FUmC8bwa3OWnCshd2FqLfsEA9PFc4w1p2J65bw=
|
||||||
|
github.com/ivanpirog/coloredcobra v1.0.1 h1:aURSdEmlR90/tSiWS0dMjdwOvCVUeYLfltLfbgNxrN4=
|
||||||
|
github.com/ivanpirog/coloredcobra v1.0.1/go.mod h1:iho4nEKcnwZFiniGSdcgdvRgZNjxm+h20acv8vqmN6Q=
|
||||||
|
github.com/ka-weihe/fast-levenshtein v0.0.0-20201227151214-4c99ee36a1ba h1:keZ4vJpYOVm6yrjLzZ6QgozbEBaT0GjfH30ihbO67+4=
|
||||||
|
github.com/ka-weihe/fast-levenshtein v0.0.0-20201227151214-4c99ee36a1ba/go.mod h1:kaXTPU4xitQT0rfT7/i9O9Gm8acSh3DXr0p4y3vKqiE=
|
||||||
|
github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE=
|
||||||
|
github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk=
|
||||||
|
github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
|
||||||
|
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0 h1:1nnpGOrhyZZuNyfu1QjKiUICQ74+3FNCN69Aj6K7nkY=
|
||||||
|
github.com/lucasb-eyer/go-colorful v1.2.0/go.mod h1:R4dSotOR9KMtayYi1e77YzuveK+i7ruzyGqttikkLy0=
|
||||||
|
github.com/magiconair/properties v1.8.7 h1:IeQXZAiQcpL9mgcAe1Nu6cX9LLw6ExEHKjN0VQdvPDY=
|
||||||
|
github.com/magiconair/properties v1.8.7/go.mod h1:Dhd985XPs7jluiymwWYZ0G4Z61jb3vdS329zhj2hYo0=
|
||||||
|
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
|
||||||
|
github.com/mattn/go-colorable v0.1.13 h1:fFA4WZxdEF4tXPZVKMLwD8oUnCTTo08duU7wxecdEvA=
|
||||||
|
github.com/mattn/go-colorable v0.1.13/go.mod h1:7S9/ev0klgBDR4GtXTXX8a3vIGJpMovkB8vQcUbaXHg=
|
||||||
|
github.com/mattn/go-isatty v0.0.12/go.mod h1:cbi8OIDigv2wuxKPP5vlRcQ1OAZbq2CE4Kysco4FUpU=
|
||||||
|
github.com/mattn/go-isatty v0.0.14/go.mod h1:7GGIvUiUoEMVVmxf/4nioHXj79iQHKdU27kJ6hsGG94=
|
||||||
|
github.com/mattn/go-isatty v0.0.16/go.mod h1:kYGgaQfpe5nmfYZH+SKPsOc2e4SrIfOl2e/yFXSvRLM=
|
||||||
|
github.com/mattn/go-isatty v0.0.20 h1:xfD0iDuEKnDkl03q4limB+vH+GxLEtL/jb4xVJSWWEY=
|
||||||
|
github.com/mattn/go-isatty v0.0.20/go.mod h1:W+V8PltTTMOvKvAeJH7IuucS94S2C6jfK/D7dTCTo3Y=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16 h1:E5ScNMtiwvlvB5paMFdw9p4kSQzbXFikJ5SQO6TULQc=
|
||||||
|
github.com/mattn/go-runewidth v0.0.16/go.mod h1:Jdepj2loyihRzMpdS35Xk/zdY8IAYHsh153qUoGf23w=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0 h1:jeMsZIYE/09sWLaz43PL7Gy6RuMjD2eJVyuac5Z2hdY=
|
||||||
|
github.com/mitchellh/mapstructure v1.5.0/go.mod h1:bFUtVrKA4DC2yAKiSyO/QUcy7e+RRV2QTWOzhPopBRo=
|
||||||
|
github.com/muesli/termenv v0.15.2 h1:GohcuySI0QmI3wN8Ok9PtKGkgkFIk7y6Vpb5PvrY+Wo=
|
||||||
|
github.com/muesli/termenv v0.15.2/go.mod h1:Epx+iuz8sNs7mNKhxzH4fWXGNpZwUaJKRS1noLXviQ8=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.3 h1:YmeHyLY8mFWbdkNWwpr+qIL2bEqT0o95WSdkNHvL12M=
|
||||||
|
github.com/pelletier/go-toml/v2 v2.2.3/go.mod h1:MfCQTFTvCcUyyvvwm1+G6H/jORL20Xlb6rzQu9GuUkc=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U=
|
||||||
|
github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rivo/uniseg v0.2.0/go.mod h1:J6wj4VEh+S6ZtnVlnTBMWIodfgj8LQOQFoIToxlJtxc=
|
||||||
|
github.com/rivo/uniseg v0.4.7 h1:WUdvkW8uEhrYfLC4ZzdpI2ztxP1I582+49Oc5Mq64VQ=
|
||||||
|
github.com/rivo/uniseg v0.4.7/go.mod h1:FN3SvrM+Zdj16jyLfmOkMNblXMcoc8DfTHruCPUcx88=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0 h1:73kH8U+JUqXU8lRuOHeVHaa/SZPifC7BkcraZVejAe8=
|
||||||
|
github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs=
|
||||||
|
github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM=
|
||||||
|
github.com/sagikazarmark/locafero v0.6.0 h1:ON7AQg37yzcRPU69mt7gwhFEBwxI6P9T4Qu3N51bwOk=
|
||||||
|
github.com/sagikazarmark/locafero v0.6.0/go.mod h1:77OmuIc6VTraTXKXIs/uvUxKGUXjE1GbemJYHqdNjX0=
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0 h1:diDBnUNK9N/354PgrxMywXnAwEr1QZcOr6gto+ugjYE=
|
||||||
|
github.com/sagikazarmark/slog-shim v0.1.0/go.mod h1:SrcSrq8aKtyuqEI1uvTDTK1arOWRIczQRv+GVI1AkeQ=
|
||||||
|
github.com/samber/lo v1.47.0 h1:z7RynLwP5nbyRscyvcD043DWYoOcYRv3mV8lBeqOCLc=
|
||||||
|
github.com/samber/lo v1.47.0/go.mod h1:RmDH9Ct32Qy3gduHQuKJ3gW1fMHAnE/fAzQuf6He5cU=
|
||||||
|
github.com/sourcegraph/conc v0.3.0 h1:OQTbbt6P72L20UqAkXXuLOj79LfEanQ+YQFNpLA9ySo=
|
||||||
|
github.com/sourcegraph/conc v0.3.0/go.mod h1:Sdozi7LEKbFPqYX2/J+iBAM6HpqSLTASQIKqDmF7Mt0=
|
||||||
|
github.com/spf13/afero v1.11.0 h1:WJQKhtpdm3v2IzqG8VMqrr6Rf3UYpEF239Jy9wNepM8=
|
||||||
|
github.com/spf13/afero v1.11.0/go.mod h1:GH9Y3pIexgf1MTIWtNGyogA5MwRIDXGUr+hbWNoBjkY=
|
||||||
|
github.com/spf13/cast v1.7.0 h1:ntdiHjuueXFgm5nzDRdOS4yfT43P5Fnud6DH50rz/7w=
|
||||||
|
github.com/spf13/cast v1.7.0/go.mod h1:ancEpBxwJDODSW/UG4rDrAqiKolqNNh2DX3mk86cAdo=
|
||||||
|
github.com/spf13/cobra v1.4.0/go.mod h1:Wo4iy3BUC+X2Fybo0PDqwJIv3dNRiZLHQymsfxlB84g=
|
||||||
|
github.com/spf13/cobra v1.8.1 h1:e5/vxKd/rZsfSJMUX1agtjeTDf+qv1/JdBF8gg5k9ZM=
|
||||||
|
github.com/spf13/cobra v1.8.1/go.mod h1:wHxEcudfqmLYa8iTfL+OuZPbBZkmvliBWKIezN3kD9Y=
|
||||||
|
github.com/spf13/pflag v1.0.5 h1:iy+VFUOCP1a+8yFto/drg2CJ5u0yRoB7fZw3DKv/JXA=
|
||||||
|
github.com/spf13/pflag v1.0.5/go.mod h1:McXfInJRrz4CZXVZOBLb0bTZqETkiAhM9Iw0y3An2Bg=
|
||||||
|
github.com/spf13/viper v1.19.0 h1:RWq5SEjt8o25SROyN3z2OrDB9l7RPd3lwTWU8EcEdcI=
|
||||||
|
github.com/spf13/viper v1.19.0/go.mod h1:GQUN9bilAbhU/jgc1bKs99f/suXKeUMct8Adx5+Ntkg=
|
||||||
|
github.com/stretchr/testify v1.9.0 h1:HtqpIVDClZ4nwg75+f6Lvsy/wHu+3BoSGCbBAcpTsTg=
|
||||||
|
github.com/stretchr/testify v1.9.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY=
|
||||||
|
github.com/subosito/gotenv v1.6.0 h1:9NlTDc1FTs4qu0DDq7AEtTPNw6SVm7uBMsUCUjABIf8=
|
||||||
|
github.com/subosito/gotenv v1.6.0/go.mod h1:Dk4QP5c2W3ibzajGcXpNraDfq2IrhjMIvMSWPKKo0FU=
|
||||||
|
go.uber.org/multierr v1.11.0 h1:blXXJkSxSSfBVBlC76pxqeO+LN3aDfLQo+309xJstO0=
|
||||||
|
go.uber.org/multierr v1.11.0/go.mod h1:20+QtiLqy0Nd6FdQB9TLXag12DsQkrbs3htMFfDN80Y=
|
||||||
|
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948 h1:kx6Ds3MlpiUHKj7syVnbp57++8WpuKPcR5yjLBjvLEA=
|
||||||
|
golang.org/x/exp v0.0.0-20240823005443-9b4947da3948/go.mod h1:akd2r19cwCdwSwWeIdzYQGa/EZZyqcOdwWiwj5L5eKQ=
|
||||||
|
golang.org/x/sys v0.0.0-20200116001909-b77594299b42/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20200223170610-d5e6a3e2c0ae/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs=
|
||||||
|
golang.org/x/sys v0.0.0-20210630005230-0f9fa26af87c/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.0.0-20220811171246-fbc7d0a398ab/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.6.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg=
|
||||||
|
golang.org/x/sys v0.24.0 h1:Twjiwq9dn6R1fQcyiK+wQyHWfaz/BJB+YIpzU/Cv3Xg=
|
||||||
|
golang.org/x/sys v0.24.0/go.mod h1:/VUhepiaJMQUp4+oa/7Zr1D23ma6VTLIYjOOTFZPUcA=
|
||||||
|
golang.org/x/text v0.17.0 h1:XtiM5bkSOt+ewxlOE/aE/AKEHibwj/6gvWMl9Rsh0Qc=
|
||||||
|
golang.org/x/text v0.17.0/go.mod h1:BuEKDfySbSR4drPmRPG/7iBdf8hvFMuRexcpahXilzY=
|
||||||
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15 h1:YR8cESwS4TdDjEe65xsg0ogRM/Nc3DYOhEAlW+xobZo=
|
||||||
|
gopkg.in/check.v1 v1.0.0-20190902080502-41f04d3bba15/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/ini.v1 v1.67.0 h1:Dgnx+6+nfE+IfzjUEISNeydPJh9AXNNsWbGP9KzCsOA=
|
||||||
|
gopkg.in/ini.v1 v1.67.0/go.mod h1:pNLf8WUiyNEtQjuu5G5vTm06TEv9tsIgeAvK8hOrP4k=
|
||||||
|
gopkg.in/yaml.v2 v2.4.0/go.mod h1:RDklbk79AGWmwhnvt/jBztapEOGDOx6ZbXqjP6csGnQ=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
15
internal/app/meta.go
Normal file
15
internal/app/meta.go
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
package app
|
||||||
|
|
||||||
|
// Version is the version of the application
|
||||||
|
const Version = "0.0.1"
|
||||||
|
|
||||||
|
const (
|
||||||
|
// App is the name of the application
|
||||||
|
Name = "app"
|
||||||
|
|
||||||
|
// DescriptionShort short description of the app
|
||||||
|
DescriptionShort = Name + " description"
|
||||||
|
|
||||||
|
// DescriptionLong long description of the app
|
||||||
|
DescriptionLong = Name + " v" + Version
|
||||||
|
)
|
||||||
25
internal/color/color.go
Normal file
25
internal/color/color.go
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
package color
|
||||||
|
|
||||||
|
import "github.com/charmbracelet/lipgloss"
|
||||||
|
|
||||||
|
const (
|
||||||
|
Red = lipgloss.Color("1")
|
||||||
|
Green = lipgloss.Color("2")
|
||||||
|
Yellow = lipgloss.Color("3")
|
||||||
|
Blue = lipgloss.Color("4")
|
||||||
|
Purple = lipgloss.Color("5")
|
||||||
|
Cyan = lipgloss.Color("6")
|
||||||
|
White = lipgloss.Color("7")
|
||||||
|
Black = lipgloss.Color("8")
|
||||||
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
HiRed = lipgloss.Color("9")
|
||||||
|
HiGreen = lipgloss.Color("10")
|
||||||
|
HiYellow = lipgloss.Color("11")
|
||||||
|
HiBlue = lipgloss.Color("12")
|
||||||
|
HiPurple = lipgloss.Color("13")
|
||||||
|
HiCyan = lipgloss.Color("14")
|
||||||
|
HiWhite = lipgloss.Color("15")
|
||||||
|
HiBlack = lipgloss.Color("16")
|
||||||
|
)
|
||||||
66
internal/config/default.go
Normal file
66
internal/config/default.go
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"hub.cybercinch.nz/guisea/go-template/internal/config/key"
|
||||||
|
)
|
||||||
|
|
||||||
|
// fields is the config fields with their default values and descriptions
|
||||||
|
var fields = []*Field{
|
||||||
|
// LOGS
|
||||||
|
{
|
||||||
|
key.LogsWrite,
|
||||||
|
true,
|
||||||
|
"Write logs to file",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key.LogsLevel,
|
||||||
|
"info",
|
||||||
|
`Logs level.
|
||||||
|
Available options are: (from less to most verbose)
|
||||||
|
fatal, error, warn, info, debug`,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
key.LogsReportCaller,
|
||||||
|
false,
|
||||||
|
"Whether the logger should report the caller location.",
|
||||||
|
},
|
||||||
|
//{
|
||||||
|
// key.TenantId,
|
||||||
|
// "some_client_id",
|
||||||
|
// "The TenantID to use with Xero.",
|
||||||
|
//},
|
||||||
|
//{
|
||||||
|
// key.BankAccountId,
|
||||||
|
// "a-random-bank-account-id",
|
||||||
|
// "Guid for the xero account to use.",
|
||||||
|
//},
|
||||||
|
// END LOGS
|
||||||
|
// BEANSTALK
|
||||||
|
//{
|
||||||
|
// key.BeanstalkServer,
|
||||||
|
// "localhost",
|
||||||
|
// "The Beanstalk Server to use",
|
||||||
|
//},
|
||||||
|
//{
|
||||||
|
// key.BeanstalkPort,
|
||||||
|
// "11300",
|
||||||
|
// "The port to communicate with Beanstalk server",
|
||||||
|
//},
|
||||||
|
//{
|
||||||
|
// key.BeanstalkTube,
|
||||||
|
// "visa-transactions",
|
||||||
|
// "The tube to use to find our transactions in.",
|
||||||
|
//},
|
||||||
|
}
|
||||||
|
|
||||||
|
func setDefaults() {
|
||||||
|
Default = make(map[string]*Field, len(fields))
|
||||||
|
for _, f := range fields {
|
||||||
|
Default[f.Key] = f
|
||||||
|
viper.SetDefault(f.Key, f.DefaultValue)
|
||||||
|
viper.MustBindEnv(f.Key)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var Default map[string]*Field
|
||||||
90
internal/config/field.go
Normal file
90
internal/config/field.go
Normal file
@@ -0,0 +1,90 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"fmt"
|
||||||
|
"reflect"
|
||||||
|
"strconv"
|
||||||
|
"strings"
|
||||||
|
"text/template"
|
||||||
|
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"hub.cybercinch.nz/guisea/go-template/internal/app"
|
||||||
|
|
||||||
|
"hub.cybercinch.nz/guisea/go-template/internal/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
type Field struct {
|
||||||
|
Key string
|
||||||
|
DefaultValue any
|
||||||
|
Description string
|
||||||
|
}
|
||||||
|
|
||||||
|
// typeName returns the type of the field without reflection
|
||||||
|
func (f *Field) typeName() string {
|
||||||
|
switch f.DefaultValue.(type) {
|
||||||
|
case string:
|
||||||
|
return "string"
|
||||||
|
case int:
|
||||||
|
return "int"
|
||||||
|
case bool:
|
||||||
|
return "bool"
|
||||||
|
case []string:
|
||||||
|
return "[]string"
|
||||||
|
case []int:
|
||||||
|
return "[]int"
|
||||||
|
default:
|
||||||
|
return "unknown"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var prettyTemplate = lo.Must(template.New("pretty").Funcs(template.FuncMap{
|
||||||
|
"faint": lipgloss.NewStyle().Faint(true).Render,
|
||||||
|
"bold": lipgloss.NewStyle().Bold(true).Render,
|
||||||
|
"purple": lipgloss.NewStyle().Foreground(color.Purple).Render,
|
||||||
|
"blue": lipgloss.NewStyle().Foreground(color.Blue).Render,
|
||||||
|
"cyan": lipgloss.NewStyle().Foreground(color.Cyan).Render,
|
||||||
|
"value": func(k string) any { return viper.Get(k) },
|
||||||
|
"hl": func(v any) string {
|
||||||
|
switch value := v.(type) {
|
||||||
|
case bool:
|
||||||
|
b := strconv.FormatBool(value)
|
||||||
|
if value {
|
||||||
|
return lipgloss.NewStyle().Foreground(color.Green).Render(b)
|
||||||
|
}
|
||||||
|
|
||||||
|
return lipgloss.NewStyle().Foreground(color.Red).Render(b)
|
||||||
|
case string:
|
||||||
|
return lipgloss.NewStyle().Foreground(color.Yellow).Render(value)
|
||||||
|
default:
|
||||||
|
return fmt.Sprint(value)
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"typename": func(v any) string { return reflect.TypeOf(v).String() },
|
||||||
|
}).Parse(`{{ faint .Description }}
|
||||||
|
{{ blue "Key:" }} {{ purple .Key }}
|
||||||
|
{{ blue "Env:" }} {{ .Env }}
|
||||||
|
{{ blue "Value:" }} {{ hl (value .Key) }}
|
||||||
|
{{ blue "Default:" }} {{ hl (.DefaultValue) }}
|
||||||
|
{{ blue "Type:" }} {{ typename .DefaultValue }}`))
|
||||||
|
|
||||||
|
func (f *Field) Pretty() string {
|
||||||
|
var b strings.Builder
|
||||||
|
|
||||||
|
lo.Must0(prettyTemplate.Execute(&b, f))
|
||||||
|
|
||||||
|
return b.String()
|
||||||
|
}
|
||||||
|
|
||||||
|
func (f *Field) Env() string {
|
||||||
|
env := strings.ToUpper(EnvKeyReplacer.Replace(f.Key))
|
||||||
|
appPrefix := strings.ToUpper(app.Name + "_")
|
||||||
|
|
||||||
|
if strings.HasPrefix(env, appPrefix) {
|
||||||
|
return env
|
||||||
|
}
|
||||||
|
|
||||||
|
return appPrefix + env
|
||||||
|
}
|
||||||
38
internal/config/init.go
Normal file
38
internal/config/init.go
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
package config
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"hub.cybercinch.nz/guisea/go-template/internal/app"
|
||||||
|
"hub.cybercinch.nz/guisea/go-template/internal/filesystem"
|
||||||
|
"hub.cybercinch.nz/guisea/go-template/internal/where"
|
||||||
|
)
|
||||||
|
|
||||||
|
// ConfigFormat is the format of the config file
|
||||||
|
// Available options are: json, yaml, toml
|
||||||
|
const ConfigFormat = "yaml"
|
||||||
|
|
||||||
|
var EnvKeyReplacer = strings.NewReplacer(".", "_")
|
||||||
|
|
||||||
|
func Init() error {
|
||||||
|
viper.SetConfigName(app.Name)
|
||||||
|
viper.SetConfigType(ConfigFormat)
|
||||||
|
viper.SetFs(filesystem.Api())
|
||||||
|
viper.AddConfigPath(where.Config())
|
||||||
|
viper.SetTypeByDefaultValue(true)
|
||||||
|
viper.SetEnvPrefix(app.Name)
|
||||||
|
viper.SetEnvKeyReplacer(EnvKeyReplacer)
|
||||||
|
|
||||||
|
setDefaults()
|
||||||
|
|
||||||
|
err := viper.ReadInConfig()
|
||||||
|
|
||||||
|
switch err.(type) {
|
||||||
|
case viper.ConfigFileNotFoundError:
|
||||||
|
// Use defaults then
|
||||||
|
return nil
|
||||||
|
default:
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
}
|
||||||
7
internal/config/key/keys.go
Normal file
7
internal/config/key/keys.go
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
package key
|
||||||
|
|
||||||
|
const (
|
||||||
|
LogsWrite = "logs.write"
|
||||||
|
LogsLevel = "logs.level"
|
||||||
|
LogsReportCaller = "logs.show_caller"
|
||||||
|
)
|
||||||
8
internal/filesystem/api.go
Normal file
8
internal/filesystem/api.go
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
package filesystem
|
||||||
|
|
||||||
|
import "github.com/spf13/afero"
|
||||||
|
|
||||||
|
// Api returns the filesystem api
|
||||||
|
func Api() afero.Afero {
|
||||||
|
return wrapper
|
||||||
|
}
|
||||||
5
internal/filesystem/init.go
Normal file
5
internal/filesystem/init.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package filesystem
|
||||||
|
|
||||||
|
func init() {
|
||||||
|
SetOsFs()
|
||||||
|
}
|
||||||
18
internal/filesystem/set.go
Normal file
18
internal/filesystem/set.go
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
package filesystem
|
||||||
|
|
||||||
|
import "github.com/spf13/afero"
|
||||||
|
|
||||||
|
// SetOsFs sets the filesystem to the os filesystem
|
||||||
|
func SetOsFs() {
|
||||||
|
if wrapper.Fs == nil || wrapper.Fs.Name() != "os" {
|
||||||
|
wrapper.Fs = afero.NewOsFs()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// SetMemMapFs sets the filesystem to the memory mapped filesystem
|
||||||
|
// Use this if you want to use the filesystem in a sandbox
|
||||||
|
func SetMemMapFs() {
|
||||||
|
if wrapper.Fs == nil || wrapper.Fs.Name() != "memmap" {
|
||||||
|
wrapper.Fs = afero.NewMemMapFs()
|
||||||
|
}
|
||||||
|
}
|
||||||
5
internal/filesystem/wrapper.go
Normal file
5
internal/filesystem/wrapper.go
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
package filesystem
|
||||||
|
|
||||||
|
import "github.com/spf13/afero"
|
||||||
|
|
||||||
|
var wrapper = afero.Afero{}
|
||||||
13
internal/icon/icon.go
Normal file
13
internal/icon/icon.go
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
package icon
|
||||||
|
|
||||||
|
const (
|
||||||
|
Cross = "✖"
|
||||||
|
Check = "✔"
|
||||||
|
Arrow = "➜"
|
||||||
|
Info = "ℹ"
|
||||||
|
Star = "★"
|
||||||
|
Heart = "♥"
|
||||||
|
Warn = "⚠"
|
||||||
|
Gear = "⚙"
|
||||||
|
Ellipsis = "…"
|
||||||
|
)
|
||||||
48
internal/logger/init.go
Normal file
48
internal/logger/init.go
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
package logger
|
||||||
|
|
||||||
|
import (
|
||||||
|
"errors"
|
||||||
|
"fmt"
|
||||||
|
"github.com/charmbracelet/log"
|
||||||
|
"github.com/spf13/viper"
|
||||||
|
"hub.cybercinch.nz/guisea/go-template/internal/config/key"
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"time"
|
||||||
|
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"hub.cybercinch.nz/guisea/go-template/internal/filesystem"
|
||||||
|
"hub.cybercinch.nz/guisea/go-template/internal/where"
|
||||||
|
)
|
||||||
|
|
||||||
|
func Init() error {
|
||||||
|
logsPath := where.Logs()
|
||||||
|
|
||||||
|
if logsPath == "" {
|
||||||
|
return errors.New("logs path is not set")
|
||||||
|
}
|
||||||
|
|
||||||
|
today := time.Now().Format("2006-01-02")
|
||||||
|
logFilePath := filepath.Join(logsPath, fmt.Sprintf("%s.log", today))
|
||||||
|
if !lo.Must(filesystem.Api().Exists(logFilePath)) {
|
||||||
|
lo.Must(filesystem.Api().Create(logFilePath))
|
||||||
|
}
|
||||||
|
|
||||||
|
logFile, err := filesystem.Api().OpenFile(logFilePath, os.O_RDWR|os.O_CREATE|os.O_APPEND, 0666)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
logger := log.NewWithOptions(logFile, log.Options{
|
||||||
|
TimeFormat: time.TimeOnly,
|
||||||
|
ReportTimestamp: true,
|
||||||
|
ReportCaller: viper.GetBool(key.LogsReportCaller),
|
||||||
|
})
|
||||||
|
|
||||||
|
level, _ := log.ParseLevel(key.LogsLevel)
|
||||||
|
logger.SetLevel(level)
|
||||||
|
|
||||||
|
log.SetDefault(logger)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
12
internal/style/style.go
Normal file
12
internal/style/style.go
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
package style
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/charmbracelet/lipgloss"
|
||||||
|
"hub.cybercinch.nz/guisea/go-template/internal/color"
|
||||||
|
)
|
||||||
|
|
||||||
|
var (
|
||||||
|
Success = lipgloss.NewStyle().Foreground(color.Green).Render
|
||||||
|
Failure = lipgloss.NewStyle().Foreground(color.Red).Render
|
||||||
|
Warning = lipgloss.NewStyle().Foreground(color.Yellow).Render
|
||||||
|
)
|
||||||
11
internal/util/strings.go
Normal file
11
internal/util/strings.go
Normal file
@@ -0,0 +1,11 @@
|
|||||||
|
package util
|
||||||
|
|
||||||
|
import "strings"
|
||||||
|
|
||||||
|
func Capitalize(s string) string {
|
||||||
|
if len(s) == 0 {
|
||||||
|
return s
|
||||||
|
}
|
||||||
|
|
||||||
|
return strings.ToUpper(string(s[0])) + s[1:]
|
||||||
|
}
|
||||||
10
internal/where/env.go
Normal file
10
internal/where/env.go
Normal file
@@ -0,0 +1,10 @@
|
|||||||
|
package where
|
||||||
|
|
||||||
|
import (
|
||||||
|
"strings"
|
||||||
|
|
||||||
|
"hub.cybercinch.nz/guisea/go-template/internal/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
// EnvConfigPath is the environment variable name for the config path
|
||||||
|
var EnvConfigPath = strings.ToUpper(app.Name) + "_CONFIG_PATH"
|
||||||
14
internal/where/util.go
Normal file
14
internal/where/util.go
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
package where
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"hub.cybercinch.nz/guisea/go-template/internal/filesystem"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
// mkdir creates a directory and all parent directories if they don't exist
|
||||||
|
// will return the path of the directory
|
||||||
|
func mkdir(path string) string {
|
||||||
|
lo.Must0(filesystem.Api().MkdirAll(path, os.ModePerm))
|
||||||
|
return path
|
||||||
|
}
|
||||||
68
internal/where/where.go
Normal file
68
internal/where/where.go
Normal file
@@ -0,0 +1,68 @@
|
|||||||
|
package where
|
||||||
|
|
||||||
|
import (
|
||||||
|
"os"
|
||||||
|
"path/filepath"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
|
"hub.cybercinch.nz/guisea/go-template/internal/app"
|
||||||
|
)
|
||||||
|
|
||||||
|
func home() string {
|
||||||
|
home, err := os.UserHomeDir()
|
||||||
|
if err == nil {
|
||||||
|
return home
|
||||||
|
}
|
||||||
|
|
||||||
|
return "."
|
||||||
|
}
|
||||||
|
|
||||||
|
// Config path
|
||||||
|
// Will create the directory if it doesn't exist
|
||||||
|
func Config() string {
|
||||||
|
var path string
|
||||||
|
|
||||||
|
if customDir, present := os.LookupEnv(EnvConfigPath); present {
|
||||||
|
return mkdir(customDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
var userConfigDir string
|
||||||
|
|
||||||
|
if runtime.GOOS == "darwin" {
|
||||||
|
userConfigDir = filepath.Join(home(), ".config")
|
||||||
|
} else {
|
||||||
|
var err error
|
||||||
|
userConfigDir, err = os.UserConfigDir()
|
||||||
|
if err != nil {
|
||||||
|
userConfigDir = filepath.Join(home(), ".config")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
path = filepath.Join(userConfigDir, app.Name)
|
||||||
|
return mkdir(path)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Logs path
|
||||||
|
// Will create the directory if it doesn't exist
|
||||||
|
func Logs() string {
|
||||||
|
return mkdir(filepath.Join(Cache(), "logs"))
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cache path
|
||||||
|
// Will create the directory if it doesn't exist
|
||||||
|
func Cache() string {
|
||||||
|
userCacheDir, err := os.UserCacheDir()
|
||||||
|
if err != nil {
|
||||||
|
userCacheDir = "."
|
||||||
|
}
|
||||||
|
|
||||||
|
cacheDir := filepath.Join(userCacheDir, app.Name)
|
||||||
|
return mkdir(cacheDir)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Temp path
|
||||||
|
// Will create the directory if it doesn't exist
|
||||||
|
func Temp() string {
|
||||||
|
tempDir := filepath.Join(os.TempDir(), app.Name)
|
||||||
|
return mkdir(tempDir)
|
||||||
|
}
|
||||||
17
justfile
Executable file
17
justfile
Executable file
@@ -0,0 +1,17 @@
|
|||||||
|
#!/usr/bin/env just --justfile
|
||||||
|
|
||||||
|
go-mod := `go list`
|
||||||
|
flags := '-ldflags="-s -w"'
|
||||||
|
|
||||||
|
install:
|
||||||
|
go install {{flags}}
|
||||||
|
|
||||||
|
build:
|
||||||
|
@echo -n "Building app ... "
|
||||||
|
@go build {{flags}} -o bin/ && echo "OK" || echo "FAILED"
|
||||||
|
update:
|
||||||
|
go get -u
|
||||||
|
go mod tidy -v
|
||||||
|
|
||||||
|
rename new-go-mod:
|
||||||
|
@find . -type f -not -path './.git/*' -exec sed -i '' -e "s|{{go-mod}}|{{new-go-mod}}|g" >/dev/null 2>&1 {} \;
|
||||||
28
main.go
Normal file
28
main.go
Normal file
@@ -0,0 +1,28 @@
|
|||||||
|
package main
|
||||||
|
|
||||||
|
import (
|
||||||
|
"github.com/charmbracelet/log"
|
||||||
|
"github.com/samber/lo"
|
||||||
|
"hub.cybercinch.nz/guisea/go-template/cmd"
|
||||||
|
"hub.cybercinch.nz/guisea/go-template/internal/config"
|
||||||
|
"hub.cybercinch.nz/guisea/go-template/internal/logger"
|
||||||
|
"os"
|
||||||
|
)
|
||||||
|
|
||||||
|
func handlePanic() {
|
||||||
|
if err := recover(); err != nil {
|
||||||
|
log.Error("crashed", "err", err)
|
||||||
|
os.Exit(1)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func main() {
|
||||||
|
defer handlePanic()
|
||||||
|
|
||||||
|
// prepare config and logs
|
||||||
|
lo.Must0(config.Init())
|
||||||
|
lo.Must0(logger.Init())
|
||||||
|
|
||||||
|
// run the app
|
||||||
|
cmd.Execute()
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user