Initial project

This commit is contained in:
bwilliams committed 2026-09-23 14:32:50 +01:00
commit e715024cbd
23 files changed
+1547

No files matched your search

+41
View File
@@ -0,0 +1,41 @@
/*
Copyright © 2026 Craig Williams <craig@avsdev.uk>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package config
import (
"log/slog"
"github.com/spf13/cobra"
)
func NewCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "config",
Short: "Configuration management",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
slog.Debug("config.PersistentPreRun")
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
slog.Debug("config.PersistentPostRun")
},
}
cmd.AddCommand(commandShow())
cmd.AddCommand(commandGet())
cmd.AddCommand(commandSet())
cmd.AddCommand(commandInit())
return cmd
}
+83
View File
@@ -0,0 +1,83 @@
/*
Copyright © 2026 Craig Williams <craig@avsdev.uk>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package config
import (
"errors"
"fmt"
"log/slog"
"git.avsdev.uk/Go/job-search/internal/cli"
"github.com/spf13/cobra"
)
func commandGet() *cobra.Command {
cmd := &cobra.Command{
Use: "get [config_key]",
Args: cobra.ExactArgs(1),
// Args: func(cmd *cobra.Command, args []string) error {
// // Optionally run one of the validators provided by cobra
// if err := cobra.ExactArgs(1)(cmd, args); err != nil {
// return err
// }
// conf, ok := cli.ConfigFromContext(cmd.Context())
// if !ok {
// return errors.New("no configuration loaded")
// }
// flatConf := config.FlattenConfig(conf)
// if !util.HasKey(flatConf, args[0]) {
// return fmt.Errorf("invalid config key specified: %s", args[0])
// }
// return nil
// },
Short: "Get a value from configuration",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
slog.Debug("config.get.PersistentPreRun")
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
slog.Debug("config.get.PersistentPostRun")
},
RunE: func(cmd *cobra.Command, args []string) error {
slog.Debug("config.get.Run")
if err := cobra.ExactArgs(1)(cmd, args); err != nil {
return err
}
conf, ok := cli.ConfigFromContext(cmd.Context())
if !ok {
return errors.New("no configuration loaded")
}
v, err := conf.ValueOf(args[0])
if err != nil {
return err
}
t, _ := conf.TypeOf(args[0])
fmt.Println(v, "<"+t.String()+">")
return nil
},
Annotations: map[string]string{
"terminal_log": "1",
},
}
return cmd
}
+96
View File
@@ -0,0 +1,96 @@
/*
Copyright © 2026 Craig Williams <craig@avsdev.uk>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package config
import (
"errors"
"fmt"
"log/slog"
"os"
"git.avsdev.uk/Go/job-search/internal/config"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func commandInit() *cobra.Command {
cmd := &cobra.Command{
Use: "init [config_path]",
Args: cobra.MatchAll(cobra.MinimumNArgs(0), cobra.MaximumNArgs(1)),
Short: "Create an empty configuration file",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
slog.Debug("config.init.PersistentPreRun")
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
slog.Debug("config.init.PersistentPostRun")
},
RunE: func(cmd *cobra.Command, args []string) error {
slog.Debug("config.init.Run")
if err := cobra.MinimumNArgs(0)(cmd, args); err != nil {
return err
}
if err := cobra.MaximumNArgs(1)(cmd, args); err != nil {
return err
}
cfgFile, _ := cmd.Flags().GetString("config")
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Search config in /etc or local directory.
viper.AddConfigPath(config.EtcConfigPath)
viper.AddConfigPath(".")
viper.SetConfigName("config")
}
err := viper.ReadInConfig()
if err == nil {
return errors.New("configuration file already exists")
}
_, ok := err.(viper.ConfigFileNotFoundError)
if !errors.Is(err, os.ErrNotExist) && !ok {
// Config file was found but another error was produced
return err
}
// Load default configuration
config.LoadDefaultConfig()
if viper.ConfigFileUsed() == "" {
viper.SetConfigFile(config.EtcConfigPath + "/config.yaml")
}
fmt.Print(
"writing default configuration to file: " +
viper.ConfigFileUsed() +
"\n")
if err := viper.WriteConfig(); err != nil {
return err
}
return nil
},
Annotations: map[string]string{
"no_config": "1",
"terminal_log": "1",
},
}
return cmd
}
+141
View File
@@ -0,0 +1,141 @@
/*
Copyright © 2026 Craig Williams <craig@avsdev.uk>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package config
import (
"errors"
"fmt"
"log/slog"
"reflect"
"strconv"
"git.avsdev.uk/Go/job-search/internal/cli"
"git.avsdev.uk/Go/job-search/internal/config"
"github.com/spf13/cobra"
"github.com/spf13/viper"
)
func setConfig(conf config.JobSearchConfig, key string, value string) error {
var confValue any = nil
var confType reflect.Kind
var err error
confType, err = conf.TypeOf(key)
if err != nil {
return err
}
switch confType {
case reflect.Bool:
confValue, err = strconv.ParseBool(value)
case reflect.String:
confValue = value
case reflect.Int, reflect.Int8, reflect.Int16, reflect.Int32, reflect.Int64:
confValue, err = strconv.ParseInt(value, 10, 64)
case reflect.Uint, reflect.Uint8, reflect.Uint16, reflect.Uint32, reflect.Uint64:
confValue, err = strconv.ParseUint(value, 10, 64)
case reflect.Float32, reflect.Float64:
confValue, err = strconv.ParseFloat(value, 64)
default:
err = errors.ErrUnsupported
}
if err != nil {
return fmt.Errorf("config value type not accepted, expected <%s>", confType)
}
viper.Set(key, confValue)
if err := viper.WriteConfig(); err != nil {
return err
}
fmt.Print(key, " = ", confValue, " <", confType, ">\n")
return nil
}
func unsetConfig(conf config.JobSearchConfig, key string) error {
if _, err := conf.TypeOf(key); err != nil {
return err
}
v := viper.New()
v.SetConfigFile(viper.ConfigFileUsed())
keys := viper.AllKeys()
for i := range keys {
k := keys[i]
if k != key {
v.Set(k, viper.Get(k))
}
}
if err := v.WriteConfig(); err != nil {
return err
}
return nil
}
func commandSet() *cobra.Command {
cmd := &cobra.Command{
Use: "set [config_key] [config_value]",
Args: cobra.MatchAll(cobra.MinimumNArgs(1), cobra.MaximumNArgs(2)),
Short: "Set a value to configuration",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
slog.Debug("config.set.PersistentPreRun")
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
slog.Debug("config.set.PersistentPostRun")
},
RunE: func(cmd *cobra.Command, args []string) error {
slog.Debug("config.set.Run")
if err := cobra.MinimumNArgs(1)(cmd, args); err != nil {
return err
}
if err := cobra.MaximumNArgs(2)(cmd, args); err != nil {
return err
}
unset, unsetFlagErr := cmd.Flags().GetBool("unset")
if unsetFlagErr != nil {
return unsetFlagErr
}
if err := cobra.ExactArgs(2)(cmd, args); err != nil && !unset {
return err
}
conf, ok := cli.ConfigFromContext(cmd.Context())
if !ok {
return errors.New("no configuration loaded")
}
if viper.ConfigFileUsed() == "" {
return errors.New("no configuration file exists")
}
if unset {
return unsetConfig(conf, args[0])
} else {
return setConfig(conf, args[0], args[1])
}
},
Annotations: map[string]string{
"terminal_log": "1",
},
}
cmd.Flags().BoolP("unset", "x", false, "clear configuration setting")
return cmd
}
+62
View File
@@ -0,0 +1,62 @@
/*
Copyright © 2026 Craig Williams <craig@avsdev.uk>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package config
import (
"errors"
"log/slog"
"git.avsdev.uk/Go/job-search/internal/cli"
"github.com/spf13/cobra"
)
func commandShow() *cobra.Command {
cmd := &cobra.Command{
Use: "show",
Short: "Print config information",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
slog.Debug("config.show.PersistentPreRun")
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
slog.Debug("config.show.PersistentPostRun")
},
RunE: func(cmd *cobra.Command, args []string) error {
slog.Debug("config.show.Run")
conf, ok := cli.ConfigFromContext(cmd.Context())
if !ok {
return errors.New("no configuration loaded")
}
if yaml, _ := cmd.Flags().GetBool("yaml"); yaml {
conf.PrintYaml()
} else if json, _ := cmd.Flags().GetBool("json"); json {
conf.PrintJson()
} else {
conf.PrintTerminal()
}
return nil
},
Annotations: map[string]string{
"terminal_log": "1",
},
}
cmd.Flags().Bool("json", false, "format configuration as json")
cmd.Flags().Bool("yaml", false, "format configuration as yaml")
return cmd
}
+54
View File
@@ -0,0 +1,54 @@
/*
Copyright © 2026 Craig Williams <craig@avsdev.uk>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package version
import (
"fmt"
"log/slog"
"git.avsdev.uk/Go/job-search/internal/version"
"github.com/spf13/cobra"
)
func NewCommand() *cobra.Command {
cmd := &cobra.Command{
Use: "version",
Short: "Print version information",
PersistentPreRun: func(cmd *cobra.Command, args []string) {
slog.Debug("version.PersistentPreRun")
},
PersistentPostRun: func(cmd *cobra.Command, args []string) {
slog.Debug("version.PersistentPostRun")
},
Run: func(cmd *cobra.Command, args []string) {
slog.Debug("version.Run")
fmt.Printf("PBCNinja - %s\n", version.Version)
if version.BuildTime != "unknown" {
fmt.Printf("Built: %s\n", version.BuildTime)
}
if version.GitCommit != "unknown" {
fmt.Printf("Commit: %s\n", version.GitCommit)
}
},
Annotations: map[string]string{
"no_config": "1",
"terminal_log": "1",
},
}
return cmd
}
+43
View File
@@ -0,0 +1,43 @@
/*
Copyright © 2026 Craig Williams <craig@avsdev.uk>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package cli
import (
"context"
"git.avsdev.uk/Go/job-search/internal/config"
)
// Define a private key type to avoid collisions
type configContextKey struct {
name string
}
// Define the key
var configKey = &configContextKey{"config"}
// Public accessor to store a value to ctx
func WithConfig(ctx context.Context, conf config.JobSearchConfig) context.Context {
// No allocation here since userIDKey is a pointer to a struct
return context.WithValue(ctx, configKey, conf)
}
// Public accessor to fetch a value from ctx
func ConfigFromContext(ctx context.Context) (config.JobSearchConfig, bool) {
v, ok := ctx.Value(configKey).(config.JobSearchConfig)
return v, ok
}
+108
View File
@@ -0,0 +1,108 @@
/*
Copyright © 2026 Craig Williams <craig@avsdev.uk>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package config
import (
"errors"
"log/slog"
"reflect"
)
type JobSearchConfigLogging struct {
Level string
}
// Default configuration
func defaultConfigLogging() JobSearchConfigLogging {
return JobSearchConfigLogging{
Level: "warn",
}
}
// Converts configuration to a map
func (conf JobSearchConfigLogging) Map() map[string]any {
return map[string]any{
"level": conf.Level,
}
}
// Converts configuration to a flat dotted format
func (conf JobSearchConfigLogging) Flatten() map[string]any {
confMap := conf.Map()
retMap := map[string]any{}
for k, v := range confMap {
retMap["logging."+k] = v
}
return retMap
}
// Returns the expected type of a configuration key value
func (c JobSearchConfigLogging) TypeOf(key string) (reflect.Kind, error) {
switch key {
case "level":
return reflect.String, nil
default:
return reflect.Invalid, errors.New("unknown configuration key")
}
}
// Returns the value of a configuration key
func (c JobSearchConfigLogging) ValueOf(key string) (any, error) {
switch key {
case "level":
return c.Level, nil
default:
return "", errors.New("unknown configuration key")
}
}
// Sets the value of a configuration key
func (c *JobSearchConfigLogging) SetValue(key string, value any) error {
switch key {
case "level":
newLevel := value.(string)
switch newLevel {
case "debug", "info", "warn", "error":
c.Level = value.(string)
default:
return errors.New(
"invalid log level. Must be one of: debug, info, warn, error")
}
default:
return errors.New("unknown configuration key")
}
return nil
}
func ConfigLevelToLogLevel(level string) slog.Level {
switch level {
case "debug":
return slog.LevelDebug
case "info":
return slog.LevelInfo
case "warn":
return slog.LevelWarn
case "error":
return slog.LevelError
default:
return 99
}
}
func (c *JobSearchConfigLogging) LogLevel() slog.Level {
return ConfigLevelToLogLevel(c.Level)
}
+114
View File
@@ -0,0 +1,114 @@
/*
Copyright © 2026 Craig Williams <craig@avsdev.uk>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package config
import (
"errors"
"maps"
"reflect"
"strings"
"github.com/spf13/viper"
)
const EnvPrefix string = "JOBSEARCH"
const EtcConfigPath string = "/etc/job-search"
type JobSearchConfig struct {
Logging JobSearchConfigLogging
}
// Default configuration
func LoadDefaultConfig() {
conf := JobSearchConfig{
Logging: defaultConfigLogging(),
}
LoadToViper(conf)
}
// Converts configuration to a map
func (conf JobSearchConfig) Map() map[string]any {
confMap := map[string]any{}
confMap["logging"] = conf.Logging.Map()
return confMap
}
// Converts configuration to a flat dotted format
func (conf JobSearchConfig) Flatten() map[string]any {
confFlat := map[string]any{}
maps.Copy(confFlat, conf.Logging.Flatten())
return confFlat
}
// Returns the expected type of a configuration key value
func (c JobSearchConfig) TypeOf(key string) (reflect.Kind, error) {
keyParts := strings.Split(key, ".")
switch keyParts[0] {
case "logging":
return c.Logging.TypeOf(strings.Join(keyParts[1:], "."))
default:
return reflect.Invalid, errors.New("unknown configuration key")
}
}
// Returns the expected type of a configuration value
func (c JobSearchConfig) ValueOf(key string) (any, error) {
keyParts := strings.Split(key, ".")
switch keyParts[0] {
case "logging":
return c.Logging.ValueOf(strings.Join(keyParts[1:], "."))
default:
return nil, errors.New("unknown configuration key")
}
}
// Returns the expected type of a configuration value
func (c *JobSearchConfig) SetValue(key string, value any) error {
keyParts := strings.Split(key, ".")
switch keyParts[0] {
case "logging":
return c.Logging.SetValue(strings.Join(keyParts[1:], "."), value)
default:
return errors.New("unknown configuration key")
}
}
func LoadFromViper(conf *JobSearchConfig) {
keys := viper.AllKeys()
for idx := range keys {
key := keys[idx]
keyType, _ := conf.TypeOf(key)
if keyType == reflect.Invalid {
continue
}
switch keyType {
case reflect.String:
conf.SetValue(key, viper.GetString(key))
case reflect.Int:
conf.SetValue(key, viper.GetInt(key))
case reflect.Bool:
conf.SetValue(key, viper.GetBool(key))
}
}
}
func LoadToViper(conf JobSearchConfig) {
flatMap := conf.Flatten()
for key := range flatMap {
viper.SetDefault(key, flatMap[key])
}
}
+60
View File
@@ -0,0 +1,60 @@
/*
Copyright © 2026 Craig Williams <craig@avsdev.uk>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package config
import (
"fmt"
"log/slog"
"os"
"reflect"
"slices"
"strings"
"github.com/spf13/viper"
)
// Debug: Prints detected environment variables
func printEnv() {
slog.Debug("Environment:")
env := os.Environ()
slices.Sort(env)
for _, e := range env {
pair := strings.SplitN(e, "=", 2)
if strings.HasPrefix(pair[0], EnvPrefix) {
slog.Debug("\t" + pair[0] + "=" + pair[1])
}
}
}
// Debug: Prints loaded configuration
func printLoadedConfig() {
slog.Debug("Loaded config:")
keys := viper.AllKeys()
for i := range keys {
k := keys[i]
v := viper.Get(k)
t := reflect.TypeOf(v)
slog.Debug(
"\t" + k + " = " + fmt.Sprintf("%#v", v) + " <" + t.Kind().String() + ">")
}
}
// Debug: Prints parsed configuration
func printConfig(conf JobSearchConfig) {
slog.Debug("Config:")
conf.PrintLog(slog.LevelDebug)
}
+96
View File
@@ -0,0 +1,96 @@
/*
Copyright © 2026 Craig Williams <craig@avsdev.uk>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package config
import (
"fmt"
"log/slog"
"github.com/joho/godotenv"
"github.com/spf13/viper"
)
// Reads in dotenv file for configuration
func loadEnv() error {
if err := godotenv.Load(); err != nil {
return fmt.Errorf("invalid .env file: %w", err)
}
return nil
}
// Reads in config file and ENV variables if set to form a configuration.
func loadConfig(cfgFile string) error {
if cfgFile != "" {
// Use config file from the flag.
viper.SetConfigFile(cfgFile)
} else {
// Search config in /etc or local directory.
viper.AddConfigPath(EtcConfigPath)
viper.AddConfigPath(".")
viper.SetConfigName("config")
}
// Load default configuration
LoadDefaultConfig()
viper.SetEnvPrefix(EnvPrefix)
viper.AutomaticEnv() // read in environment variables that match
err := viper.ReadInConfig()
if err != nil {
if _, ok := err.(viper.ConfigFileNotFoundError); ok {
// Indicates an explicitly set config file is not found or
// that no config file was found in any search path
slog.Warn("No config file found!")
} else {
// Config file was found but another error was produced
return err
}
} else {
// Config file found and successfully parsed
slog.Info("Using config file: " + viper.ConfigFileUsed())
}
return nil
}
// Initilises environment file configuration and general app configuration
func Init(cfgFile string, conf *JobSearchConfig) error {
if err := loadEnv(); err != nil {
return err
}
if conf.Logging.LogLevel() <= slog.LevelDebug {
printEnv()
}
if err := loadConfig(cfgFile); err != nil {
return err
}
oldLogLevel := conf.Logging.Level
LoadFromViper(conf)
if ConfigLevelToLogLevel(oldLogLevel) < conf.Logging.LogLevel() {
conf.Logging.Level = oldLogLevel
}
if conf.Logging.LogLevel() <= slog.LevelDebug {
printLoadedConfig()
printConfig(*conf)
}
return nil
}
+75
View File
@@ -0,0 +1,75 @@
/*
Copyright © 2026 Craig Williams <craig@avsdev.uk>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package config
import (
"encoding/json"
"fmt"
"log/slog"
"go.yaml.in/yaml/v3"
)
// Prints configuration to stdout in flat dotted format
func (conf JobSearchConfig) PrintTerminal() {
for k, v := range conf.Flatten() {
typ, _ := conf.TypeOf(k)
fmt.Print(k, " = ", v, " <", typ, ">\n")
}
}
// Prints configuration to stdout as JSON
func (conf JobSearchConfig) PrintJson() {
confMap := conf.Map()
jbytes, _ := json.MarshalIndent(confMap, "", " ")
fmt.Print(string(jbytes) + "\n")
}
// Prints configuration to stdout as YAML
func (conf JobSearchConfig) PrintYaml() {
confMap := conf.Map()
jbytes, _ := yaml.Marshal(confMap)
fmt.Print(string(jbytes) + "\n")
}
// Prints configuration to logrus log at debug, info (default) or error level
func (conf JobSearchConfig) PrintLog(atLevel slog.Level) {
var logFn func(args ...interface{})
switch atLevel {
case slog.LevelDebug:
logFn = func(args ...interface{}) {
slog.Debug(fmt.Sprint(args...))
}
case slog.LevelError:
logFn = func(args ...interface{}) {
slog.Error(fmt.Sprint(args...))
}
case slog.LevelWarn:
logFn = func(args ...interface{}) {
slog.Warn(fmt.Sprint(args...))
}
default:
logFn = func(args ...interface{}) {
slog.Info(fmt.Sprint(args...))
}
}
for k, v := range conf.Flatten() {
logFn("\t", k, " = ", v)
}
}
+63
View File
@@ -0,0 +1,63 @@
/*
Copyright © 2026 Craig Williams <craig@avsdev.uk>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU Lesser General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public License
along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
package log
import (
"log/slog"
"os"
"github.com/lmittmann/tint"
)
var logLevel slog.LevelVar
// Set up the logger using defaults
func Init() {
logLevel.Set(slog.LevelWarn)
opts := &slog.HandlerOptions{
Level: &logLevel,
}
handler := slog.NewTextHandler(os.Stdout, opts)
logger := slog.New(handler)
slog.SetDefault(logger)
}
// Enable info level logging
func EnableInfo() {
if logLevel.Level() > slog.LevelInfo {
logLevel.Set(slog.LevelInfo)
slog.Info("Info logging enabled")
}
}
// Enable debug level logging
func EnableDebug() {
if logLevel.Level() > slog.LevelDebug {
logLevel.Set(slog.LevelDebug)
slog.Debug("Debug logging enabled")
}
}
func LogToTerminal() {
opts := &tint.Options{
Level: &logLevel,
TimeFormat: "*",
}
handler := tint.NewTextHandler(os.Stdout, opts)
logger := slog.New(handler)
slog.SetDefault(logger)
}
+8
View File
@@ -0,0 +1,8 @@
package version
// Version information (set at build time with -ldflags)
var (
Version = "dev"
BuildTime = "unknown"
GitCommit = "unknown"
)