mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2025-01-13 20:27:28 -08:00
feat: nightscout segment
This commit is contained in:
parent
7627a65c66
commit
fd81f4a00c
88
docs/docs/segment_nightscout.md
Normal file
88
docs/docs/segment_nightscout.md
Normal file
|
@ -0,0 +1,88 @@
|
|||
---
|
||||
id: nightscout
|
||||
title: Nightscout
|
||||
sidebar_label: Nightscout
|
||||
---
|
||||
|
||||
## What
|
||||
|
||||
[Nightscout][nightscout] (CGM in the Cloud) is an open source,
|
||||
DIY project that allows real time access to a CGM data via an HTTP REST API. It
|
||||
is used for secure remote viewing of blood sugar data from anywhere...including
|
||||
OhMyPosh segments on the command line!
|
||||
|
||||
## Sample Configuration
|
||||
|
||||
This example is using mg/dl, you'll want change the numbers for mmol. Your idea
|
||||
of "high" or "low" is different from others. You'll also want to think about
|
||||
your background and foreground colors. Don't use white text on a yellow
|
||||
background, for example.
|
||||
|
||||
The foreground_templates example below could be set to just a single color,
|
||||
if that color is visible against any of your backgrounds.
|
||||
|
||||
```json
|
||||
{
|
||||
"type": "nightscout",
|
||||
"style": "diamond",
|
||||
"foreground": "#ffffff",
|
||||
"background": "#ff0000",
|
||||
"background_templates": [
|
||||
"{{ if gt .Sgv 150 }}#FFFF00{{ end }}",
|
||||
"{{ if lt .Sgv 60 }}#FF0000{{ end }}",
|
||||
"#00FF00"
|
||||
],
|
||||
"foreground_templates": [
|
||||
"{{ if gt .Sgv 150 }}#000000{{ end }}",
|
||||
"{{ if lt .Sgv 60 }}#000000{{ end }}",
|
||||
"#000000"
|
||||
],
|
||||
|
||||
"leading_diamond": "",
|
||||
"trailing_diamond": "\uE0B0",
|
||||
"properties": {
|
||||
"url": "https://YOURNIGHTSCOUTAPP.herokuapp.com/api/v1/entries.json?count=1&token=APITOKENFROMYOURADMIN",
|
||||
"http_timeout": 1500,
|
||||
"template": " {{.Sgv}}{{.TrendIcon}}"
|
||||
}
|
||||
},
|
||||
```
|
||||
|
||||
## Properties
|
||||
|
||||
- url: `string` - Your Nightscout URL, inclding the full path to entries.json
|
||||
AND count=1 AND token. Example above. You'll know this works if you can curl
|
||||
it yourself and get a single value. - defaults to ``
|
||||
- http_timeout: `int` - How long do you want to wait before you want to see
|
||||
your prompt more than your sugar? I figure a half second is a good default -
|
||||
defaults to 500ms
|
||||
- template: `string` - a go [text/template][go-text-template] template extended
|
||||
with [sprig][sprig] utilizing the properties below.
|
||||
See the example above where I added a syringe.
|
||||
You can change the icon, put the trend elsewhere, add text, however you like!
|
||||
Make sure your NerdFont has the glyph you want or search for one
|
||||
at nerdfonts.com
|
||||
- NSCacheTimeout: `int` in minutes - How long do you want your numbers cached? -
|
||||
defaults to 5 min
|
||||
|
||||
- NOTE: You can change the icons for trend, put the trend elsewhere, add text,
|
||||
however you like!
|
||||
Make sure your NerdFont has the glyph you want or search for one at
|
||||
nerdfonts.com
|
||||
- DoubleUpIcon - defaults to ↑↑
|
||||
- SingleUpIcon - defaults to ↑
|
||||
- FortyFiveUpIcon - defaults to ↗
|
||||
- FlatIcon - defaults to →
|
||||
- FortyFiveDownIcon - defaults to ↘
|
||||
- SingleDownIcon - defaults to ↓
|
||||
- DoubleDownIcon - defaults to ↓↓
|
||||
|
||||
## Template Properties
|
||||
|
||||
- .Sgv: `int` - Your Serum Glucose Value (your sugar)
|
||||
- .TrendIcon: `string` - By default, this will be something like ↑↑ or ↘ etc but you can
|
||||
override them with any glpyh as seen above
|
||||
|
||||
[go-text-template]: https://golang.org/pkg/text/template/
|
||||
[sprig]: https://masterminds.github.io/sprig/
|
||||
[nightscout]: http://www.nightscout.info/
|
|
@ -55,6 +55,7 @@ module.exports = {
|
|||
"julia",
|
||||
"kubectl",
|
||||
"nbgv",
|
||||
"nightscout",
|
||||
"node",
|
||||
"os",
|
||||
"owm",
|
||||
|
|
|
@ -130,6 +130,8 @@ const (
|
|||
Angular SegmentType = "angular"
|
||||
// PHP writes which php version is currently active
|
||||
PHP SegmentType = "php"
|
||||
// Nightscout is an open source diabetes system
|
||||
Nightscout SegmentType = "nightscout"
|
||||
)
|
||||
|
||||
func (segment *Segment) string() string {
|
||||
|
@ -263,6 +265,7 @@ func (segment *Segment) mapSegmentWithWriter(env environmentInfo) error {
|
|||
SysInfo: &sysinfo{},
|
||||
Angular: &angular{},
|
||||
PHP: &php{},
|
||||
Nightscout: &nightscout{},
|
||||
}
|
||||
if writer, ok := functions[segment.Type]; ok {
|
||||
props := &properties{
|
||||
|
|
134
src/segment_nightscout.go
Normal file
134
src/segment_nightscout.go
Normal file
|
@ -0,0 +1,134 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"encoding/json"
|
||||
)
|
||||
|
||||
// segment struct, makes templating easier
|
||||
type nightscout struct {
|
||||
props *properties
|
||||
env environmentInfo
|
||||
// array of nightscoutData (is often just 1, and we will pick the ZEROeth)
|
||||
|
||||
Sgv int64
|
||||
Direction string
|
||||
|
||||
TrendIcon string
|
||||
}
|
||||
|
||||
const (
|
||||
// Your complete Nightscout URL and APIKey like this
|
||||
URL Property = "url"
|
||||
|
||||
DoubleUpIcon Property = "doubleup_icon"
|
||||
SingleUpIcon Property = "singleup_icon"
|
||||
FortyFiveUpIcon Property = "fortyfiveup_icon"
|
||||
FlatIcon Property = "flat_icon"
|
||||
FortyFiveDownIcon Property = "fortyfivedown_icon"
|
||||
SingleDownIcon Property = "singledown_icon"
|
||||
DoubleDownIcon Property = "doubledown_icon"
|
||||
|
||||
NSCacheTimeout Property = "cache_timeout"
|
||||
)
|
||||
|
||||
type nightscoutData struct {
|
||||
Sgv int64 `json:"sgv"`
|
||||
Direction string `json:"direction"`
|
||||
}
|
||||
|
||||
func (ns *nightscout) enabled() bool {
|
||||
data, err := ns.getResult()
|
||||
if err != nil {
|
||||
return false
|
||||
}
|
||||
ns.Sgv = data.Sgv
|
||||
ns.Direction = data.Direction
|
||||
|
||||
ns.TrendIcon = ns.getTrendIcon()
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
func (ns *nightscout) getTrendIcon() string {
|
||||
switch ns.Direction {
|
||||
case "DoubleUp":
|
||||
return ns.props.getString(DoubleUpIcon, "↑↑")
|
||||
case "SingleUp":
|
||||
return ns.props.getString(SingleUpIcon, "↑")
|
||||
case "FortyFiveUp":
|
||||
return ns.props.getString(FortyFiveUpIcon, "↗")
|
||||
case "Flat":
|
||||
return ns.props.getString(FlatIcon, "→")
|
||||
case "FortyFiveDown":
|
||||
return ns.props.getString(FortyFiveDownIcon, "↘")
|
||||
case "SingleDown":
|
||||
return ns.props.getString(SingleDownIcon, "↓")
|
||||
case "DoubleDown":
|
||||
return ns.props.getString(DoubleDownIcon, "↓↓")
|
||||
default:
|
||||
return ""
|
||||
}
|
||||
}
|
||||
|
||||
func (ns *nightscout) string() string {
|
||||
segmentTemplate := ns.props.getString(SegmentTemplate, "{{.Sgv}}")
|
||||
template := &textTemplate{
|
||||
Template: segmentTemplate,
|
||||
Context: ns,
|
||||
Env: ns.env,
|
||||
}
|
||||
text, err := template.render()
|
||||
if err != nil {
|
||||
return err.Error()
|
||||
}
|
||||
|
||||
return text
|
||||
}
|
||||
|
||||
func (ns *nightscout) getResult() (*nightscoutData, error) {
|
||||
url := ns.props.getString(URL, "")
|
||||
// natural and understood NS timeout is 5, anything else is unusual
|
||||
cacheTimeout := ns.props.getInt(NSCacheTimeout, 5)
|
||||
response := &nightscoutData{}
|
||||
if cacheTimeout > 0 {
|
||||
// check if data stored in cache
|
||||
val, found := ns.env.cache().get(url)
|
||||
// we got something from the cache
|
||||
if found {
|
||||
err := json.Unmarshal([]byte(val), response)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return response, nil
|
||||
}
|
||||
}
|
||||
|
||||
httpTimeout := ns.props.getInt(HTTPTimeout, DefaultHTTPTimeout)
|
||||
|
||||
body, err := ns.env.doGet(url, httpTimeout)
|
||||
if err != nil {
|
||||
return &nightscoutData{}, err
|
||||
}
|
||||
var arr []*nightscoutData
|
||||
err = json.Unmarshal(body, &arr)
|
||||
if err != nil {
|
||||
return &nightscoutData{}, err
|
||||
}
|
||||
|
||||
firstelement := arr[0]
|
||||
firstData, err := json.Marshal(firstelement)
|
||||
if err != nil {
|
||||
return &nightscoutData{}, err
|
||||
}
|
||||
|
||||
if cacheTimeout > 0 {
|
||||
// persist new sugars in cache
|
||||
ns.env.cache().set(url, string(firstData), cacheTimeout)
|
||||
}
|
||||
return firstelement, nil
|
||||
}
|
||||
|
||||
func (ns *nightscout) init(props *properties, env environmentInfo) {
|
||||
ns.props = props
|
||||
ns.env = env
|
||||
}
|
115
src/segment_nightscout_test.go
Normal file
115
src/segment_nightscout_test.go
Normal file
|
@ -0,0 +1,115 @@
|
|||
package main
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
const (
|
||||
NSAPIURL = "FAKE"
|
||||
)
|
||||
|
||||
func TestNSSegment(t *testing.T) {
|
||||
cases := []struct {
|
||||
Case string
|
||||
JSONResponse string
|
||||
ExpectedString string
|
||||
ExpectedEnabled bool
|
||||
Template string
|
||||
Error error
|
||||
}{
|
||||
{
|
||||
Case: "Flat 150",
|
||||
JSONResponse: `
|
||||
[{"_id":"619d6fa819696e8ded5b2206","sgv":150,"date":1637707537000,"dateString":"2021-11-23T22:45:37.000Z","trend":4,"direction":"Flat","device":"share2","type":"sgv","utcOffset":0,"sysTime":"2021-11-23T22:45:37.000Z","mills":1637707537000}]`, // nolint:lll
|
||||
Template: " {{.Sgv}}{{.TrendIcon}}",
|
||||
ExpectedString: " 150→",
|
||||
ExpectedEnabled: true,
|
||||
},
|
||||
{
|
||||
Case: "DoubleDown 50",
|
||||
JSONResponse: `
|
||||
[{"_id":"619d6fa819696e8ded5b2206","sgv":50,"date":1637707537000,"dateString":"2021-11-23T22:45:37.000Z","trend":4,"direction":"DoubleDown","device":"share2","type":"sgv","utcOffset":0,"sysTime":"2021-11-23T22:45:37.000Z","mills":1637707537000}]`, // nolint:lll
|
||||
Template: " {{.Sgv}}{{.TrendIcon}}",
|
||||
ExpectedString: " 50↓↓",
|
||||
ExpectedEnabled: true,
|
||||
},
|
||||
{
|
||||
Case: "DoubleUp 250",
|
||||
JSONResponse: `
|
||||
[{"_id":"619d6fa819696e8ded5b2206","sgv":250,"date":1637707537000,"dateString":"2021-11-23T22:45:37.000Z","trend":4,"direction":"DoubleUp","device":"share2","type":"sgv","utcOffset":0,"sysTime":"2021-11-23T22:45:37.000Z","mills":1637707537000}]`, // nolint:lll
|
||||
Template: " {{.Sgv}}{{.TrendIcon}}",
|
||||
ExpectedString: " 250↑↑",
|
||||
ExpectedEnabled: true,
|
||||
},
|
||||
{
|
||||
Case: "SingleUp 130",
|
||||
JSONResponse: `
|
||||
[{"_id":"619d6fa819696e8ded5b2206","sgv":130,"date":1637707537000,"dateString":"2021-11-23T22:45:37.000Z","trend":4,"direction":"SingleUp","device":"share2","type":"sgv","utcOffset":0,"sysTime":"2021-11-23T22:45:37.000Z","mills":1637707537000}]`, // nolint:lll
|
||||
Template: " {{.Sgv}}{{.TrendIcon}}",
|
||||
ExpectedString: " 130↑",
|
||||
ExpectedEnabled: true,
|
||||
},
|
||||
{
|
||||
Case: "FortyFiveUp 174",
|
||||
JSONResponse: `
|
||||
[{"_id":"619d6fa819696e8ded5b2206","sgv":174,"date":1637707537000,"dateString":"2021-11-23T22:45:37.000Z","trend":4,"direction":"FortyFiveUp","device":"share2","type":"sgv","utcOffset":0,"sysTime":"2021-11-23T22:45:37.000Z","mills":1637707537000}]`, // nolint:lll
|
||||
Template: " {{.Sgv}}{{.TrendIcon}}",
|
||||
ExpectedString: " 174↗",
|
||||
ExpectedEnabled: true,
|
||||
},
|
||||
{
|
||||
Case: "FortyFiveDown 61",
|
||||
JSONResponse: `
|
||||
[{"_id":"619d6fa819696e8ded5b2206","sgv":61,"date":1637707537000,"dateString":"2021-11-23T22:45:37.000Z","trend":4,"direction":"FortyFiveDown","device":"share2","type":"sgv","utcOffset":0,"sysTime":"2021-11-23T22:45:37.000Z","mills":1637707537000}]`, // nolint:lll
|
||||
Template: " {{.Sgv}}{{.TrendIcon}}",
|
||||
ExpectedString: " 61↘",
|
||||
ExpectedEnabled: true,
|
||||
},
|
||||
{
|
||||
Case: "DoubleDown 50",
|
||||
JSONResponse: `
|
||||
[{"_id":"619d6fa819696e8ded5b2206","sgv":50,"date":1637707537000,"dateString":"2021-11-23T22:45:37.000Z","trend":4,"direction":"DoubleDown","device":"share2","type":"sgv","utcOffset":0,"sysTime":"2021-11-23T22:45:37.000Z","mills":1637707537000}]`, // nolint:lll
|
||||
Template: " {{.Sgv}}{{.TrendIcon}}",
|
||||
ExpectedString: " 50↓↓",
|
||||
ExpectedEnabled: true,
|
||||
},
|
||||
{
|
||||
Case: "Error in retrieving data",
|
||||
JSONResponse: "nonsense",
|
||||
Error: errors.New("Something went wrong"),
|
||||
ExpectedEnabled: false,
|
||||
},
|
||||
}
|
||||
|
||||
for _, tc := range cases {
|
||||
env := &MockedEnvironment{}
|
||||
props := &properties{
|
||||
values: map[Property]interface{}{
|
||||
CacheTimeout: 0,
|
||||
URL: "FAKE",
|
||||
},
|
||||
}
|
||||
|
||||
env.On("doGet", NSAPIURL).Return([]byte(tc.JSONResponse), tc.Error)
|
||||
|
||||
if tc.Template != "" {
|
||||
props.values[SegmentTemplate] = tc.Template
|
||||
}
|
||||
|
||||
ns := &nightscout{
|
||||
props: props,
|
||||
env: env,
|
||||
}
|
||||
|
||||
enabled := ns.enabled()
|
||||
assert.Equal(t, tc.ExpectedEnabled, enabled, tc.Case)
|
||||
if !enabled {
|
||||
continue
|
||||
}
|
||||
|
||||
assert.Equal(t, tc.ExpectedString, ns.string(), tc.Case)
|
||||
}
|
||||
}
|
Loading…
Reference in a new issue