Skip to content

Commit 8296ff3

Browse files
authored
Add user-defined configuration support (dominikbraun#1)
1 parent f158bab commit 8296ff3

File tree

19 files changed

+369
-254
lines changed

19 files changed

+369
-254
lines changed

Dockerfile

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,10 @@ COPY --from=downloader ["/bin/timetrace", "/bin/timetrace"]
2727

2828
# Create a symlink for musl, see https://stackoverflow.com/a/35613430.
2929
RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2
30+
31+
RUN mkdir /etc/timetrace && \
32+
echo "store: '/data'" >> /etc/timetrace/config.yml
33+
3034
RUN mkdir /data
3135

3236
ENTRYPOINT ["/bin/timetrace"]

cli/create.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"github.com/spf13/cobra"
88
)
99

10-
func createCommand() *cobra.Command {
10+
func createCommand(t *core.Timetrace) *cobra.Command {
1111
create := &cobra.Command{
1212
Use: "create",
1313
Short: "Create a new resource",
@@ -16,12 +16,12 @@ func createCommand() *cobra.Command {
1616
},
1717
}
1818

19-
create.AddCommand(createProjectCommand())
19+
create.AddCommand(createProjectCommand(t))
2020

2121
return create
2222
}
2323

24-
func createProjectCommand() *cobra.Command {
24+
func createProjectCommand(t *core.Timetrace) *cobra.Command {
2525
createProject := &cobra.Command{
2626
Use: "project <KEY>",
2727
Short: "Create a new project",
@@ -33,7 +33,7 @@ func createProjectCommand() *cobra.Command {
3333
Key: key,
3434
}
3535

36-
if err := core.SaveProject(project, false); err != nil {
36+
if err := t.SaveProject(project, false); err != nil {
3737
out.Err("Failed to create project: %s", err.Error())
3838
return
3939
}

cli/delete.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"github.com/spf13/cobra"
88
)
99

10-
func deleteCommand() *cobra.Command {
10+
func deleteCommand(t *core.Timetrace) *cobra.Command {
1111
delete := &cobra.Command{
1212
Use: "delete",
1313
Short: "Delete a resource",
@@ -16,12 +16,12 @@ func deleteCommand() *cobra.Command {
1616
},
1717
}
1818

19-
delete.AddCommand(deleteProjectCommand())
19+
delete.AddCommand(deleteProjectCommand(t))
2020

2121
return delete
2222
}
2323

24-
func deleteProjectCommand() *cobra.Command {
24+
func deleteProjectCommand(t *core.Timetrace) *cobra.Command {
2525
deleteProject := &cobra.Command{
2626
Use: "project <KEY>",
2727
Short: "Delete a project",
@@ -33,7 +33,7 @@ func deleteProjectCommand() *cobra.Command {
3333
Key: key,
3434
}
3535

36-
if err := core.DeleteProject(project); err != nil {
36+
if err := t.DeleteProject(project); err != nil {
3737
out.Err("Failed to delete %s", err.Error())
3838
return
3939
}

cli/edit.go

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ import (
77
"github.com/spf13/cobra"
88
)
99

10-
func editCommand() *cobra.Command {
10+
func editCommand(t *core.Timetrace) *cobra.Command {
1111
edit := &cobra.Command{
1212
Use: "edit",
1313
Short: "Edit a resource",
@@ -16,12 +16,12 @@ func editCommand() *cobra.Command {
1616
},
1717
}
1818

19-
edit.AddCommand(editProjectCommand())
19+
edit.AddCommand(editProjectCommand(t))
2020

2121
return edit
2222
}
2323

24-
func editProjectCommand() *cobra.Command {
24+
func editProjectCommand(t *core.Timetrace) *cobra.Command {
2525
editProject := &cobra.Command{
2626
Use: "project <KEY>",
2727
Short: "Edit a project",
@@ -30,7 +30,7 @@ func editProjectCommand() *cobra.Command {
3030
key := args[0]
3131
out.Info("Opening %s in default editor", key)
3232

33-
if err := core.EditProject(key); err != nil {
33+
if err := t.EditProject(key); err != nil {
3434
out.Err("Failed to edit project: %s", err.Error())
3535
return
3636
}

cli/get.go

Lines changed: 19 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -3,13 +3,18 @@ package cli
33
import (
44
"time"
55

6+
"github.com/dominikbraun/timetrace/config"
67
"github.com/dominikbraun/timetrace/core"
78
"github.com/dominikbraun/timetrace/out"
89

910
"github.com/spf13/cobra"
1011
)
1112

12-
func getCommand() *cobra.Command {
13+
const (
14+
defaultRecordArgLayout = "2006-01-02-15-04"
15+
)
16+
17+
func getCommand(t *core.Timetrace) *cobra.Command {
1318
get := &cobra.Command{
1419
Use: "get",
1520
Short: "Display a resource",
@@ -18,21 +23,21 @@ func getCommand() *cobra.Command {
1823
},
1924
}
2025

21-
get.AddCommand(getProjectCommand())
22-
get.AddCommand(getRecordCommand())
26+
get.AddCommand(getProjectCommand(t))
27+
get.AddCommand(getRecordCommand(t))
2328

2429
return get
2530
}
2631

27-
func getProjectCommand() *cobra.Command {
32+
func getProjectCommand(t *core.Timetrace) *cobra.Command {
2833
getProject := &cobra.Command{
2934
Use: "project <KEY>",
3035
Short: "Display a project",
3136
Args: cobra.ExactArgs(1),
3237
Run: func(cmd *cobra.Command, args []string) {
3338
key := args[0]
3439

35-
project, err := core.LoadProject(key)
40+
project, err := t.LoadProject(key)
3641
if err != nil {
3742
out.Err("Failed to get project: %s", key)
3843
return
@@ -45,19 +50,25 @@ func getProjectCommand() *cobra.Command {
4550
return getProject
4651
}
4752

48-
func getRecordCommand() *cobra.Command {
53+
func getRecordCommand(t *core.Timetrace) *cobra.Command {
4954
getRecord := &cobra.Command{
5055
Use: "record YYYY-MM-DD-HH-MM",
5156
Short: "Display a record",
5257
Args: cobra.ExactArgs(1),
5358
Run: func(cmd *cobra.Command, args []string) {
54-
start, err := time.Parse("2006-01-02-15-04", args[0])
59+
layout := defaultRecordArgLayout
60+
61+
if config.Get().Use12Hours {
62+
layout = "2006-01-02-03-04PM"
63+
}
64+
65+
start, err := time.Parse(layout, args[0])
5566
if err != nil {
5667
out.Err("Failed to parse date argument: %s", err.Error())
5768
return
5869
}
5970

60-
record, err := core.LoadRecord(start)
71+
record, err := t.LoadRecord(start)
6172
if err != nil {
6273
out.Err("Failed to read record: %s", err.Error())
6374
return

cli/root.go

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package cli
22

33
import (
4-
"github.com/dominikbraun/timetrace/fs"
4+
"github.com/dominikbraun/timetrace/core"
55

66
"github.com/spf13/cobra"
77
)
@@ -11,28 +11,28 @@ const (
1111
defaultBool = "no"
1212
)
1313

14-
func RootCommand(version string) *cobra.Command {
14+
func RootCommand(t *core.Timetrace, version string) *cobra.Command {
1515
root := &cobra.Command{
1616
Use: "timetrace",
1717
Short: "timetrace is a simple CLI for tracking your working time.",
1818
Version: version,
1919
SilenceUsage: true,
2020
SilenceErrors: true,
2121
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
22-
return fs.EnsureDirectories()
22+
return t.EnsureDirectories()
2323
},
2424
Run: func(cmd *cobra.Command, args []string) {
2525
_ = cmd.Help()
2626
},
2727
}
2828

29-
root.AddCommand(createCommand())
30-
root.AddCommand(getCommand())
31-
root.AddCommand(editCommand())
32-
root.AddCommand(deleteCommand())
33-
root.AddCommand(startCommand())
34-
root.AddCommand(statusCommand())
35-
root.AddCommand(stopCommand())
29+
root.AddCommand(createCommand(t))
30+
root.AddCommand(getCommand(t))
31+
root.AddCommand(editCommand(t))
32+
root.AddCommand(deleteCommand(t))
33+
root.AddCommand(startCommand(t))
34+
root.AddCommand(statusCommand(t))
35+
root.AddCommand(stopCommand(t))
3636
root.AddCommand(versionCommand(version))
3737

3838
return root

cli/start.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ type startOptions struct {
1111
isBillable bool
1212
}
1313

14-
func startCommand() *cobra.Command {
14+
func startCommand(t *core.Timetrace) *cobra.Command {
1515
var options startOptions
1616

1717
start := &cobra.Command{
@@ -21,7 +21,7 @@ func startCommand() *cobra.Command {
2121
Run: func(cmd *cobra.Command, args []string) {
2222
projectKey := args[0]
2323

24-
if err := core.Start(projectKey, options.isBillable); err != nil {
24+
if err := t.Start(projectKey, options.isBillable); err != nil {
2525
out.Err("Failed to start tracking: %s", err.Error())
2626
return
2727
}

cli/status.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,12 +7,12 @@ import (
77
"github.com/spf13/cobra"
88
)
99

10-
func statusCommand() *cobra.Command {
10+
func statusCommand(t *core.Timetrace) *cobra.Command {
1111
status := &cobra.Command{
1212
Use: "status",
1313
Short: "Display the current tracking status",
1414
Run: func(cmd *cobra.Command, args []string) {
15-
report, err := core.Status()
15+
report, err := t.Status()
1616
if err != nil {
1717
out.Err("Failed to obtain status: %s", err.Error())
1818
return

cli/stop.go

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,13 @@ import (
77
"github.com/spf13/cobra"
88
)
99

10-
func stopCommand() *cobra.Command {
10+
func stopCommand(t *core.Timetrace) *cobra.Command {
1111
stop := &cobra.Command{
1212
Use: "stop",
1313
Short: "Stop tracking your time",
1414
Args: cobra.ExactArgs(0),
1515
Run: func(cmd *cobra.Command, args []string) {
16-
if err := core.Stop(); err != nil {
16+
if err := t.Stop(); err != nil {
1717
out.Err("Failed to stop tracking: %s", err.Error())
1818
return
1919
}

config/config.go

Lines changed: 26 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,13 @@ import (
66
)
77

88
type Config struct {
9-
Root string `json:"root"`
10-
Use12Hours bool `json:"use_12_hours"`
9+
Store string `json:"store"`
10+
Use12Hours bool `json:"use12hours"`
1111
Editor string `json:"editor"`
1212
}
1313

14+
var cached *Config
15+
1416
// FromFile reads a configuration file called config.yml and returns it as a
1517
// Config instance. If no configuration file is found, nil and no error will be
1618
// returned. The configuration must live in one of the following directories:
@@ -40,5 +42,26 @@ func FromFile() (*Config, error) {
4042
return nil, err
4143
}
4244

43-
return &config, nil
45+
cached = &config
46+
47+
return cached, nil
48+
}
49+
50+
// Get returns the parsed configuration. The fields of this configuration either
51+
// contain values specified by the user or the zero value of the respective data
52+
// type, e.g. "" for an un-configured string.
53+
//
54+
// Using Get over FromFile avoids the config file from being parsed each time
55+
// the config is needed.
56+
func Get() *Config {
57+
if cached != nil {
58+
return cached
59+
}
60+
61+
config, err := FromFile()
62+
if err != nil {
63+
return &Config{}
64+
}
65+
66+
return config
4467
}

0 commit comments

Comments
 (0)