mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2025-03-05 20:49:04 -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",
|
"julia",
|
||||||
"kubectl",
|
"kubectl",
|
||||||
"nbgv",
|
"nbgv",
|
||||||
|
"nightscout",
|
||||||
"node",
|
"node",
|
||||||
"os",
|
"os",
|
||||||
"owm",
|
"owm",
|
||||||
|
|
|
@ -130,6 +130,8 @@ const (
|
||||||
Angular SegmentType = "angular"
|
Angular SegmentType = "angular"
|
||||||
// PHP writes which php version is currently active
|
// PHP writes which php version is currently active
|
||||||
PHP SegmentType = "php"
|
PHP SegmentType = "php"
|
||||||
|
// Nightscout is an open source diabetes system
|
||||||
|
Nightscout SegmentType = "nightscout"
|
||||||
)
|
)
|
||||||
|
|
||||||
func (segment *Segment) string() string {
|
func (segment *Segment) string() string {
|
||||||
|
@ -263,6 +265,7 @@ func (segment *Segment) mapSegmentWithWriter(env environmentInfo) error {
|
||||||
SysInfo: &sysinfo{},
|
SysInfo: &sysinfo{},
|
||||||
Angular: &angular{},
|
Angular: &angular{},
|
||||||
PHP: &php{},
|
PHP: &php{},
|
||||||
|
Nightscout: &nightscout{},
|
||||||
}
|
}
|
||||||
if writer, ok := functions[segment.Type]; ok {
|
if writer, ok := functions[segment.Type]; ok {
|
||||||
props := &properties{
|
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