-
Notifications
You must be signed in to change notification settings - Fork 0
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
feat: main implementation refactor: divided in packages refactor: output produces string to print instead of printing directly refactor: extracted constants for report config refactor: upgraded go version to use slices std lib for contains tests: tests for output package tests: tests for logic package docs: added README.md
- Loading branch information
1 parent
b9c9913
commit be36ead
Showing
10 changed files
with
283 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,54 @@ | ||
# Created by https://www.toptal.com/developers/gitignore/api/visualstudiocode,go | ||
# Edit at https://www.toptal.com/developers/gitignore?templates=visualstudiocode,go | ||
|
||
### Go ### | ||
# If you prefer the allow list template instead of the deny list, see community template: | ||
# https://github.com/github/gitignore/blob/main/community/Golang/Go.AllowList.gitignore | ||
# | ||
# Binaries for programs and plugins | ||
*.exe | ||
*.exe~ | ||
*.dll | ||
*.so | ||
*.dylib | ||
|
||
# Test binary, built with `go test -c` | ||
*.test | ||
|
||
# Output of the go coverage tool, specifically when used with LiteIDE | ||
*.out | ||
|
||
# Dependency directories (remove the comment below to include it) | ||
# vendor/ | ||
|
||
# Go workspace file | ||
go.work | ||
|
||
### VisualStudioCode ### | ||
.vscode/* | ||
!.vscode/settings.json | ||
!.vscode/tasks.json | ||
!.vscode/launch.json | ||
!.vscode/extensions.json | ||
!.vscode/*.code-snippets | ||
|
||
# Local History for Visual Studio Code | ||
.history/ | ||
|
||
# Built Visual Studio Code Extensions | ||
*.vsix | ||
|
||
### VisualStudioCode Patch ### | ||
# Ignore all local history of files | ||
.history | ||
.ionide | ||
|
||
# End of https://www.toptal.com/developers/gitignore/api/visualstudiocode,go | ||
|
||
tag-percentage | ||
|
||
coverage.out | ||
|
||
dist/ | ||
|
||
.idea/ |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,35 @@ | ||
before: | ||
hooks: | ||
- go mod tidy | ||
builds: | ||
- env: | ||
- CGO_ENABLED=0 | ||
binary: | ||
tag-percentage | ||
goos: | ||
#Timewarrior is officially distributed only on linux platforms | ||
- linux | ||
goarch: | ||
- amd64 | ||
|
||
archives: | ||
- id: binary | ||
format: tar.gz | ||
# this name template makes the OS and Arch compatible with the results of uname. | ||
name_template: >- | ||
{{ .ProjectName }}_ | ||
{{- title .Os }}_ | ||
{{- if eq .Arch "amd64" }}x86_64 | ||
{{- else if eq .Arch "386" }}i386 | ||
{{- else }}{{ .Arch }}{{ end }} | ||
{{- if .Arm }}v{{ .Arm }}{{ end }} | ||
checksum: | ||
name_template: 'checksums.txt' | ||
snapshot: | ||
name_template: "{{ incpatch .Version }}-snapshot" | ||
changelog: | ||
sort: asc | ||
filters: | ||
exclude: | ||
- '^docs:' | ||
- '^test:' |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,37 @@ | ||
# Tag percentage Timewarrior extension | ||
|
||
This [Timewarrior extension](https://timewarrior.net/docs/extensions/) calculates the percentage of time registered for a particular tag on the total for each day in the input intervals. | ||
|
||
## Installation | ||
|
||
1. Download the latest executable for your operating system from | ||
the [releases page](https://github.com/crossbone-magister/tag-percentage/releases). | ||
2. Add it to the Timewarrior extension folder as described in the [documentation](https://timewarrior.net/docs/api/). | ||
3. Verify that the extension is active and installed by running `timew extensions`. | ||
|
||
## Configuration | ||
|
||
Add the entry `reports.tagpercentage.target` Timewarrior configuration to allow this extension to work. | ||
Its value represents the tag to look for when calculating percentages. | ||
|
||
For example: | ||
```shell | ||
timew config reports.tagpercentage.target project1 | ||
``` | ||
|
||
Sets the label `project1` as the target for this extension. | ||
|
||
|
||
## Usage | ||
|
||
In a terminal window, run `timew tag-percentage`. An example output could be: | ||
|
||
```bash | ||
2024-01-01 - project1: 80.39% | ||
2024-01-02 - project1: 71.12% | ||
2024-01-03 - project1: 23.67% | ||
2024-01-04 - project1: 45.96% | ||
2024-01-05 - project1: 85.71% | ||
2024-01-06 - project1: 32.56% | ||
Average percentage: 56.57% | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,7 @@ | ||
module tag-percentage | ||
|
||
go 1.22.0 | ||
|
||
toolchain go1.23.3 | ||
|
||
require github.com/crossbone-magister/timewlib v0.3.0 |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,2 @@ | ||
github.com/crossbone-magister/timewlib v0.3.0 h1:fwWZUH4At14aEl9+yHriZs9xdHTsYrhDIdDuF8LpDLc= | ||
github.com/crossbone-magister/timewlib v0.3.0/go.mod h1:g+5jGSrqG6+Ws7eTFx8GZ7xKFGuMh7kWC2xDU2bqk3c= |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
package logic | ||
|
||
import ( | ||
"fmt" | ||
"github.com/crossbone-magister/timewlib" | ||
"slices" | ||
"time" | ||
) | ||
|
||
func CalculateTagPercentage(intervals []timewlib.Interval, targetTag string) (map[string]float64, float64) { | ||
totalPerDay := make(map[string]time.Duration) | ||
totalTargetTagPerDay := make(map[string]time.Duration) | ||
for _, interval := range intervals { | ||
y, m, d := interval.StartDate() | ||
key := fmt.Sprintf("%d-%02d-%02d", y, m, d) | ||
if _, ok := totalPerDay[key]; !ok { | ||
totalPerDay[key] = 0 | ||
} | ||
totalPerDay[key] += interval.Duration() | ||
if slices.Contains(interval.Tags, targetTag) { | ||
totalTargetTagPerDay[key] += interval.Duration() | ||
} | ||
} | ||
var percentages = make(map[string]float64) | ||
var average = 0.0 | ||
for day, total := range totalPerDay { | ||
percentage := (totalTargetTagPerDay[day].Seconds() / total.Seconds()) * 100 | ||
percentages[day] = percentage | ||
average += percentage | ||
} | ||
average /= float64(len(totalPerDay)) | ||
return percentages, average | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,30 @@ | ||
package logic | ||
|
||
import ( | ||
"github.com/crossbone-magister/timewlib" | ||
"testing" | ||
"time" | ||
) | ||
|
||
func TestCalculateTagPercentage(t *testing.T) { | ||
var interval1 = timewlib.NewInterval(10, 0, 11, 0) | ||
interval1.Tags = []string{"target"} | ||
var interval2 = timewlib.NewInterval(11, 0, 12, 0) | ||
percentagesPerDay, averagePercentage := CalculateTagPercentage([]timewlib.Interval{ | ||
*interval1, | ||
*interval2, | ||
}, "target") | ||
var label = time.Now().Format("2006-01-02") | ||
if len(percentagesPerDay) <= 0 { | ||
t.Errorf("PercentagesPerDay is empty") | ||
} | ||
if _, ok := percentagesPerDay[label]; !ok { | ||
t.Errorf("PercentagesPerDay doesn't contain day %s", label) | ||
} | ||
if percentagesPerDay[label] != 50.0 { | ||
t.Errorf("PercentagesPerDay for day %s is not 0.5 but %f", label, percentagesPerDay[label]) | ||
} | ||
if averagePercentage != 50.0 { | ||
t.Errorf("AveragePercentage is less than 0.5") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,45 @@ | ||
package main | ||
|
||
import ( | ||
"fmt" | ||
"github.com/crossbone-magister/timewlib" | ||
"os" | ||
"tag-percentage/logic" | ||
"tag-percentage/output" | ||
) | ||
|
||
const TargetTagConfig = "reports.tagpercentage.target" | ||
|
||
func main() { | ||
parsed, err := timewlib.Parse(os.Stdin) | ||
if err == nil { | ||
var config timewlib.Configuration = parsed.Configuration | ||
var targetTag = config[TargetTagConfig] | ||
if targetTag != "" { | ||
intervals, err := timewlib.Process(parsed.Intervals) | ||
if err == nil { | ||
if len(intervals) > 0 { | ||
percentages, average := logic.CalculateTagPercentage(intervals, targetTag) | ||
for _, row := range output.FormatPercentages(percentages, targetTag, average) { | ||
fmt.Println(row) | ||
} | ||
} else { | ||
fmt.Println(timewlib.GenerateNoDataMessage(config)) | ||
} | ||
} else { | ||
printErrorAndExit(err) | ||
} | ||
} else { | ||
fmt.Println("No target tag specified") | ||
os.Exit(1) | ||
} | ||
} else { | ||
printErrorAndExit(err) | ||
} | ||
|
||
} | ||
|
||
func printErrorAndExit(err error) { | ||
fmt.Printf("Error while reading timewarrior input: %s\n", err) | ||
os.Exit(1) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package output | ||
|
||
import ( | ||
"fmt" | ||
"sort" | ||
) | ||
|
||
func FormatPercentages(percentages map[string]float64, targetTag string, average float64) []string { | ||
var output = make([]string, 0) | ||
var sortedDates = make([]string, 0, len(percentages)) | ||
for key, _ := range percentages { | ||
sortedDates = append(sortedDates, key) | ||
} | ||
sort.Strings(sortedDates) | ||
for _, date := range sortedDates { | ||
output = append(output, fmt.Sprintf("%s - %s: %.2f%%", date, targetTag, percentages[date])) | ||
} | ||
output = append(output, fmt.Sprintf("Average percentage: %.2f%%", average)) | ||
return output | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,20 @@ | ||
package output | ||
|
||
import "testing" | ||
|
||
func TestFormatPercentages(t *testing.T) { | ||
var percentages = map[string]float64{ | ||
"02-12-2024": 36.74589, | ||
} | ||
|
||
formatted := FormatPercentages(percentages, "target", 47.231458) | ||
expectedLineFormat := "02-12-2024 - target: 36.75%" | ||
if formatted[0] != expectedLineFormat { | ||
t.Errorf("Incorrected formatting for single row. Expected '%s', got '%s'", expectedLineFormat, formatted[0]) | ||
} | ||
expectedAverageFormat := "Average percentage: 47.23%" | ||
if formatted[1] != expectedAverageFormat { | ||
t.Errorf("Incorrected formatting for average row. Expected '%s', got '%s'", expectedLineFormat, formatted[0]) | ||
} | ||
|
||
} |