Initial commit
This commit is contained in:
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)
|
||||
}
|
||||
Reference in New Issue
Block a user