Repository: JanDeDobbeleer/oh-my-posh
Pull request #904 (2021-08-18 06:32:46)

feat: owd custom timeout + all units support + hyperlink
### Prerequisites

- [x] I have read and understood the `CONTRIBUTING` guide
- [x] The commit message follows the [conventional commits][cc] guidelines
- [ ] Tests for the changes have been added (for bug fixes/features)
- [ ] Docs have been added / updated (for bug fixes/features)

### Description

there is an issue with right prompt and hyperlink(nothing to do with the segment, ansi len calculation) to fix. 
![image](https://user-images.githubusercontent.com/1829553/129849442-6cb69e91-8840-49f9-9101-3ce56812512e.png)

all units support for openweathermap 
json schema updated(missing entries for owm)
owd files renamed to owm(mixed before)
hyperlink support for owm + fix when hyperlink used in the right prompt(pwsh only)
custom http timeout added



<!---

Tips:

If you're not comfortable with working with Git, we're working a guide (https://ohmyposh.dev/docs/contributing_git) to help you out.
Oh My Posh advises GitKraken (https://www.gitkraken.com/invite/nQmDPR9D) as your preferred cross platform Git GUI power tool.

-->

[cc]: https://www.conventionalcommits.org/en/v1.0.0/#summary


lnu: ![image](https://user-images.githubusercontent.com/1829553/130016660-68c0b515-6ea6-4a14-a87e-c4fabbdb0dcd.png)

works fine with multiple segments having enable_hyperlink enabled on both left/right prompt

Patch:
diff --git a/docs/docs/segment-owm.md b/docs/docs/segment-owm.md
index a85ba2c4..39d68092 100644
--- a/docs/docs/segment-owm.md
+++ b/docs/docs/segment-owm.md
@@ -27,7 +27,9 @@ The free tier for *Current weather and forecasts collection* is sufficient.
   "properties": {
     "apikey": "<YOUR_API_KEY>",
     "location": "AMSTERDAM,NL",
-    "units": "metric"
+    "units": "metric",
+    "enable_hyperlink" : false,
+    "http_timeout": 20
   }
 }
 ```
@@ -39,4 +41,6 @@ The free tier for *Current weather and forecasts collection* is sufficient.
                         Formatted as <City,STATE,COUNTRY_CODE>. City name, state code and country code divided by comma.
                         Please, refer to ISO 3166 for the state codes or country codes - defaults to `DE BILT,NL`
 - units: `string` - Units of measurement.
+- enable_hyperlink: `bool` - Displays an hyperlink to get openweathermap data
+- http_timeout: `int` - The default timeout for http request is 20ms.
 Available values are standard (kelvin), metric (celsius), and imperial (fahrenheit) - defaults to `standard`
diff --git a/src/ansi.go b/src/ansi.go
index 065d5a73..c8861ea4 100644
--- a/src/ansi.go
+++ b/src/ansi.go
@@ -121,8 +121,8 @@ func (a *ansiUtils) lenWithoutANSI(text string) int {
 	if len(text) == 0 {
 		return 0
 	}
-	// replace hyperlinks
-	matches := findAllNamedRegexMatch(`(?P<STR>\x1b]8;;file:\/\/(.+)\x1b\\(?P<URL>.+)\x1b]8;;\x1b\\)`, text)
+	// replace hyperlinks(file/http/https)
+	matches := findAllNamedRegexMatch(`(?P<STR>\x1b]8;;(file|http|https):\/\/(.+?)\x1b\\(?P<URL>.+?)\x1b]8;;\x1b\\)`, text)
 	for _, match := range matches {
 		text = strings.ReplaceAll(text, match[str], match[url])
 	}
diff --git a/src/config.go b/src/config.go
index abf7427e..6088e6ba 100644
--- a/src/config.go
+++ b/src/config.go
@@ -40,6 +40,10 @@ type TransientPrompt struct {
 const (
 	// EnableHyperlink enable hyperlink
 	EnableHyperlink Property = "enable_hyperlink"
+	// HTTPTimeout timeout used when executing http request
+	HTTPTimeout Property = "http_timeout"
+	// DefaultHTTPTimeout default timeout used when executing http request
+	DefaultHTTPTimeout = 20
 )
 
 func printConfigError(err error) {
diff --git a/src/environment.go b/src/environment.go
index ea191ab2..1f51c3fa 100644
--- a/src/environment.go
+++ b/src/environment.go
@@ -72,7 +72,7 @@ type environmentInfo interface {
 	getBatteryInfo() ([]*battery.Battery, error)
 	getShellName() string
 	getWindowTitle(imageName, windowTitleRegex string) (string, error)
-	doGet(url string) ([]byte, error)
+	doGet(url string, timeout int) ([]byte, error)
 	hasParentFilePath(path string) (fileInfo *fileInfo, err error)
 	isWsl() bool
 	stackCount() int
@@ -389,9 +389,9 @@ func (env *environment) getShellName() string {
 	return *env.args.Shell
 }
 
-func (env *environment) doGet(url string) ([]byte, error) {
+func (env *environment) doGet(url string, timeout int) ([]byte, error) {
 	defer env.tracer.trace(time.Now(), "doGet", url)
-	ctx, cncl := context.WithTimeout(context.Background(), time.Millisecond*20)
+	ctx, cncl := context.WithTimeout(context.Background(), time.Millisecond*time.Duration(timeout))
 	defer cncl()
 	request, err := http.NewRequestWithContext(ctx, http.MethodGet, url, nil)
 	if err != nil {
diff --git a/src/segment.go b/src/segment.go
index 35b30800..985b7333 100644
--- a/src/segment.go
+++ b/src/segment.go
@@ -121,7 +121,8 @@ const (
 	Nbgv SegmentType = "nbgv"
 	// Rust writes the cargo version information if cargo.toml is present
 	Rust SegmentType = "rust"
-	OWM  SegmentType = "owm"
+	// OWM writes the weather coming from openweatherdata
+	OWM SegmentType = "owm"
 )
 
 func (segment *Segment) string() string {
diff --git a/src/segment_owd.go b/src/segment_owm.go
similarity index 68%
rename from src/segment_owd.go
rename to src/segment_owm.go
index 6a7ed280..08aff3e3 100644
--- a/src/segment_owd.go
+++ b/src/segment_owm.go
@@ -10,12 +10,17 @@ type owm struct {
 	env         environmentInfo
 	temperature float64
 	weather     string
+	url         string
+	units       string
 }
 
 const (
-	APIKEY   Property = "apikey"
+	// APIKEY openweathermap api key
+	APIKEY Property = "apikey"
+	// LOCATION openweathermap location
 	LOCATION Property = "location"
-	UNITS    Property = "units"
+	// UNITS openweathermap units
+	UNITS Property = "units"
 )
 
 type weather struct {
@@ -38,16 +43,32 @@ func (d *owm) enabled() bool {
 }
 
 func (d *owm) string() string {
-	return fmt.Sprintf("%s (%g\ue33e)", d.weather, d.temperature)
+	unitIcon := "\ue33e"
+	switch d.units {
+	case "imperial":
+		unitIcon = "°F" // \ue341"
+	case "metric":
+		unitIcon = "°C" // \ue339"
+	case "":
+		fallthrough
+	case "standard":
+		unitIcon = "°K" // \ufa05"
+	}
+	text := fmt.Sprintf("%s (%g%s)", d.weather, d.temperature, unitIcon)
+	if d.props.getBool(EnableHyperlink, false) {
+		text = fmt.Sprintf("[%s](%s)", text, d.url)
+	}
+	return text
 }
 
 func (d *owm) setStatus() error {
 	apikey := d.props.getString(APIKEY, ".")
 	location := d.props.getString(LOCATION, "De Bilt,NL")
 	units := d.props.getString(UNITS, "standard")
+	timeout := d.props.getInt(HTTPTimeout, DefaultHTTPTimeout)
 
 	url := fmt.Sprintf("http://api.openweathermap.org/data/2.5/weather?q=%s&units=%s&appid=%s", location, units, apikey)
-	body, err := d.env.doGet(url)
+	body, err := d.env.doGet(url, timeout)
 	if err != nil {
 		return err
 	}
@@ -79,6 +100,8 @@ func (d *owm) setStatus() error {
 		icon = "\ue313"
 	}
 	d.weather = icon
+	d.url = url
+	d.units = units
 	return nil
 }
 
diff --git a/src/segment_ytm.go b/src/segment_ytm.go
index 6912ec94..080fe0f8 100644
--- a/src/segment_ytm.go
+++ b/src/segment_ytm.go
@@ -85,7 +85,8 @@ type track struct {
 func (y *ytm) setStatus() error {
 	// https://github.com/ytmdesktop/ytmdesktop/wiki/Remote-Control-API
 	url := y.props.getString(APIURL, "http://127.0.0.1:9863")
-	body, err := y.env.doGet(url + "/query")
+	httpTimeout := y.props.getInt(APIURL, DefaultHTTPTimeout)
+	body, err := y.env.doGet(url+"/query", httpTimeout)
 	if err != nil {
 		return err
 	}
diff --git a/themes/schema.json b/themes/schema.json
index 04dedf46..aecef061 100644
--- a/themes/schema.json
+++ b/themes/schema.json
@@ -27,6 +27,18 @@
       "description": "Show or hide the version number",
       "default": true
     },
+    "enable_hyperlink": {
+      "type": "boolean",
+      "title": "Enable hyperlink",
+      "description": "Displays an hyperlink for the current segment",
+      "default": false
+    },
+    "http_timeout": {
+      "type": "integer",
+      "title": "Http requst timeout",
+      "description": "Timeout value to use for http request",
+      "default": 20
+    },
     "display_mode": {
       "type": "string",
       "title": "Display Mode",
@@ -155,7 +167,8 @@
             "azfunc",
             "crystal",
             "dart",
-            "rust"
+            "rust",
+            "owm"
           ]
         },
         "style": {
@@ -681,10 +694,10 @@
                   "behind_color": { "$ref": "#/definitions/color" },
                   "ahead_color": { "$ref": "#/definitions/color" },
                   "branch_max_length": {
-                      "type": "integer",
-                      "title": "Branch max length",
-                      "description": "the max length for the displayed branch name where 0 implies full length",
-                      "default": 0
+                    "type": "integer",
+                    "title": "Branch max length",
+                    "description": "the max length for the displayed branch name where 0 implies full length",
+                    "default": 0
                   }
                 }
               }
@@ -1227,17 +1240,14 @@
                   "description": "Replace known locations in the path with the replacements before applying the style.",
                   "default": true
                 },
-                "enable_hyperlink": {
-                  "type": "boolean",
-                  "title": "Enable hyperlink",
-                  "description": "Displays an hyperlink for the current path",
-                  "default": false
-                },
                 "stack_count_enabled": {
                   "type": "boolean",
                   "title": "Show/hide stack count",
                   "description": "Displays the stack count when using pushd/popd",
                   "default": false
+                },
+                "enable_hyperlink": {
+                  "$ref": "#/definitions/enable_hyperlink"
                 }
               }
             }
@@ -1512,6 +1522,9 @@
                     "title": "API URL",
                     "description": "The YTMDA Remote Control API URL",
                     "default": "http://127.0.0.1:9863"
+                  },
+                  "http_timeout": {
+                    "$ref": "#/definitions/http_timeout"
                   }
                 }
               }
@@ -1547,6 +1560,12 @@
                     "title": "units",
                     "description": "Units of measurement. Available values are standard (kelvin), metric (celsius), and imperial (fahrenheit). Default is standard",
                     "default": "standard"
+                  },
+                  "enable_hyperlink": {
+                    "$ref": "#/definitions/enable_hyperlink"
+                  },
+                  "http_timeout": {
+                    "$ref": "#/definitions/http_timeout"
                   }
                 }
               }
@@ -1687,4 +1706,4 @@
       }
     }
   }
-}
\ No newline at end of file
+}


Test patch:
diff --git a/src/segment_owd_test.go b/src/segment_owm_test.go
similarity index 93%
rename from src/segment_owd_test.go
rename to src/segment_owm_test.go
index 11adc992..ffc4f838 100644
--- a/src/segment_owd_test.go
+++ b/src/segment_owm_test.go
@@ -8,7 +8,7 @@ import (
 	"github.com/stretchr/testify/assert"
 )
 
-func TestOWDSegmentSingle(t *testing.T) {
+func TestOWMSegmentSingle(t *testing.T) {
 	cases := []struct {
 		Case            string
 		JSONResponse    string
@@ -19,7 +19,7 @@ func TestOWDSegmentSingle(t *testing.T) {
 		{
 			Case:            "Sunny Display",
 			JSONResponse:    `{"weather":[{"icon":"01d"}],"main":{"temp":20}}`,
-			ExpectedString:  "\ufa98 (20\ue33e)",
+			ExpectedString:  "\ufa98 (20°C)",
 			ExpectedEnabled: true,
 		},
 		{
@@ -59,7 +59,7 @@ func TestOWDSegmentSingle(t *testing.T) {
 	}
 }
 
-func TestOWDSegmentIcons(t *testing.T) {
+func TestOWMSegmentIcons(t *testing.T) {
 	cases := []struct {
 		Case               string
 		IconID             string
@@ -124,7 +124,7 @@ func TestOWDSegmentIcons(t *testing.T) {
 
 		url := "http://api.openweathermap.org/data/2.5/weather?q=AMSTERDAM,NL&units=metric&appid=key"
 		response := fmt.Sprintf(`{"weather":[{"icon":"%s"}],"main":{"temp":20}}`, tc.IconID)
-		expectedString := fmt.Sprintf("%s (20\ue33e)", tc.ExpectedIconString)
+		expectedString := fmt.Sprintf("%s (20°C)", tc.ExpectedIconString)
 
 		env.On("doGet", url).Return([]byte(response), nil)
 
diff --git a/src/segment_path_test.go b/src/segment_path_test.go
index c124c40b..56a941c3 100644
--- a/src/segment_path_test.go
+++ b/src/segment_path_test.go
@@ -124,7 +124,7 @@ func (env *MockedEnvironment) getWindowTitle(imageName, windowTitleRegex string)
 	return args.String(0), args.Error(1)
 }
 
-func (env *MockedEnvironment) doGet(url string) ([]byte, error) {
+func (env *MockedEnvironment) doGet(url string, timeout int) ([]byte, error) {
 	args := env.Called(url)
 	return args.Get(0).([]byte), args.Error(1)
 }