Repository: JanDeDobbeleer/oh-my-posh
Pull request #3874 (2023-05-18 17:43:09)

feat: upgrade segment
feat: upgrade segment



Patch:
diff --git a/src/cli/print.go b/src/cli/print.go
index 2a13959d..420959f4 100644
--- a/src/cli/print.go
+++ b/src/cli/print.go
@@ -62,6 +62,7 @@ var printCmd = &cobra.Command{
 			Primary:       args[0] == "primary",
 			Cleared:       cleared,
 			NoExitCode:    noExitCode,
+			Version:       cliVersion,
 		}
 
 		eng := engine.New(flags)
diff --git a/src/engine/segment.go b/src/engine/segment.go
index a1f86413..0e0fba2c 100644
--- a/src/engine/segment.go
+++ b/src/engine/segment.go
@@ -231,6 +231,8 @@ const (
 	UI5TOOLING SegmentType = "ui5tooling"
 	// UNITY writes which Unity version is currently active
 	UNITY SegmentType = "unity"
+	// UPGRADE lets you know if you can upgrade Oh My Posh
+	UPGRADE SegmentType = "upgrade"
 	// VALA writes the active vala version
 	VALA SegmentType = "vala"
 	// WAKATIME writes tracked time spend in dev editors
@@ -315,6 +317,7 @@ var Segments = map[SegmentType]func() SegmentWriter{
 	TIME:          func() SegmentWriter { return &segments.Time{} },
 	UI5TOOLING:    func() SegmentWriter { return &segments.UI5Tooling{} },
 	UNITY:         func() SegmentWriter { return &segments.Unity{} },
+	UPGRADE:       func() SegmentWriter { return &segments.Upgrade{} },
 	VALA:          func() SegmentWriter { return &segments.Vala{} },
 	WAKATIME:      func() SegmentWriter { return &segments.Wakatime{} },
 	WINREG:        func() SegmentWriter { return &segments.WindowsRegistry{} },
diff --git a/src/segments/brewfather.go b/src/segments/brewfather.go
index a4c84177..6dac12be 100644
--- a/src/segments/brewfather.go
+++ b/src/segments/brewfather.go
@@ -54,8 +54,6 @@ const (
 
 	BFDayIcon properties.Property = "day_icon"
 
-	BFCacheTimeout properties.Property = "cache_timeout"
-
 	BFStatusPlanning     string = "Planning"
 	BFStatusBrewing      string = "Brewing"
 	BFStatusFermenting   string = "Fermenting"
@@ -247,7 +245,7 @@ func (bf *Brewfather) getResult() (*Batch, error) {
 	batchReadingsURL := fmt.Sprintf("https://api.brewfather.app/v1/batches/%s/readings", batchID)
 
 	httpTimeout := bf.props.GetInt(properties.HTTPTimeout, properties.DefaultHTTPTimeout)
-	cacheTimeout := bf.props.GetInt(BFCacheTimeout, 5)
+	cacheTimeout := bf.props.GetInt(properties.CacheTimeout, 5)
 
 	if cacheTimeout > 0 {
 		if data, err := getFromCache(batchURL); err == nil {
diff --git a/src/segments/nightscout.go b/src/segments/nightscout.go
index 0786846f..b182be9b 100644
--- a/src/segments/nightscout.go
+++ b/src/segments/nightscout.go
@@ -29,8 +29,6 @@ const (
 	FortyFiveDownIcon properties.Property = "fortyfivedown_icon"
 	SingleDownIcon    properties.Property = "singledown_icon"
 	DoubleDownIcon    properties.Property = "doubledown_icon"
-
-	NSCacheTimeout properties.Property = "cache_timeout"
 )
 
 // NightscoutData struct contains the API data
@@ -110,7 +108,7 @@ func (ns *Nightscout) getResult() (*NightscoutData, error) {
 	url := ns.props.GetString(URL, "")
 	httpTimeout := ns.props.GetInt(properties.HTTPTimeout, properties.DefaultHTTPTimeout)
 	// natural and understood NS timeout is 5, anything else is unusual
-	cacheTimeout := ns.props.GetInt(NSCacheTimeout, 5)
+	cacheTimeout := ns.props.GetInt(properties.CacheTimeout, 5)
 
 	if cacheTimeout > 0 {
 		if data, err := getCacheValue(url); err == nil {
diff --git a/src/segments/upgrade.go b/src/segments/upgrade.go
new file mode 100644
index 00000000..8d2886e3
--- /dev/null
+++ b/src/segments/upgrade.go
@@ -0,0 +1,86 @@
+package segments
+
+import (
+	"encoding/json"
+	"errors"
+	"fmt"
+
+	"github.com/jandedobbeleer/oh-my-posh/src/platform"
+	"github.com/jandedobbeleer/oh-my-posh/src/properties"
+	"github.com/jandedobbeleer/oh-my-posh/src/upgrade"
+)
+
+type UpgradeCache struct {
+	Latest  string `json:"latest"`
+	Current string `json:"current"`
+}
+
+type Upgrade struct {
+	props properties.Properties
+	env   platform.Environment
+
+	Version string
+}
+
+const UPGRADECACHEKEY = "upgrade_segment"
+
+func (u *Upgrade) Template() string {
+	return " \uf019 "
+}
+
+func (u *Upgrade) Init(props properties.Properties, env platform.Environment) {
+	u.props = props
+	u.env = env
+}
+
+func (u *Upgrade) Enabled() bool {
+	version := fmt.Sprintf("v%s", u.env.Flags().Version)
+
+	if shouldDisplay, err := u.ShouldDisplay(version); err == nil {
+		return shouldDisplay
+	}
+
+	latest, err := upgrade.Latest(u.env)
+	if err != nil {
+		return false
+	}
+
+	if latest == version {
+		return false
+	}
+
+	cacheData := &UpgradeCache{
+		Latest:  latest,
+		Current: version,
+	}
+	cacheJSON, err := json.Marshal(cacheData)
+	if err != nil {
+		return false
+	}
+
+	oneWeek := 10080
+	cacheTimeout := u.props.GetInt(properties.CacheTimeout, oneWeek)
+	u.env.Cache().Set(UPGRADECACHEKEY, string(cacheJSON), cacheTimeout)
+
+	u.Version = latest
+	return true
+}
+
+func (u *Upgrade) ShouldDisplay(version string) (bool, error) {
+	data, OK := u.env.Cache().Get(UPGRADECACHEKEY)
+	if !OK {
+		return false, errors.New("no cache")
+	}
+
+	var cacheJSON UpgradeCache
+	err := json.Unmarshal([]byte(data), &cacheJSON)
+	if err != nil {
+		return false, errors.New("invalid cache data")
+	}
+
+	if version == cacheJSON.Current {
+		return cacheJSON.Current != cacheJSON.Latest, nil
+	}
+
+	return false, errors.New("version changed, run the check again")
+}
diff --git a/src/upgrade/notice.go b/src/upgrade/notice.go
index 461cfa41..8ad87955 100644
--- a/src/upgrade/notice.go
+++ b/src/upgrade/notice.go
@@ -29,7 +29,7 @@ type release struct {
 }
 
 const (
-	releaseURL    = "https://api.github.com/repos/jandedobbeleer/oh-my-posh/releases/latest"
+	RELEASEURL    = "https://api.github.com/repos/jandedobbeleer/oh-my-posh/releases/latest"
 	upgradeNotice = `
 A new release of Oh My Posh is available: %s → %s
 %s
@@ -42,8 +42,8 @@ https://ohmyposh.dev/docs/installation/windows#update`
 	CACHEKEY = "upgrade_check"
 )
 
-func getLatestVersion(env platform.Environment) (string, error) {
-	body, err := env.HTTPRequest(releaseURL, nil, 1000)
+func Latest(env platform.Environment) (string, error) {
+	body, err := env.HTTPRequest(RELEASEURL, nil, 1000)
 	if err != nil {
 		return "", err
 	}
@@ -67,7 +67,7 @@ func Notice(env platform.Environment) (string, bool) {
 		return "", false
 	}
 
-	latest, err := getLatestVersion(env)
+	latest, err := Latest(env)
 	if err != nil {
 		return "", false
 	}
diff --git a/website/docs/contributing/segment.mdx b/website/docs/contributing/segment.mdx
index 7136f1df..81a400f1 100644
--- a/website/docs/contributing/segment.mdx
+++ b/website/docs/contributing/segment.mdx
@@ -15,8 +15,8 @@ You can use the following template as a guide.
 package segments
 
 import (
-  "oh-my-posh/platform"
-  "oh-my-posh/properties"
+  "github.com/jandedobbeleer/oh-my-posh/src/platform"
+	"github.com/jandedobbeleer/oh-my-posh/src/properties"
 )
 
 type New struct {
@@ -31,15 +31,15 @@ const (
     NewProp properties.Property = "newprop"
 )
 
-func (n *new) Enabled() bool {
+func (n *New) Enabled() bool {
     return true
 }
 
-func (n *new) Template() string {
+func (n *New) Template() string {
     return " {{.Text}} world "
 }
 
-func (n *new) Init(props properties.Properties, env platform.Environment) {
+func (n *New) Init(props properties.Properties, env platform.Environment) {
     n.props = props
     n.env = env
 
diff --git a/website/docs/segments/upgrade.mdx b/website/docs/segments/upgrade.mdx
new file mode 100644
index 00000000..23355ef0
--- /dev/null
+++ b/website/docs/segments/upgrade.mdx
@@ -0,0 +1,46 @@
+---
+id: upgrade
+title: Upgrade notice
+sidebar_label: Upgrade
+---
+
+## What
+
+Displays when there's an update available for Oh My Posh.
+
+## Sample Configuration
+
+import Config from "@site/src/components/Config.js";
+
+<Config
+  data={{
+    type: "upgrade",
+    style: "plain",
+    foreground: "#111111",
+    background: "#FFD664",
+  }}
+/>
+
+## Properties
+
+| Name            | Type  | Description                                                                                   |
+| --------------- | ----- | --------------------------------------------------------------------------------------------- |
+| `cache_timeout` | `int` | in minutes - How long to wait before checking for a new version. Default is 10080 (one week). |
+
+## Template ([info][templates])
+
+:::note default template
+
+```template
+ \uf019
+```
+
+:::
+
+### Properties
+
+| Name       | Type     | Description            |
+| ---------- | -------- | ---------------------- |
+| `.Version` | `string` | the new version number |
+
+[templates]: /docs/configuration/templates
diff --git a/website/sidebars.js b/website/sidebars.js
index cd7d16d0..4110875f 100644
--- a/website/sidebars.js
+++ b/website/sidebars.js
@@ -118,6 +118,7 @@ module.exports = {
         "segments/time",
         "segments/ui5tooling",
         "segments/unity",
+        "segments/upgrade",
         "segments/vala",
         "segments/wakatime",
         "segments/withings",


Test patch:
diff --git a/src/segments/gitversion_test.go b/src/segments/gitversion_test.go
index eb933e6e..e5e90c6b 100644
--- a/src/segments/gitversion_test.go
+++ b/src/segments/gitversion_test.go
@@ -77,7 +77,7 @@ func TestGitversion(t *testing.T) {
 		env.On("Pwd").Return("test-dir")
 		env.On("Cache").Return(cache)
 		cache.On("Get", "test-dir").Return(tc.CacheResponse, len(tc.CacheResponse) != 0)
-		cache.On("Set", mock2.Anything, mock2.Anything, mock2.Anything).Return(tc.Response, true)
+		cache.On("Set", mock2.Anything, mock2.Anything, mock2.Anything)
 
 		env.On("RunCommand", "gitversion", []string{"-output", "json"}).Return(tc.Response, tc.CommandError)
 
diff --git a/src/segments/upgrade_test.go b/src/segments/upgrade_test.go
new file mode 100644
index 00000000..213b55a1
--- /dev/null
+++ b/src/segments/upgrade_test.go
@@ -0,0 +1,93 @@
+package segments
+
+import (
+	"errors"
+	"fmt"
+	"testing"
+
+	"github.com/jandedobbeleer/oh-my-posh/src/mock"
+	"github.com/jandedobbeleer/oh-my-posh/src/platform"
+	"github.com/jandedobbeleer/oh-my-posh/src/properties"
+	"github.com/jandedobbeleer/oh-my-posh/src/upgrade"
+
+	"github.com/alecthomas/assert"
+	mock2 "github.com/stretchr/testify/mock"
+)
+
+func TestUpgrade(t *testing.T) {
+	cases := []struct {
+		Case            string
+		ExpectedEnabled bool
+		HasCache        bool
+		CurrentVersion  string
+		LatestVersion   string
+		CacheVersion    string
+		Error           error
+	}{
+		{
+			Case:            "Should upgrade",
+			CurrentVersion:  "1.0.0",
+			LatestVersion:   "1.0.1",
+			ExpectedEnabled: true,
+		},
+		{
+			Case:           "On latest",
+			CurrentVersion: "1.0.1",
+			LatestVersion:  "1.0.1",
+		},
+		{
+			Case:  "Version error",
+			Error: errors.New("error"),
+		},
+		{
+			Case:            "On previous, from cache",
+			HasCache:        true,
+			CurrentVersion:  "1.0.2",
+			LatestVersion:   "1.0.3",
+			CacheVersion:    "1.0.2",
+			ExpectedEnabled: true,
+		},
+		{
+			Case:           "On latest, version changed",
+			HasCache:       true,
+			CurrentVersion: "1.0.2",
+			LatestVersion:  "1.0.2",
+			CacheVersion:   "1.0.1",
+		},
+		{
+			Case:            "On previous, version changed",
+			HasCache:        true,
+			CurrentVersion:  "1.0.2",
+			LatestVersion:   "1.0.3",
+			CacheVersion:    "1.0.1",
+			ExpectedEnabled: true,
+		},
+	}
+
+	for _, tc := range cases {
+		env := new(mock.MockedEnvironment)
+		cache := &mock.MockedCache{}
+
+		env.On("Cache").Return(cache)
+		if len(tc.CacheVersion) == 0 {
+			tc.CacheVersion = tc.CurrentVersion
+		}
+		cacheData := fmt.Sprintf(`{"latest":"v%s", "current": "v%s"}`, tc.LatestVersion, tc.CacheVersion)
+		cache.On("Get", UPGRADECACHEKEY).Return(cacheData, tc.HasCache)
+		cache.On("Set", mock2.Anything, mock2.Anything, mock2.Anything)
+
+		env.On("Flags").Return(&platform.Flags{Version: tc.CurrentVersion})
+
+		json := fmt.Sprintf(`{"tag_name":"v%s"}`, tc.LatestVersion)
+		env.On("HTTPRequest", upgrade.RELEASEURL).Return([]byte(json), tc.Error)
+
+		ug := &Upgrade{
+			env:   env,
+			props: properties.Map{},
+		}
+
+		enabled := ug.Enabled()
+
+		assert.Equal(t, tc.ExpectedEnabled, enabled, tc.Case)
+	}
+}
diff --git a/src/upgrade/notice_test.go b/src/upgrade/notice_test.go
index e9efa142..fe7921c0 100644
--- a/src/upgrade/notice_test.go
+++ b/src/upgrade/notice_test.go
@@ -44,7 +44,7 @@ func TestCanUpgrade(t *testing.T) {
 		env.On("Getenv", "POSH_INSTALLER").Return(tc.Installer)
 
 		json := fmt.Sprintf(`{"tag_name":"%s"}`, tc.LatestVersion)
-		env.On("HTTPRequest", releaseURL).Return([]byte(json), tc.Error)
+		env.On("HTTPRequest", RELEASEURL).Return([]byte(json), tc.Error)
 		// ignore the notice
 		_, canUpgrade := Notice(env)
 		assert.Equal(t, tc.Expected, canUpgrade, tc.Case)