Repository: JanDeDobbeleer/oh-my-posh
Pull request #1945 (2022-03-19 08:27:24)

feat: adds R segment
### Prerequisites

- [x] I have read and understood the `CONTRIBUTING` guide
- [x] The commit message follows the [conventional commits][cc] guidelines
- [x] Tests for the changes have been added (for bug fixes/features)
- [x] Docs have been added / updated (for bug fixes/features)

### Description

Adds an [R][r] language segment to display the active R version. Will first attempt to fetch from `Rscript` as it is slightly faster than `R`.

![image](https://user-images.githubusercontent.com/85419773/159113663-34ebab06-c688-4a0c-863d-17a14fa40468.png)

Also I couldn't find a stable release page for the `versionURLTemplate` so I just used the R homepage

[cc]: https://www.conventionalcommits.org/en/v1.0.0/#summary
[r]: https://www.r-project.org/



Patch:
diff --git a/docs/docs/segment-r.md b/docs/docs/segment-r.md
new file mode 100644
index 00000000..ddd90702
--- /dev/null
+++ b/docs/docs/segment-r.md
@@ -0,0 +1,58 @@
+---
+id: r
+title: R
+sidebar_label: R
+---
+
+## What
+
+Display the currently active [R][r-homepage] version.
+
+## Sample Configuration
+
+```json
+{
+  "type": "r",
+  "style": "powerline",
+  "powerline_symbol": "\uE0B0",
+  "foreground": "blue",
+  "background": "lightWhite",
+  "properties": {
+    "template": " R {{ .Full }} "
+  }
+}
+```
+
+## Properties
+
+- home_enabled: `boolean` - display the segment in the HOME folder or not - defaults to `false`
+- fetch_version: `boolean` - display the R version - defaults to `true`
+- display_error: `boolean` - show the error context when failing to retrieve the version information - defaults to `true`
+- missing_command_text: `string` - text to display when the command is missing - defaults to empty
+- display_mode: `string` - determines when the segment is displayed
+  - `always`: the segment is always displayed
+  - `files`: the segment is only displayed when `*.R`, `*.Rmd`, `*.Rsx`, `*.Rda`, `*.Rd`, `*.Rproj`, or `.Rproj.user`
+    files are present (default)
+
+## Template ([info][templates])
+
+:::note default template
+
+``` template
+{{ if .Error }}{{ .Error }}{{ else }}{{ .Full }}{{ end }}
+```
+
+:::
+
+### Properties
+
+- `.Full`: `string` - the full version
+- `.Major`: `string` - major number
+- `.Minor`: `string` - minor number
+- `.Patch`: `string` - patch number
+- `.Prerelease`: `string` - prerelease info text
+- `.BuildMetadata`: `string` - build metadata
+- `.Error`: `string` - when fetching the version string errors
+
+[templates]: /docs/config-templates
+[r-homepage]: https://www.r-project.org/
diff --git a/docs/sidebars.js b/docs/sidebars.js
index cc44a85e..bb0d74ca 100644
--- a/docs/sidebars.js
+++ b/docs/sidebars.js
@@ -86,6 +86,7 @@ module.exports = {
         "plastic",
         "project",
         "python",
+        "r",
         "root",
         "ruby",
         "rust",
diff --git a/src/engine/segment.go b/src/engine/segment.go
index e513b670..d1224117 100644
--- a/src/engine/segment.go
+++ b/src/engine/segment.go
@@ -168,6 +168,8 @@ const (
 	NPM SegmentType = "npm"
 	// Project version
 	PROJECT SegmentType = "project"
+	// R version
+	R SegmentType = "r"
 )
 
 func (segment *Segment) shouldIncludeFolder() bool {
@@ -302,6 +304,7 @@ func (segment *Segment) mapSegmentWithWriter(env environment.Environment) error
 		KOTLIN:        &segments.Kotlin{},
 		SWIFT:         &segments.Swift{},
 		CDS:           &segments.Cds{},
+		R:             &segments.R{},
 	}
 	if segment.Properties == nil {
 		segment.Properties = make(properties.Map)
diff --git a/src/segments/r.go b/src/segments/r.go
new file mode 100644
index 00000000..8d095bb1
--- /dev/null
+++ b/src/segments/r.go
@@ -0,0 +1,45 @@
+package segments
+
+import (
+	"oh-my-posh/environment"
+	"oh-my-posh/properties"
+)
+
+type R struct {
+	language
+}
+
+func (r *R) Template() string {
+	return languageTemplate
+}
+
+func (r *R) Init(props properties.Properties, env environment.Environment) {
+	rRegex := `R (scripting front-end )?version (?P<version>((?P<major>[0-9]+).(?P<minor>[0-9]+).(?P<patch>[0-9]+)))`
+	r.language = language{
+		env:        env,
+		props:      props,
+		extensions: []string{"*.R", "*.Rmd", "*.Rsx", "*.Rda", "*.Rd", "*.Rproj", ".Rproj.user"},
+		commands: []*cmd{
+			{
+				executable: "Rscript",
+				args:       []string{"--version"},
+				regex:      rRegex,
+			},
+			{
+				executable: "R",
+				args:       []string{"--version"},
+				regex:      rRegex,
+			},
+			{
+				executable: "R.exe",
+				args:       []string{"--version"},
+				regex:      rRegex,
+			},
+		},
+		versionURLTemplate: "https://www.r-project.org/",
+	}
+}
+
+func (r *R) Enabled() bool {
+	return r.language.Enabled()
+}
diff --git a/themes/schema.json b/themes/schema.json
index c256279b..0f12f664 100644
--- a/themes/schema.json
+++ b/themes/schema.json
@@ -200,7 +200,8 @@
             "kotlin",
             "swift",
             "npm",
-            "project"
+            "project",
+            "r"
           ]
         },
         "style": {
@@ -2094,6 +2095,32 @@
               }
             }
           }
+        },
+        {
+          "if": {
+            "properties": {
+              "type": { "const": "r" }
+            }
+          },
+          "then": {
+            "title": "R Segment",
+            "description": "https://ohmyposh.dev/docs/r",
+            "properties": {
+              "properties": {
+                "properties": {
+                  "fetch_version": {
+                    "$ref": "#/definitions/fetch_version"
+                  },
+                  "display_mode": {
+                    "$ref": "#/definitions/display_mode"
+                  },
+                  "missing_command_text": {
+                    "$ref": "#/definitions/missing_command_text"
+                  }
+                }
+              }
+            }
+          }
         }
       ]
     }


Test patch:
diff --git a/src/segments/r_test.go b/src/segments/r_test.go
new file mode 100644
index 00000000..994addec
--- /dev/null
+++ b/src/segments/r_test.go
@@ -0,0 +1,57 @@
+package segments
+
+import (
+	"fmt"
+	"oh-my-posh/environment"
+	"oh-my-posh/mock"
+	"oh-my-posh/properties"
+	"testing"
+
+	"github.com/stretchr/testify/assert"
+)
+
+func TestR(t *testing.T) {
+	cases := []struct {
+		Case           string
+		ExpectedString string
+		HasRscript     bool
+		HasR           bool
+		HasRexe        bool
+		Version        string
+	}{
+		{Case: "Rscript 4.1.3", ExpectedString: "4.1.3", HasRscript: true, Version: "R scripting front-end version 4.1.3 (2022-03-10)"},
+		{Case: "Rscript 4.1.3 patched", ExpectedString: "4.1.3", HasRscript: true, Version: "R scripting front-end version 4.1.3 Patched (2022-03-10 r81896)"},
+		{Case: "Rscript 4.0.0", ExpectedString: "4.0.0", HasRscript: true, Version: "R scripting front-end version 4.0.0 (2020-04-24)"},
+		{Case: "Rscript devel", ExpectedString: "4.2.0", HasRscript: true, Version: "R scripting front-end version 4.2.0 Under development (unstable) (2022-03-14 r81896)"},
+
+		{Case: "R 4.1.2", ExpectedString: "4.1.2", HasR: true, Version: "R version 4.1.2 (2021-11-01) -- \"Bird Hippie\""},
+		{Case: "R 4.1.3 patched", ExpectedString: "4.1.3", HasR: true, Version: "R version 4.1.3 Patched (2022-03-10 r81896) -- \"One Push-Up\""},
+		{Case: "R 4.0.0", ExpectedString: "4.0.0", HasR: true, Version: "R version 4.0.0 (2020-04-24) -- \"Arbor Day\""},
+
+		{Case: "R.exe 4.1.2", ExpectedString: "4.1.2", HasRexe: true, Version: "R version 4.1.2 (2021-11-01) -- \"Bird Hippie\""},
+		{Case: "R.exe 4.1.3 patched", ExpectedString: "4.1.3", HasRexe: true, Version: "R version 4.1.3 Patched (2022-03-10 r81896) -- \"One Push-Up\""},
+		{Case: "R.exe 4.0.0", ExpectedString: "4.0.0", HasRexe: true, Version: "R version 4.0.0 (2020-04-24) -- \"Arbor Day\""},
+	}
+	for _, tc := range cases {
+		env := new(mock.MockedEnvironment)
+		env.On("HasCommand", "Rscript").Return(tc.HasRscript)
+		env.On("RunCommand", "Rscript", []string{"--version"}).Return(tc.Version, nil)
+		env.On("HasCommand", "R").Return(tc.HasR)
+		env.On("RunCommand", "R", []string{"--version"}).Return(tc.Version, nil)
+		env.On("HasCommand", "R.exe").Return(tc.HasRexe)
+		env.On("RunCommand", "R.exe", []string{"--version"}).Return(tc.Version, nil)
+		env.On("HasFiles", "*.R").Return(true)
+		env.On("Pwd").Return("/usr/home/project")
+		env.On("Home").Return("/usr/home")
+		env.On("TemplateCache").Return(&environment.TemplateCache{
+			Env: make(map[string]string),
+		})
+		props := properties.Map{
+			properties.FetchVersion: true,
+		}
+		r := &R{}
+		r.Init(props, env)
+		assert.True(t, r.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case))
+		assert.Equal(t, tc.ExpectedString, renderTemplate(env, r.Template(), r), fmt.Sprintf("Failed in case: %s", tc.Case))
+	}
+}