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

Plugin support #104

Draft
wants to merge 9 commits into
base: main
Choose a base branch
from
Draft
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
3 changes: 3 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -20,3 +20,6 @@ target

# Dependency directories (remove the comment below to include it)
# vendor/

plugins/*
!plugins/example
4 changes: 2 additions & 2 deletions cli/delete.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ func deleteRecordCommand(t *core.Timetrace) *cobra.Command {
return
}

showRecord(record, t.Formatter())
showRecord(&record, t.Formatter())
if !confirmed {
if !askForConfirmation() {
out.Info("Record NOT deleted.")
Expand All @@ -119,7 +119,7 @@ func deleteRecordCommand(t *core.Timetrace) *cobra.Command {
return
}

if err := t.DeleteRecord(*record); err != nil {
if err := t.DeleteRecord(record); err != nil {
out.Err("Failed to delete %s", err.Error())
return
}
Expand Down
2 changes: 1 addition & 1 deletion cli/get.go
Original file line number Diff line number Diff line change
Expand Up @@ -68,7 +68,7 @@ func getRecordCommand(t *core.Timetrace) *cobra.Command {
return
}

showRecord(record, t.Formatter())
showRecord(&record, t.Formatter())
},
}

Expand Down
16 changes: 8 additions & 8 deletions cli/list.go
Original file line number Diff line number Diff line change
Expand Up @@ -109,7 +109,7 @@ func listRecordsCommand(t *core.Timetrace) *cobra.Command {

rows[i] = make([]string, 6)
rows[i][0] = strconv.Itoa(i + 1)
rows[i][1] = t.Formatter().RecordKey(record)
rows[i][1] = t.Formatter().RecordKey(&record)
rows[i][2] = record.Project.Key
rows[i][3] = t.Formatter().TimeString(record.Start)
rows[i][4] = end
Expand All @@ -133,8 +133,8 @@ func listRecordsCommand(t *core.Timetrace) *cobra.Command {
return listRecords
}

func filterBillableRecords(records []*core.Record) []*core.Record {
billableRecords := []*core.Record{}
func filterBillableRecords(records []core.Record) []core.Record {
billableRecords := []core.Record{}
for _, record := range records {
if record.IsBillable == true {
billableRecords = append(billableRecords, record)
Expand All @@ -143,8 +143,8 @@ func filterBillableRecords(records []*core.Record) []*core.Record {
return billableRecords
}

func filterProjectRecords(records []*core.Record, key string) []*core.Record {
projectRecords := []*core.Record{}
func filterProjectRecords(records []core.Record, key string) []core.Record {
projectRecords := []core.Record{}
for _, record := range records {
if record.Project.Key == key || record.Project.Parent() == key {
projectRecords = append(projectRecords, record)
Expand All @@ -153,8 +153,8 @@ func filterProjectRecords(records []*core.Record, key string) []*core.Record {
return projectRecords
}

func removeModules(allProjects []*core.Project) []*core.Project {
var parentProjects []*core.Project
func removeModules(allProjects []core.Project) []core.Project {
var parentProjects []core.Project
for _, p := range allProjects {
if !p.IsModule() {
parentProjects = append(parentProjects, p)
Expand All @@ -164,7 +164,7 @@ func removeModules(allProjects []*core.Project) []*core.Project {
return parentProjects
}

func getTotalTrackedTime(records []*core.Record) time.Duration {
func getTotalTrackedTime(records []core.Record) time.Duration {
var totalTime time.Duration
for _, record := range records {
if record.End != nil {
Expand Down
42 changes: 21 additions & 21 deletions cli/list_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,37 +12,37 @@ func TestFilterBillableRecords(t *testing.T) {

tt := []struct {
title string
records []*core.Record
expected []*core.Record
records []core.Record
expected []core.Record
}{
{
title: "all records are billable",
records: []*core.Record{
records: []core.Record{
{Project: &core.Project{Key: "a"}, IsBillable: true},
{Project: &core.Project{Key: "b"}, IsBillable: true},
},
expected: []*core.Record{
expected: []core.Record{
{Project: &core.Project{Key: "a"}, IsBillable: true},
{Project: &core.Project{Key: "b"}, IsBillable: true},
},
},
{
title: "no records are billable",
records: []*core.Record{
records: []core.Record{
{Project: &core.Project{Key: "a"}, IsBillable: false},
{Project: &core.Project{Key: "b"}, IsBillable: false},
},
expected: []*core.Record{},
expected: []core.Record{},
},
{
title: "half of records are billable",
records: []*core.Record{
records: []core.Record{
{Project: &core.Project{Key: "a"}, IsBillable: true},
{Project: &core.Project{Key: "b"}, IsBillable: true},
{Project: &core.Project{Key: "c"}, IsBillable: false},
{Project: &core.Project{Key: "d"}, IsBillable: false},
},
expected: []*core.Record{
expected: []core.Record{
{Project: &core.Project{Key: "a"}, IsBillable: true},
{Project: &core.Project{Key: "b"}, IsBillable: true},
},
Expand All @@ -62,49 +62,49 @@ func TestFilterProjectRecords(t *testing.T) {
tt := []struct {
title string
filter string
records []*core.Record
expected []*core.Record
records []core.Record
expected []core.Record
}{
{
title: "filter by project a",
filter: "a",
records: []*core.Record{
records: []core.Record{
{Project: &core.Project{Key: "a"}, IsBillable: false},
{Project: &core.Project{Key: "b"}, IsBillable: true},
},
expected: []*core.Record{
expected: []core.Record{
{Project: &core.Project{Key: "a"}, IsBillable: false},
},
},
{
title: "filter by project b",
filter: "b",
records: []*core.Record{
records: []core.Record{
{Project: &core.Project{Key: "a"}, IsBillable: false},
{Project: &core.Project{Key: "b"}, IsBillable: true},
},
expected: []*core.Record{
expected: []core.Record{
{Project: &core.Project{Key: "b"}, IsBillable: true},
},
},
{
title: "filter by project module@b",
filter: "b",
records: []*core.Record{
records: []core.Record{
{Project: &core.Project{Key: "module@b"}, IsBillable: false},
},
expected: []*core.Record{
expected: []core.Record{
{Project: &core.Project{Key: "module@b"}, IsBillable: false},
},
},
{
title: "no records found",
filter: "a",
records: []*core.Record{
records: []core.Record{
{Project: &core.Project{Key: "c"}, IsBillable: false},
{Project: &core.Project{Key: "d"}, IsBillable: false},
},
expected: []*core.Record{},
expected: []core.Record{},
},
}

Expand All @@ -118,10 +118,10 @@ func TestFilterProjectRecords(t *testing.T) {

func TestTotalTrackedTime(t *testing.T) {
tt := []struct {
records []*core.Record
records []core.Record
expected time.Duration
}{
{records: []*core.Record{
{records: []core.Record{
{
Start: time.Date(2021, 06, 07, 16, 00, 00, 00, time.Local), // 4:00PM
End: timePtr(time.Date(2021, 06, 07, 16, 25, 00, 00, time.Local)), // 4:25PM
Expand All @@ -135,7 +135,7 @@ func TestTotalTrackedTime(t *testing.T) {
End: timePtr(time.Date(2021, 06, 07, 17, 10, 00, 00, time.Local)), // 5:10PM
},
},
expected: time.Duration(time.Hour),
expected: time.Hour,
},
}
for _, test := range tt {
Expand Down
2 changes: 1 addition & 1 deletion cli/report.go
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ func generateReportCommand(t *core.Timetrace) *cobra.Command {
}

// set-up filter options based on cmd flags
var filter = []func(*core.Record) bool{
var filter = []func(core.Record) bool{
// this will ignore records which end time to not set
// so current tracked times for example
core.FilterNoneNilEndTime,
Expand Down
5 changes: 4 additions & 1 deletion cli/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ package cli

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

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

func RootCommand(t *core.Timetrace, version string) *cobra.Command {
func RootCommand(t *core.Timetrace, version string, pluginHost *plugin.Host) *cobra.Command {
root := &cobra.Command{
Use: "timetrace",
Short: "timetrace is a simple CLI for tracking your working time.",
Expand All @@ -37,5 +38,7 @@ func RootCommand(t *core.Timetrace, version string) *cobra.Command {
root.AddCommand(generateReportCommand(t))
root.AddCommand(versionCommand(version))

pluginHost.AddToCobra(root)

return root
}
9 changes: 5 additions & 4 deletions config/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,11 @@ import (
)

type Config struct {
Store string `json:"store"`
Use12Hours bool `json:"use12hours"`
Editor string `json:"editor"`
ReportPath string `json:"report-path"`
Store string `json:"store"`
Use12Hours bool `json:"use12hours"`
Editor string `json:"editor"`
ReportPath string `json:"report-path"`
PluginFolder string `json:"plugin-folder"`
}

var cached *Config
Expand Down
38 changes: 27 additions & 11 deletions core/project.go
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,13 @@ type Project struct {
Key string `json:"key"`
}

func unwrapProjectPointerResult(project *Project, err error) (Project, error) {
if err != nil {
return Project{}, err
}
return *project, err
}

// Parent returns the parent project of the current project or an empty string
// if there is no parent. If it has a parent, the current project is a module.
func (p *Project) Parent() string {
Expand All @@ -42,19 +49,22 @@ func (p *Project) IsModule() bool {

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

func (t *Timetrace) LoadBackupProject(key string) (*Project, error) {
//goplug:generate
func (t *Timetrace) LoadBackupProject(key string) (Project, error) {
path := t.fs.ProjectBackupFilepath(key)
return t.loadProject(path)
return unwrapProjectPointerResult(t.loadProject(path))
}

// ListProjectModules loads all modules for a project and returns their keys as a concatenated string
func (t *Timetrace) ListProjectModules(project *Project) (string, error) {
allModules, err := t.loadProjectModules(project)
//goplug:generate
func (t *Timetrace) ListProjectModules(project Project) (string, error) {
allModules, err := t.loadProjectModules(&project)
if err != nil {
return "", err
}
Expand All @@ -78,27 +88,29 @@ func (t *Timetrace) ListProjectModules(project *Project) (string, error) {

// ListProjects loads and returns all stored projects sorted by their filenames.
// If no projects are found, an empty slice and no error will be returned.
func (t *Timetrace) ListProjects() ([]*Project, error) {
//goplug:generate
func (t *Timetrace) ListProjects() ([]Project, error) {
paths, err := t.fs.ProjectFilepaths()
if err != nil {
return nil, err
}

projects := make([]*Project, 0)
projects := make([]Project, 0)

for _, path := range paths {
project, err := t.loadProject(path)
if err != nil {
return nil, err
}
projects = append(projects, project)
projects = append(projects, *project)
}

return projects, nil
}

// SaveProject persists the given project. Returns ErrProjectAlreadyExists if
// the project already exists and saving isn't forced.
//goplug:generate
func (t *Timetrace) SaveProject(project Project, force bool) error {
if project.IsModule() {
err := t.assertParent(project)
Expand Down Expand Up @@ -128,6 +140,7 @@ func (t *Timetrace) SaveProject(project Project, force bool) error {
}

// BackupProject creates a backup of the given project file.
//goplug:generate
func (t *Timetrace) BackupProject(projectKey string) error {
project, err := t.LoadProject(projectKey)
if err != nil {
Expand All @@ -151,6 +164,7 @@ func (t *Timetrace) BackupProject(projectKey string) error {
return err
}

//goplug:generate
func (t *Timetrace) RevertProject(projectKey string) error {
project, err := t.LoadBackupProject(projectKey)
if err != nil {
Expand All @@ -175,6 +189,7 @@ func (t *Timetrace) RevertProject(projectKey string) error {
}

// EditProject opens the project file in the preferred or default editor .
//goplug:generate
func (t *Timetrace) EditProject(projectKey string) error {
if _, err := t.LoadProject(projectKey); err != nil {
return err
Expand All @@ -193,6 +208,7 @@ func (t *Timetrace) EditProject(projectKey string) error {

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

Expand Down Expand Up @@ -228,13 +244,13 @@ func (t *Timetrace) loadProject(path string) (*Project, error) {
//
// Since project modules are projects with the name <module>@<project>, this
// function simply loads all "projects" suffixed with @<key>.
func (t *Timetrace) loadProjectModules(project *Project) ([]*Project, error) {
func (t *Timetrace) loadProjectModules(project *Project) ([]Project, error) {
projects, err := t.ListProjects()
if err != nil {
return nil, err
}

var modules []*Project
var modules []Project

for _, p := range projects {
if p.Parent() == project.Key {
Expand Down
Loading