mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2024-12-27 20:09:39 -08:00
feat(wakatime): add segment
This commit is contained in:
parent
2ccaf90cbf
commit
0fb5951375
57
docs/docs/segment-wakatime.md
Normal file
57
docs/docs/segment-wakatime.md
Normal file
|
@ -0,0 +1,57 @@
|
|||
---
|
||||
id: wakatime
|
||||
title: Wakatime
|
||||
sidebar_label: Wakatime
|
||||
---
|
||||
|
||||
## What
|
||||
|
||||
Shows the tracked time on [wakatime][wt] of the current day
|
||||
|
||||
:::caution
|
||||
|
||||
You **must** request an API key at the [wakatime][wt] website.
|
||||
The free tier for is sufficient. You'll find the API key in your profile settings page.
|
||||
|
||||
:::
|
||||
|
||||
## Sample Configuration
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "wakatime",
|
||||
"style": "powerline",
|
||||
"powerline_symbol": "\uE0B0",
|
||||
"foreground": "#ffffff",
|
||||
"background": "#007acc",
|
||||
"properties": {
|
||||
"prefix": " \uf7d9 ",
|
||||
"url": "https://wakatime.com/api/v1/users/current/summaries?start=today&end=today&api_key=API_KEY",
|
||||
"cache_timeout": 10,
|
||||
"http_timeout": 500
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
- url: `string` - Your Wakatime [summaries][wk-summaries] URL, including the API key. Example above. You'll know this
|
||||
works if you can curl it yourself and a result. - defaults to ``
|
||||
- http_timeout: `int` - The default timeout for http request is 20ms. If no segment is shown, try increasing this timeout.
|
||||
- cache_timeout: `int` - The default timeout for request caching is 10m. A value of 0 disables the cache.
|
||||
- template: `string` - A go [text/template][go-text-template] template extended with [sprig][sprig] utilizing the
|
||||
properties below - defaults to `{{ secondsRound .CummulativeTotal.Seconds }}`
|
||||
|
||||
## Template Properties
|
||||
|
||||
- `.CummulativeTotal`: `wtTotals` - object holding total tracked time values
|
||||
|
||||
### wtTotal Properties
|
||||
|
||||
- `.Seconds`: `int` - a number reprecenting the total tracked time in seconds
|
||||
- `.Text`: `string` - a string with human readable tracked time (eg: "2 hrs 30 mins")
|
||||
|
||||
[wt]: https://wakatime.com
|
||||
[wk-summaries]: https://wakatime.com/developers#summaries
|
||||
[go-text-template]: https://golang.org/pkg/text/template/
|
||||
[sprig]: https://masterminds.github.io/sprig/
|
|
@ -73,6 +73,7 @@ module.exports = {
|
|||
"terraform",
|
||||
"text",
|
||||
"time",
|
||||
"wakatime",
|
||||
"wifi",
|
||||
"winreg",
|
||||
"ytm",
|
||||
|
|
|
@ -133,6 +133,8 @@ const (
|
|||
PHP SegmentType = "php"
|
||||
// Nightscout is an open source diabetes system
|
||||
Nightscout SegmentType = "nightscout"
|
||||
// Wakatime writes tracked time spend in dev editors
|
||||
Wakatime SegmentType = "wakatime"
|
||||
// WiFi writes details about the current WiFi connection
|
||||
WiFi SegmentType = "wifi"
|
||||
// WinReg queries the Windows registry.
|
||||
|
@ -266,6 +268,7 @@ func (segment *Segment) mapSegmentWithWriter(env environmentInfo) error {
|
|||
Angular: &angular{},
|
||||
PHP: &php{},
|
||||
Nightscout: &nightscout{},
|
||||
Wakatime: &wakatime{},
|
||||
WiFi: &wifi{},
|
||||
WinReg: &winreg{},
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
)
|
||||
|
||||
const (
|
||||
NSAPIURL = "FAKE"
|
||||
FAKEAPIURL = "FAKE"
|
||||
)
|
||||
|
||||
func TestNSSegment(t *testing.T) {
|
||||
|
@ -136,10 +136,10 @@ func TestNSSegment(t *testing.T) {
|
|||
}
|
||||
|
||||
cache := &MockedCache{}
|
||||
cache.On("get", NSAPIURL).Return(tc.JSONResponse, !tc.CacheFoundFail)
|
||||
cache.On("set", NSAPIURL, tc.JSONResponse, tc.CacheTimeout).Return()
|
||||
cache.On("get", FAKEAPIURL).Return(tc.JSONResponse, !tc.CacheFoundFail)
|
||||
cache.On("set", FAKEAPIURL, tc.JSONResponse, tc.CacheTimeout).Return()
|
||||
|
||||
env.On("doGet", NSAPIURL).Return([]byte(tc.JSONResponse), tc.Error)
|
||||
env.On("doGet", FAKEAPIURL).Return([]byte(tc.JSONResponse), tc.Error)
|
||||
env.On("cache", nil).Return(cache)
|
||||
|
||||
if tc.Template != "" {
|
||||
|
|
79
src/segment_wakatime.go
Normal file
79
src/segment_wakatime.go
Normal file
|
@ -0,0 +1,79 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
type wakatime struct {
|
||||
props properties
|
||||
env environmentInfo
|
||||
|
||||
wtData
|
||||
}
|
||||
|
||||
type wtTotals struct {
|
||||
Seconds float64 `json:"seconds"`
|
||||
Text string `json:"text"`
|
||||
}
|
||||
|
||||
type wtData struct {
|
||||
CummulativeTotal wtTotals `json:"cummulative_total"`
|
||||
Start string `json:"start"`
|
||||
End string `json:"end"`
|
||||
}
|
||||
|
||||
func (w *wakatime) enabled() bool {
|
||||
err := w.setAPIData()
|
||||
return err == nil
|
||||
}
|
||||
|
||||
func (w *wakatime) string() string {
|
||||
segmentTemplate := w.props.getString(SegmentTemplate, "{{ secondsRound .CummulativeTotal.Seconds }}")
|
||||
template := &textTemplate{
|
||||
Template: segmentTemplate,
|
||||
Context: w,
|
||||
Env: w.env,
|
||||
}
|
||||
text, err := template.render()
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
func (w *wakatime) setAPIData() error {
|
||||
url := w.props.getString(URL, "")
|
||||
cacheTimeout := w.props.getInt(CacheTimeout, DefaultCacheTimeout)
|
||||
if cacheTimeout > 0 {
|
||||
// check if data stored in cache
|
||||
if val, found := w.env.cache().get(url); found {
|
||||
err := json.Unmarshal([]byte(val), &w.wtData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
httpTimeout := w.props.getInt(HTTPTimeout, DefaultHTTPTimeout)
|
||||
|
||||
body, err := w.env.doGet(url, httpTimeout)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
err = json.Unmarshal(body, &w.wtData)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
if cacheTimeout > 0 {
|
||||
w.env.cache().set(url, string(body), cacheTimeout)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (w *wakatime) init(props properties, env environmentInfo) {
|
||||
w.props = props
|
||||
w.env = env
|
||||
}
|
93
src/segment_wakatime_test.go
Normal file
93
src/segment_wakatime_test.go
Normal file
|
@ -0,0 +1,93 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
func TestWTTrackedTime(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
Seconds int
|
||||
Expected string
|
||||
Template string
|
||||
CacheTimeout int
|
||||
CacheFoundFail bool
|
||||
Error error
|
||||
}{
|
||||
{
|
||||
Case: "nothing tracked",
|
||||
Seconds: 0,
|
||||
Expected: "0s",
|
||||
},
|
||||
{
|
||||
Case: "25 minutes",
|
||||
Seconds: 1500,
|
||||
Expected: "25m",
|
||||
},
|
||||
{
|
||||
Case: "2 hours",
|
||||
Seconds: 7200,
|
||||
Expected: "2h",
|
||||
},
|
||||
{
|
||||
Case: "2h 45m",
|
||||
Seconds: 9900,
|
||||
Expected: "2h 45m",
|
||||
},
|
||||
{
|
||||
Case: "negative number",
|
||||
Seconds: -9900,
|
||||
Expected: "2h 45m",
|
||||
},
|
||||
{
|
||||
Case: "cache 2h 45m",
|
||||
Seconds: 9900,
|
||||
Expected: "2h 45m",
|
||||
CacheTimeout: 20,
|
||||
},
|
||||
{
|
||||
Case: "no cache 2h 45m",
|
||||
Seconds: 9900,
|
||||
Expected: "2h 45m",
|
||||
CacheTimeout: 20,
|
||||
CacheFoundFail: true,
|
||||
},
|
||||
{
|
||||
Case: "api error",
|
||||
Seconds: 2,
|
||||
Expected: "0s",
|
||||
CacheTimeout: 20,
|
||||
CacheFoundFail: true,
|
||||
Error: errors.New("api error"),
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
env := &MockedEnvironment{}
|
||||
|
||||
response := fmt.Sprintf(`{"cummulative_total": {"seconds": %.2f, "text": "x"}}`, float64(tc.Seconds))
|
||||
|
||||
env.On("doGet", FAKEAPIURL).Return([]byte(response), tc.Error)
|
||||
|
||||
cache := &MockedCache{}
|
||||
cache.On("get", FAKEAPIURL).Return(response, !tc.CacheFoundFail)
|
||||
cache.On("set", FAKEAPIURL, response, tc.CacheTimeout).Return()
|
||||
env.On("cache", nil).Return(cache)
|
||||
|
||||
w := &wakatime{
|
||||
props: map[Property]interface{}{
|
||||
APIKey: "key",
|
||||
CacheTimeout: tc.CacheTimeout,
|
||||
URL: FAKEAPIURL,
|
||||
},
|
||||
env: env,
|
||||
}
|
||||
|
||||
assert.ErrorIs(t, tc.Error, w.setAPIData(), tc.Case+" - Error")
|
||||
assert.Equal(t, tc.Expected, w.string(), tc.Case+" - String")
|
||||
}
|
||||
}
|
|
@ -6,8 +6,6 @@ import (
|
|||
"reflect"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
)
|
||||
|
||||
const (
|
||||
|
@ -48,7 +46,7 @@ func (t *textTemplate) renderPlainContextTemplate(context map[string]interface{}
|
|||
}
|
||||
|
||||
func (t *textTemplate) render() (string, error) {
|
||||
tmpl, err := template.New("title").Funcs(sprig.TxtFuncMap()).Parse(t.Template)
|
||||
tmpl, err := template.New("title").Funcs(funcMap()).Parse(t.Template)
|
||||
if err != nil {
|
||||
return "", errors.New(invalidTemplate)
|
||||
}
|
||||
|
|
81
src/template_func.go
Normal file
81
src/template_func.go
Normal file
|
@ -0,0 +1,81 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"strconv"
|
||||
"strings"
|
||||
"text/template"
|
||||
|
||||
"github.com/Masterminds/sprig/v3"
|
||||
)
|
||||
|
||||
var funcMapCache map[string]interface{}
|
||||
|
||||
func funcMap() template.FuncMap {
|
||||
if funcMapCache != nil {
|
||||
return template.FuncMap(funcMapCache)
|
||||
}
|
||||
funcMapCache = map[string]interface{}{
|
||||
"secondsRound": secondsRound,
|
||||
}
|
||||
for key, fun := range sprig.TxtFuncMap() {
|
||||
if _, ok := funcMapCache[key]; !ok {
|
||||
funcMapCache[key] = fun
|
||||
}
|
||||
}
|
||||
return template.FuncMap(funcMapCache)
|
||||
}
|
||||
|
||||
func parseSeconds(seconds interface{}) (int, error) {
|
||||
switch seconds := seconds.(type) {
|
||||
default:
|
||||
return 0, errors.New("invalid seconds type")
|
||||
case string:
|
||||
return strconv.Atoi(seconds)
|
||||
case int:
|
||||
return seconds, nil
|
||||
case int64:
|
||||
return int(seconds), nil
|
||||
case float64:
|
||||
return int(seconds), nil
|
||||
}
|
||||
}
|
||||
|
||||
func secondsRound(seconds interface{}) string {
|
||||
s, err := parseSeconds(seconds)
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
if s == 0 {
|
||||
return "0s"
|
||||
}
|
||||
neg := s < 0
|
||||
if neg {
|
||||
s = -s
|
||||
}
|
||||
|
||||
var (
|
||||
second = 1
|
||||
minute = 60
|
||||
hour = minute * 60
|
||||
day = hour * 24
|
||||
month = day * 30
|
||||
year = day * 365
|
||||
)
|
||||
var builder strings.Builder
|
||||
writePart := func(unit int, name string) {
|
||||
if s >= unit {
|
||||
builder.WriteString(" ")
|
||||
builder.WriteString(strconv.Itoa(s / unit))
|
||||
builder.WriteString(name)
|
||||
s %= unit
|
||||
}
|
||||
}
|
||||
writePart(year, "y")
|
||||
writePart(month, "mo")
|
||||
writePart(day, "d")
|
||||
writePart(hour, "h")
|
||||
writePart(minute, "m")
|
||||
writePart(second, "s")
|
||||
return strings.Trim(builder.String(), " ")
|
||||
}
|
|
@ -181,6 +181,7 @@
|
|||
"sysinfo",
|
||||
"angular",
|
||||
"php",
|
||||
"wakatime",
|
||||
"wifi",
|
||||
"winreg",
|
||||
"plastic"
|
||||
|
@ -1649,6 +1650,41 @@
|
|||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
"type": { "const": "wakatime" }
|
||||
}
|
||||
},
|
||||
"then": {
|
||||
"title": "Wakatime",
|
||||
"description": "Displays the tracked time on wakatime.com",
|
||||
"properties": {
|
||||
"properties": {
|
||||
"properties": {
|
||||
"apikey": {
|
||||
"type": "string",
|
||||
"title": "apikey",
|
||||
"description": "The apikey used for the api call (Required)",
|
||||
"default": "."
|
||||
},
|
||||
"http_timeout": {
|
||||
"$ref": "#/definitions/http_timeout"
|
||||
},
|
||||
"cache_timeout": {
|
||||
"type": "integer",
|
||||
"title": "cache timeout",
|
||||
"description": "The number of minutes the response is cached. A value of 0 disables the cache.",
|
||||
"default": 10
|
||||
},
|
||||
"template": {
|
||||
"$ref": "#/definitions/template"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"if": {
|
||||
"properties": {
|
||||
|
|
Loading…
Reference in a new issue