Skip to content

Commit

Permalink
Add user-defined configuration support (dominikbraun#1)
Browse files Browse the repository at this point in the history
  • Loading branch information
dominikbraun authored May 12, 2021
1 parent f158bab commit 8296ff3
Show file tree
Hide file tree
Showing 19 changed files with 369 additions and 254 deletions.
4 changes: 4 additions & 0 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,10 @@ COPY --from=downloader ["/bin/timetrace", "/bin/timetrace"]

# Create a symlink for musl, see https://stackoverflow.com/a/35613430.
RUN mkdir /lib64 && ln -s /lib/libc.musl-x86_64.so.1 /lib64/ld-linux-x86-64.so.2

RUN mkdir /etc/timetrace && \
echo "store: '/data'" >> /etc/timetrace/config.yml

RUN mkdir /data

ENTRYPOINT ["/bin/timetrace"]
8 changes: 4 additions & 4 deletions cli/create.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/spf13/cobra"
)

func createCommand() *cobra.Command {
func createCommand(t *core.Timetrace) *cobra.Command {
create := &cobra.Command{
Use: "create",
Short: "Create a new resource",
Expand All @@ -16,12 +16,12 @@ func createCommand() *cobra.Command {
},
}

create.AddCommand(createProjectCommand())
create.AddCommand(createProjectCommand(t))

return create
}

func createProjectCommand() *cobra.Command {
func createProjectCommand(t *core.Timetrace) *cobra.Command {
createProject := &cobra.Command{
Use: "project <KEY>",
Short: "Create a new project",
Expand All @@ -33,7 +33,7 @@ func createProjectCommand() *cobra.Command {
Key: key,
}

if err := core.SaveProject(project, false); err != nil {
if err := t.SaveProject(project, false); err != nil {
out.Err("Failed to create project: %s", err.Error())
return
}
Expand Down
8 changes: 4 additions & 4 deletions cli/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/spf13/cobra"
)

func deleteCommand() *cobra.Command {
func deleteCommand(t *core.Timetrace) *cobra.Command {
delete := &cobra.Command{
Use: "delete",
Short: "Delete a resource",
Expand All @@ -16,12 +16,12 @@ func deleteCommand() *cobra.Command {
},
}

delete.AddCommand(deleteProjectCommand())
delete.AddCommand(deleteProjectCommand(t))

return delete
}

func deleteProjectCommand() *cobra.Command {
func deleteProjectCommand(t *core.Timetrace) *cobra.Command {
deleteProject := &cobra.Command{
Use: "project <KEY>",
Short: "Delete a project",
Expand All @@ -33,7 +33,7 @@ func deleteProjectCommand() *cobra.Command {
Key: key,
}

if err := core.DeleteProject(project); err != nil {
if err := t.DeleteProject(project); err != nil {
out.Err("Failed to delete %s", err.Error())
return
}
Expand Down
8 changes: 4 additions & 4 deletions cli/edit.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"github.com/spf13/cobra"
)

func editCommand() *cobra.Command {
func editCommand(t *core.Timetrace) *cobra.Command {
edit := &cobra.Command{
Use: "edit",
Short: "Edit a resource",
Expand All @@ -16,12 +16,12 @@ func editCommand() *cobra.Command {
},
}

edit.AddCommand(editProjectCommand())
edit.AddCommand(editProjectCommand(t))

return edit
}

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

if err := core.EditProject(key); err != nil {
if err := t.EditProject(key); err != nil {
out.Err("Failed to edit project: %s", err.Error())
return
}
Expand Down
27 changes: 19 additions & 8 deletions cli/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,18 @@ package cli
import (
"time"

"github.com/dominikbraun/timetrace/config"
"github.com/dominikbraun/timetrace/core"
"github.com/dominikbraun/timetrace/out"

"github.com/spf13/cobra"
)

func getCommand() *cobra.Command {
const (
defaultRecordArgLayout = "2006-01-02-15-04"
)

func getCommand(t *core.Timetrace) *cobra.Command {
get := &cobra.Command{
Use: "get",
Short: "Display a resource",
Expand All @@ -18,21 +23,21 @@ func getCommand() *cobra.Command {
},
}

get.AddCommand(getProjectCommand())
get.AddCommand(getRecordCommand())
get.AddCommand(getProjectCommand(t))
get.AddCommand(getRecordCommand(t))

return get
}

func getProjectCommand() *cobra.Command {
func getProjectCommand(t *core.Timetrace) *cobra.Command {
getProject := &cobra.Command{
Use: "project <KEY>",
Short: "Display a project",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
key := args[0]

project, err := core.LoadProject(key)
project, err := t.LoadProject(key)
if err != nil {
out.Err("Failed to get project: %s", key)
return
Expand All @@ -45,19 +50,25 @@ func getProjectCommand() *cobra.Command {
return getProject
}

func getRecordCommand() *cobra.Command {
func getRecordCommand(t *core.Timetrace) *cobra.Command {
getRecord := &cobra.Command{
Use: "record YYYY-MM-DD-HH-MM",
Short: "Display a record",
Args: cobra.ExactArgs(1),
Run: func(cmd *cobra.Command, args []string) {
start, err := time.Parse("2006-01-02-15-04", args[0])
layout := defaultRecordArgLayout

if config.Get().Use12Hours {
layout = "2006-01-02-03-04PM"
}

start, err := time.Parse(layout, args[0])
if err != nil {
out.Err("Failed to parse date argument: %s", err.Error())
return
}

record, err := core.LoadRecord(start)
record, err := t.LoadRecord(start)
if err != nil {
out.Err("Failed to read record: %s", err.Error())
return
Expand Down
20 changes: 10 additions & 10 deletions cli/root.go
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package cli

import (
"github.com/dominikbraun/timetrace/fs"
"github.com/dominikbraun/timetrace/core"

"github.com/spf13/cobra"
)
Expand All @@ -11,28 +11,28 @@ const (
defaultBool = "no"
)

func RootCommand(version string) *cobra.Command {
func RootCommand(t *core.Timetrace, version string) *cobra.Command {
root := &cobra.Command{
Use: "timetrace",
Short: "timetrace is a simple CLI for tracking your working time.",
Version: version,
SilenceUsage: true,
SilenceErrors: true,
PersistentPreRunE: func(cmd *cobra.Command, args []string) error {
return fs.EnsureDirectories()
return t.EnsureDirectories()
},
Run: func(cmd *cobra.Command, args []string) {
_ = cmd.Help()
},
}

root.AddCommand(createCommand())
root.AddCommand(getCommand())
root.AddCommand(editCommand())
root.AddCommand(deleteCommand())
root.AddCommand(startCommand())
root.AddCommand(statusCommand())
root.AddCommand(stopCommand())
root.AddCommand(createCommand(t))
root.AddCommand(getCommand(t))
root.AddCommand(editCommand(t))
root.AddCommand(deleteCommand(t))
root.AddCommand(startCommand(t))
root.AddCommand(statusCommand(t))
root.AddCommand(stopCommand(t))
root.AddCommand(versionCommand(version))

return root
Expand Down
4 changes: 2 additions & 2 deletions cli/start.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ type startOptions struct {
isBillable bool
}

func startCommand() *cobra.Command {
func startCommand(t *core.Timetrace) *cobra.Command {
var options startOptions

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

if err := core.Start(projectKey, options.isBillable); err != nil {
if err := t.Start(projectKey, options.isBillable); err != nil {
out.Err("Failed to start tracking: %s", err.Error())
return
}
Expand Down
4 changes: 2 additions & 2 deletions cli/status.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,12 +7,12 @@ import (
"github.com/spf13/cobra"
)

func statusCommand() *cobra.Command {
func statusCommand(t *core.Timetrace) *cobra.Command {
status := &cobra.Command{
Use: "status",
Short: "Display the current tracking status",
Run: func(cmd *cobra.Command, args []string) {
report, err := core.Status()
report, err := t.Status()
if err != nil {
out.Err("Failed to obtain status: %s", err.Error())
return
Expand Down
4 changes: 2 additions & 2 deletions cli/stop.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,13 +7,13 @@ import (
"github.com/spf13/cobra"
)

func stopCommand() *cobra.Command {
func stopCommand(t *core.Timetrace) *cobra.Command {
stop := &cobra.Command{
Use: "stop",
Short: "Stop tracking your time",
Args: cobra.ExactArgs(0),
Run: func(cmd *cobra.Command, args []string) {
if err := core.Stop(); err != nil {
if err := t.Stop(); err != nil {
out.Err("Failed to stop tracking: %s", err.Error())
return
}
Expand Down
29 changes: 26 additions & 3 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,13 @@ import (
)

type Config struct {
Root string `json:"root"`
Use12Hours bool `json:"use_12_hours"`
Store string `json:"store"`
Use12Hours bool `json:"use12hours"`
Editor string `json:"editor"`
}

var cached *Config

// FromFile reads a configuration file called config.yml and returns it as a
// Config instance. If no configuration file is found, nil and no error will be
// returned. The configuration must live in one of the following directories:
Expand Down Expand Up @@ -40,5 +42,26 @@ func FromFile() (*Config, error) {
return nil, err
}

return &config, nil
cached = &config

return cached, nil
}

// Get returns the parsed configuration. The fields of this configuration either
// contain values specified by the user or the zero value of the respective data
// type, e.g. "" for an un-configured string.
//
// Using Get over FromFile avoids the config file from being parsed each time
// the config is needed.
func Get() *Config {
if cached != nil {
return cached
}

config, err := FromFile()
if err != nil {
return &Config{}
}

return config
}
24 changes: 14 additions & 10 deletions core/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import (
"os"
"os/exec"

"github.com/dominikbraun/timetrace/fs"
"github.com/dominikbraun/timetrace/config"
)

const (
Expand All @@ -25,8 +25,8 @@ type Project struct {

// LoadProject loads the project with the given key. Returns ErrProjectNotFound
// if the project cannot be found.
func LoadProject(key string) (*Project, error) {
path := fs.ProjectFilepath(key)
func (t *Timetrace) LoadProject(key string) (*Project, error) {
path := t.fs.ProjectFilepath(key)

file, err := ioutil.ReadFile(path)
if err != nil {
Expand All @@ -47,8 +47,8 @@ func LoadProject(key string) (*Project, error) {

// SaveProject persists the given project. Returns ErrProjectAlreadyExists if
// the project already exists and saving isn't forced.
func SaveProject(project Project, force bool) error {
path := fs.ProjectFilepath(project.Key)
func (t *Timetrace) SaveProject(project Project, force bool) error {
path := t.fs.ProjectFilepath(project.Key)

if _, err := os.Stat(path); os.IsExist(err) && !force {
return ErrProjectAlreadyExists
Expand All @@ -70,13 +70,13 @@ func SaveProject(project Project, force bool) error {
}

// EditProject opens the project file in the preferred or default editor.
func EditProject(projectKey string) error {
if _, err := LoadProject(projectKey); err != nil {
func (t *Timetrace) EditProject(projectKey string) error {
if _, err := t.LoadProject(projectKey); err != nil {
return err
}

editor := editorFromEnvironment()
path := fs.ProjectFilepath(projectKey)
path := t.fs.ProjectFilepath(projectKey)

cmd := exec.Command(editor, path)
cmd.Stdin = os.Stdin
Expand All @@ -88,8 +88,8 @@ func EditProject(projectKey string) error {

// DeleteProject removes the given project. Returns ErrProjectNotFound if the
// project doesn't exist.
func DeleteProject(project Project) error {
path := fs.ProjectFilepath(project.Key)
func (t *Timetrace) DeleteProject(project Project) error {
path := t.fs.ProjectFilepath(project.Key)

if _, err := os.Stat(path); os.IsNotExist(err) {
return ErrProjectNotFound
Expand All @@ -99,6 +99,10 @@ func DeleteProject(project Project) error {
}

func editorFromEnvironment() string {
if config.Get().Editor != "" {
return config.Get().Editor
}

if editor := os.Getenv("EDITOR"); editor != "" {
return editor
}
Expand Down
Loading

0 comments on commit 8296ff3

Please sign in to comment.