Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Introduce file-based user configuration #249

Merged
merged 9 commits into from
Feb 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ require (
github.com/kballard/go-shellquote v0.0.0-20180428030007-95032a82bc51
github.com/posener/complete v1.2.3
github.com/stretchr/testify v1.7.2
gopkg.in/yaml.v3 v3.0.1
)

require (
Expand All @@ -18,5 +19,4 @@ require (
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/riywo/loginshell v0.0.0-20200815045211-7d26008be1ab // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
86 changes: 73 additions & 13 deletions klog.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import (
_ "embed"
"fmt"
"github.com/jotaen/klog/klog/app"
"github.com/jotaen/klog/klog/app/cli/lib"
"github.com/jotaen/klog/klog/app/cli/main"
"os"
"os/user"
Expand All @@ -24,26 +25,85 @@ func main() {
BinaryBuildHash = BinaryBuildHash[:7]
}

homeDir, err := user.Current()
if err != nil {
fmt.Println("Failed to initialise application. Error:")
fmt.Println(err)
os.Exit(1)
}
klogFolder := func() app.File {
f, err := determineKlogFolderLocation()
if err != nil {
fail(err, false)
}
return app.Join(f, app.KLOG_FOLDER_NAME)
}()

config := app.NewConfig(
app.ConfigFromStaticValues{NumCpus: runtime.NumCPU()},
app.ConfigFromEnvVars{GetVar: os.Getenv},
)
configFile := func() string {
c, err := readConfigFile(app.Join(klogFolder, app.CONFIG_FILE_NAME))
if err != nil {
fail(err, false)
}
return c
}()

exitCode, runErr := klog.Run(homeDir.HomeDir, app.Meta{
config := func() app.Config {
c, err := app.NewConfig(
app.FromStaticValues{NumCpus: runtime.NumCPU()},
app.FromEnvVars{GetVar: os.Getenv},
app.FromConfigFile{FileContents: configFile},
)
if err != nil {
fail(err, false)
}
return c
}()

err := klog.Run(klogFolder, app.Meta{
Specification: specification,
License: license,
Version: BinaryVersion,
SrcHash: BinaryBuildHash,
}, config, os.Args[1:])
if runErr != nil {
fmt.Println(runErr)
if err != nil {
fail(err, config.IsDebug.Value())
}
}

// fail terminates the process with an error.
func fail(e error, isDebug bool) {
exitCode := -1
if e != nil {
fmt.Println(lib.PrettifyError(e, isDebug))
if appErr, isAppError := e.(app.Error); isAppError {
exitCode = appErr.Code().ToInt()
}
}
os.Exit(exitCode)
}

// readConfigFile reads the config file from disk, if present.
// If not present, it returns empty string.
func readConfigFile(location app.File) (string, error) {
contents, rErr := app.ReadFile(location)
if rErr != nil {
if rErr.Code() == app.NO_SUCH_FILE {
return "", nil
}
return "", rErr
}
return contents, nil
}

// determineKlogFolderLocation returns the location where the `.klog` folder should be place.
// This is determined by following this lookup precedence:
// - $KLOG_FOLDER_LOCATION, if set
// - $XDG_CONFIG_HOME, if set
// - The default home folder, e.g. `~`
func determineKlogFolderLocation() (app.File, error) {
location := os.Getenv("KLOG_FOLDER_LOCATION")
if os.Getenv("XDG_CONFIG_HOME") != "" {
location = os.Getenv("XDG_CONFIG_HOME")
} else if location == "" {
homeDir, hErr := user.Current()
if hErr != nil {
return nil, hErr
}
location = homeDir.HomeDir
}
return app.NewFile(location)
}
36 changes: 36 additions & 0 deletions klog/app/cli/config.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
package cli

import (
"github.com/jotaen/klog/klog/app"
"github.com/jotaen/klog/klog/app/cli/lib"
"github.com/jotaen/klog/klog/app/cli/lib/terminalformat"
)

type Config struct {
ConfigFilePath bool `name:"file-path" help:"Prints the path to your config file"`
}

func (opt *Config) Help() string {
return `You are able to configure some of klog’s behaviour via a YAML file in your ` + "`" + app.KLOG_FOLDER_NAME + "`" + ` folder. (Run ` + "`" + `klog config --file-path` + "`" + ` to print the exact location.)

If you run ` + "`" + `klog config` + "`" + `, you can learn about the supported YAML properties in the file, and you also see what values are in effect at the moment.

Note: the output of the command does not print the actual file. You may, however, use the output as template for setting up the file, as its YAML-formatted.`
}

func (opt *Config) Run(ctx app.Context) app.Error {
if opt.ConfigFilePath {
ctx.Print(app.Join(ctx.KlogFolder(), app.CONFIG_FILE_NAME).Path() + "\n")
return nil
}
for i, e := range app.CONFIG_FILE_ENTRIES {
ctx.Print(lib.Subdued.Format(lib.Reflower.Reflow(e.Description+"\n"+e.Instructions, "# ")))
ctx.Print("\n")
ctx.Print(lib.Red.Format(e.Name) + `: ` + terminalformat.Style{Color: "227"}.Format(e.Value(ctx.Config())))
if i < len(app.CONFIG_FILE_ENTRIES)-1 {
ctx.Print("\n\n")
}
}
ctx.Print("\n")
return nil
}
11 changes: 7 additions & 4 deletions klog/app/cli/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,12 +25,15 @@ func (opt *Create) Run(ctx app.Context) app.Error {
opt.NoStyleArgs.Apply(&ctx)
date, isAutoDate := opt.AtDate(ctx.Now())
atDate := reconciling.NewStyled[klog.Date](date, isAutoDate)
additionalData := reconciling.AdditionalData{ShouldTotal: opt.ShouldTotal, Summary: opt.Summary}
if additionalData.ShouldTotal == nil {
ctx.Config().DefaultShouldTotal.Map(func(s klog.ShouldTotal) {
additionalData.ShouldTotal = s
})
}
return lib.Reconcile(ctx, lib.ReconcileOpts{OutputFileArgs: opt.OutputFileArgs, WarnArgs: opt.WarnArgs},
[]reconciling.Creator{
reconciling.NewReconcilerForNewRecord(
atDate,
reconciling.AdditionalData{ShouldTotal: opt.ShouldTotal, Summary: opt.Summary},
),
reconciling.NewReconcilerForNewRecord(atDate, additionalData),
},

func(reconciler *reconciling.Reconciler) (*reconciling.Result, error) {
Expand Down
50 changes: 50 additions & 0 deletions klog/app/cli/create_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -57,3 +57,53 @@ This is a
new record!
`, state.writtenFileContents)
}

func TestCreateWithFileConfig(t *testing.T) {
// With should-total from config file
{
state, err := NewTestingContext()._SetRecords(`
1920-02-01
4h33m

1920-02-02
9:00-12:00
`)._SetFileConfig(`
default_should_total: 30m!
`)._SetNow(1920, 2, 3, 15, 24)._Run((&Create{}).Run)
require.Nil(t, err)
assert.Equal(t, `
1920-02-01
4h33m

1920-02-02
9:00-12:00

1920-02-03 (30m!)
`, state.writtenFileContents)
}

// --should-total flag trumps should-total from config file
{
state, err := NewTestingContext()._SetRecords(`
1920-02-01
4h33m

1920-02-02
9:00-12:00
`)._SetFileConfig(`
default_should_total: 30m!
`)._SetNow(1920, 2, 3, 15, 24)._Run((&Create{
ShouldTotal: klog.NewShouldTotal(5, 55),
}).Run)
require.Nil(t, err)
assert.Equal(t, `
1920-02-01
4h33m

1920-02-02
9:00-12:00

1920-02-03 (5h55m!)
`, state.writtenFileContents)
}
}
1 change: 1 addition & 0 deletions klog/app/cli/index.go
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ type Cli struct {
// Misc
Edit Edit `cmd:"" group:"Misc" help:"Opens a file or bookmark in your editor"`
Goto Goto `cmd:"" group:"Misc" help:"Opens the file explorer at the given location"`
Config Config `cmd:"" group:"Misc" hidden:"" help:"Prints the current configuration"` // Still experimental / WIP
Json Json `cmd:"" group:"Misc" help:"Converts records to JSON"`
Info Info `cmd:"" group:"Misc" default:"withargs" help:"Displays meta info about klog"`
Version Version `cmd:"" group:"Misc" help:"Prints version info and check for updates"`
Expand Down
10 changes: 7 additions & 3 deletions klog/app/cli/info.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ const DESCRIPTION = "klog: command line app for time tracking with plain-text fi
"Documentation online at " + KLOG_WEBSITE_URL

type Info struct {
Version bool `short:"v" name:"version" help:"Alias for 'klog version'"`
Spec bool `name:"spec" help:"Print file format specification"`
License bool `name:"license" help:"Print license"`
Version bool `short:"v" name:"version" help:"Alias for 'klog version'"`
Spec bool `name:"spec" help:"Print file format specification"`
License bool `name:"license" help:"Print license"`
KlogFolder bool `name:"klog-folder" help:"Print path of .klog folder"`
}

func (opt *Info) Help() string {
Expand All @@ -28,6 +29,9 @@ func (opt *Info) Run(ctx app.Context) app.Error {
} else if opt.License {
ctx.Print(ctx.Meta().License + "\n")
return nil
} else if opt.KlogFolder {
ctx.Print(ctx.KlogFolder().Path() + "\n")
return nil
}
ctx.Print(DESCRIPTION + "\n")
return nil
Expand Down
6 changes: 5 additions & 1 deletion klog/app/cli/lib/args.go
Original file line number Diff line number Diff line change
Expand Up @@ -43,7 +43,7 @@ type AtDateAndTimeArgs struct {
Round service.Rounding `name:"round" short:"r" help:"Round time to nearest multiple of 5m, 10m, 15m, 30m, or 60m / 1h"`
}

func (args *AtDateAndTimeArgs) AtTime(now gotime.Time) (klog.Time, bool, app.Error) {
func (args *AtDateAndTimeArgs) AtTime(now gotime.Time, config app.Config) (klog.Time, bool, app.Error) {
if args.Time != nil {
return args.Time, false, nil
}
Expand All @@ -52,6 +52,10 @@ func (args *AtDateAndTimeArgs) AtTime(now gotime.Time) (klog.Time, bool, app.Err
time := klog.NewTimeFromGo(now)
if args.Round != nil {
time = service.RoundToNearest(time, args.Round)
} else {
config.DefaultRounding.Map(func(r service.Rounding) {
time = service.RoundToNearest(time, r)
})
}
if today.IsEqualTo(date) {
return time, true, nil
Expand Down
7 changes: 4 additions & 3 deletions klog/app/cli/lib/prettifier.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,10 @@ import (
"strings"
)

var Reflower = terminalformat.NewReflower(60, "\n")

// PrettifyError turns an error into a coloured and well-structured form.
func PrettifyError(err error, isDebug bool) error {
reflower := terminalformat.NewReflower(60, "\n")
switch e := err.(type) {
case app.ParserErrors:
message := ""
Expand All @@ -34,13 +35,13 @@ func PrettifyError(err error, isDebug bool) error {
) + "\n"
message += fmt.Sprintf(
terminalformat.Style{Color: "227"}.Format("%s"),
reflower.Reflow(e.Message(), INDENT),
Reflower.Reflow(e.Message(), INDENT),
) + "\n\n"
}
return errors.New(message)
case app.Error:
message := "Error: " + e.Error() + "\n"
message += reflower.Reflow(e.Details(), "")
message += Reflower.Reflow(e.Details(), "")
if isDebug && e.Original() != nil {
message += "\n\nOriginal Error:\n" + e.Original().Error()
}
Expand Down
17 changes: 4 additions & 13 deletions klog/app/cli/main/cli.go
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ import (
"reflect"
)

func Run(homeDir string, meta app.Meta, config app.Config, args []string) (int, error) {
func Run(homeDir app.File, meta app.Meta, config app.Config, args []string) error {
kongApp, nErr := kong.New(
&cli.Cli{},
kong.Name("klog"),
Expand Down Expand Up @@ -59,7 +59,7 @@ func Run(homeDir string, meta app.Meta, config app.Config, args []string) (int,
}),
)
if nErr != nil {
return -1, nErr
return nErr
}

ctx := app.NewContext(
Expand All @@ -76,18 +76,9 @@ func Run(homeDir string, meta app.Meta, config app.Config, args []string) (int,
kongcompletion.Register(kongApp, kongcompletion.WithPredictors(CompletionPredictors(ctx)))
kongCtx, cErr := kongApp.Parse(args)
if cErr != nil {
return -1, cErr
return cErr
}
kongCtx.BindTo(ctx, (*app.Context)(nil))

rErr := kongCtx.Run()
if rErr != nil {
ctx.Print(lib.PrettifyError(rErr, config.IsDebug.Value()).Error() + "\n")
if appErr, isAppError := rErr.(app.Error); isAppError {
return int(appErr.Code()), nil
} else {
return -1, rErr
}
}
return 0, nil
return kongCtx.Run()
}
4 changes: 2 additions & 2 deletions klog/app/cli/main/testutil_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -33,15 +33,15 @@ func (e *Env) run(invocation ...[]string) []string {
r, w, _ := os.Pipe()
os.Stdout = w

code, runErr := Run(tmpDir, app.Meta{
runErr := Run(app.NewFileOrPanic(tmpDir), app.Meta{
Specification: "[Specification text]",
License: "[License text]",
Version: "v0.0",
SrcHash: "abc1234",
}, app.NewDefaultConfig(), args)

_ = w.Close()
if runErr != nil || code != 0 {
if runErr != nil {
outs[i] = runErr.Error()
continue
}
Expand Down
8 changes: 6 additions & 2 deletions klog/app/cli/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,16 +24,20 @@ func (opt *Start) Run(ctx app.Context) app.Error {
opt.NoStyleArgs.Apply(&ctx)
now := ctx.Now()
date, isAutoDate := opt.AtDate(now)
time, isAutoTime, err := opt.AtTime(now)
time, isAutoTime, err := opt.AtTime(now, ctx.Config())
if err != nil {
return err
}
atDate := reconciling.NewStyled[klog.Date](date, isAutoDate)
startTime := reconciling.NewStyled[klog.Time](time, isAutoTime)
additionalData := reconciling.AdditionalData{}
ctx.Config().DefaultShouldTotal.Map(func(s klog.ShouldTotal) {
additionalData.ShouldTotal = s
})
return lib.Reconcile(ctx, lib.ReconcileOpts{OutputFileArgs: opt.OutputFileArgs, WarnArgs: opt.WarnArgs},
[]reconciling.Creator{
reconciling.NewReconcilerAtRecord(atDate.Value),
reconciling.NewReconcilerForNewRecord(atDate, reconciling.AdditionalData{}),
reconciling.NewReconcilerForNewRecord(atDate, additionalData),
},

func(reconciler *reconciling.Reconciler) (*reconciling.Result, error) {
Expand Down
Loading