From 30e4a591d7a4b77ee498e777e0a6c2880b784930 Mon Sep 17 00:00:00 2001 From: Warren Buckley Date: Sat, 28 Oct 2023 08:54:43 +0100 Subject: [PATCH] feat(segment): umbraco segment to display modern or legacy version --- src/engine/segment.go | 3 + src/segments/umbraco.go | 159 +++++++++++++++ src/segments/umbraco_test.go | 128 ++++++++++++ src/test/umbraco/MyProject.csproj | 35 ++++ src/test/umbraco/web.config | 325 ++++++++++++++++++++++++++++++ themes/schema.json | 24 +++ website/docs/segments/umbraco.mdx | 56 +++++ website/sidebars.js | 1 + 8 files changed, 731 insertions(+) create mode 100644 src/segments/umbraco.go create mode 100644 src/segments/umbraco_test.go create mode 100644 src/test/umbraco/MyProject.csproj create mode 100644 src/test/umbraco/web.config create mode 100644 website/docs/segments/umbraco.mdx diff --git a/src/engine/segment.go b/src/engine/segment.go index 80fafc1b..a2107745 100644 --- a/src/engine/segment.go +++ b/src/engine/segment.go @@ -232,6 +232,8 @@ const ( TIME SegmentType = "time" // UI5 Tooling segment UI5TOOLING SegmentType = "ui5tooling" + // UMBRACO writes the Umbraco version if Umbraco is present + UMBRACO SegmentType = "umbraco" // UNITY writes which Unity version is currently active UNITY SegmentType = "unity" // UPGRADE lets you know if you can upgrade Oh My Posh @@ -323,6 +325,7 @@ var Segments = map[SegmentType]func() SegmentWriter{ TEXT: func() SegmentWriter { return &segments.Text{} }, TIME: func() SegmentWriter { return &segments.Time{} }, UI5TOOLING: func() SegmentWriter { return &segments.UI5Tooling{} }, + UMBRACO: func() SegmentWriter { return &segments.Umbraco{} }, UNITY: func() SegmentWriter { return &segments.Unity{} }, UPGRADE: func() SegmentWriter { return &segments.Upgrade{} }, VALA: func() SegmentWriter { return &segments.Vala{} }, diff --git a/src/segments/umbraco.go b/src/segments/umbraco.go new file mode 100644 index 00000000..5c3209fd --- /dev/null +++ b/src/segments/umbraco.go @@ -0,0 +1,159 @@ +package segments + +import ( + "encoding/xml" + "path/filepath" + "strings" + + "github.com/jandedobbeleer/oh-my-posh/src/platform" + "github.com/jandedobbeleer/oh-my-posh/src/properties" +) + +type Umbraco struct { + props properties.Properties + env platform.Environment + + Modern bool + Version string +} + +type CSProj struct { + PackageReferences []struct { + Name string `xml:"include,attr"` + Version string `xml:"version,attr"` + } `xml:"ItemGroup>PackageReference"` +} + +type WebConfig struct { + AppSettings []struct { + Key string `xml:"key,attr"` + Value string `xml:"value,attr"` + } `xml:"appSettings>add"` +} + +func (u *Umbraco) Enabled() bool { + var location string + + // Check if we have a folder called Umbraco or umbraco in the current directory or a parent directory + folders := []string{"umbraco", "Umbraco"} + for _, folder := range folders { + if file, err := u.env.HasParentFilePath(folder); err == nil { + location = file.ParentFolder + break + } + } + + if len(location) == 0 { + u.env.Debug("No umbraco folder found in parent directories") + return false + } + + files := u.env.LsDir(location) + + // Loop over files where we found the Umbraco folder + // To see if we can find a web.config or *.csproj file + // If we do then we can scan the file to see if Umbraco has been installed + for _, file := range files { + if file.IsDir() { + continue + } + + if strings.EqualFold(file.Name(), "web.config") { + return u.TryFindLegacyUmbraco(filepath.Join(location, file.Name())) + } + + if strings.EqualFold(filepath.Ext(file.Name()), ".csproj") { + return u.TryFindModernUmbraco(filepath.Join(location, file.Name())) + } + } + + return false +} + +func (u *Umbraco) Template() string { + return "{{.Version}} " +} + +func (u *Umbraco) Init(props properties.Properties, env platform.Environment) { + u.props = props + u.env = env +} + +func (u *Umbraco) TryFindModernUmbraco(configPath string) bool { + // Check the passed in filepath is not empty + if len(configPath) == 0 { + u.env.Debug("UMBRACO: No .CSProj file path passed in") + return false + } + + // Read the file contents of the csproj file + contents := u.env.FileContent(configPath) + + // As XML unmarshal does not support case insenstivity attributes + // this is just a simple string replace to lowercase the attribute + contents = strings.ReplaceAll(contents, "Include=", "include=") + contents = strings.ReplaceAll(contents, "Version=", "version=") + + // XML Unmarshal - map the contents of the file to the CSProj struct + csProjPackages := CSProj{} + err := xml.Unmarshal([]byte(contents), &csProjPackages) + + if err != nil { + u.env.Debug("UMBRACO: Error while trying to parse XML of .csproj file") + u.env.Debug(err.Error()) + } + + // Loop over all the package references + for _, packageReference := range csProjPackages.PackageReferences { + if strings.EqualFold(packageReference.Name, "umbraco.cms") { + u.Modern = true + u.Version = packageReference.Version + + return true + } + } + + return false +} + +func (u *Umbraco) TryFindLegacyUmbraco(configPath string) bool { + // Check the passed in filepath is not empty + if len(configPath) == 0 { + u.env.Debug("UMBRACO: No web.config file path passed in") + return false + } + + // Read the file contents of the web.config + contents := u.env.FileContent(configPath) + + // As XML unmarshal does not support case insenstivity attributes + // this is just a simple string replace to lowercase the attribute + contents = strings.ReplaceAll(contents, "Key=", "key=") + contents = strings.ReplaceAll(contents, "Value=", "value=") + + // XML Unmarshal - web.config all AppSettings keys + webConfigAppSettings := WebConfig{} + err := xml.Unmarshal([]byte(contents), &webConfigAppSettings) + + if err != nil { + u.env.Debug("UMBRACO: Error while trying to parse XML of web.config file") + u.env.Debug(err.Error()) + } + + // Loop over all the package references + for _, appSetting := range webConfigAppSettings.AppSettings { + if strings.EqualFold(appSetting.Key, "umbraco.core.configurationstatus") { + u.Modern = false + + if len(appSetting.Value) == 0 { + u.Version = "Unknown" + } else { + u.Version = appSetting.Value + } + + return true + } + } + + return false +} diff --git a/src/segments/umbraco_test.go b/src/segments/umbraco_test.go new file mode 100644 index 00000000..c26deead --- /dev/null +++ b/src/segments/umbraco_test.go @@ -0,0 +1,128 @@ +package segments + +import ( + "errors" + "io/fs" + "os" + "path/filepath" + "testing" + + "github.com/jandedobbeleer/oh-my-posh/src/mock" + "github.com/jandedobbeleer/oh-my-posh/src/platform" + + "github.com/stretchr/testify/assert" + mock2 "github.com/stretchr/testify/mock" +) + +func TestUmbracoSegment(t *testing.T) { + cases := []struct { + Case string + ExpectedEnabled bool + ExpectedString string + Template string + HasUmbracoFolder bool + HasCsproj bool + HasWebConfig bool + }{ + { + Case: "No Umbraco folder found", + HasUmbracoFolder: false, + ExpectedEnabled: false, // Segment should not be enabled + }, + { + Case: "Umbraco Folder but NO web.config or .csproj", + HasUmbracoFolder: true, + HasCsproj: false, + HasWebConfig: false, + ExpectedEnabled: false, // Segment should not be enabled + }, + { + Case: "Umbraco Folder and web.config but NO .csproj", + HasUmbracoFolder: true, + HasCsproj: false, + HasWebConfig: true, + ExpectedEnabled: true, // Segment should be enabled and visible + Template: "{{ .Version }}", + ExpectedString: "8.18.9", + }, + { + Case: "Umbraco Folder and .csproj but NO web.config", + HasUmbracoFolder: true, + HasCsproj: true, + HasWebConfig: false, + ExpectedEnabled: true, // Segment should be enabled and visible + Template: "{{ .Version }}", + ExpectedString: "12.1.2", + }, + { + Case: "Umbraco Folder and .csproj with custom template", + HasUmbracoFolder: true, + HasCsproj: true, + ExpectedEnabled: true, + Template: "Version:{{ .Version }} ModernUmbraco:{{ .Modern }}", + ExpectedString: "Version:12.1.2 ModernUmbraco:true", + }, + } + + for _, tc := range cases { + // Prepare/arrange the test + env := new(mock.MockedEnvironment) + var sampleCSProj, sampleWebConfig string + + if tc.HasCsproj { + content, _ := os.ReadFile("../test/umbraco/MyProject.csproj") + sampleCSProj = string(content) + } + if tc.HasWebConfig { + content, _ := os.ReadFile("../test/umbraco/web.config") + sampleWebConfig = string(content) + } + + const umbracoProjectDirectory = "/workspace/MyProject" + env.On("Pwd").Return(umbracoProjectDirectory) + env.On("FileContent", filepath.Join(umbracoProjectDirectory, "MyProject.csproj")).Return(sampleCSProj) + env.On("FileContent", filepath.Join(umbracoProjectDirectory, "web.config")).Return(sampleWebConfig) + env.On("Debug", mock2.Anything) + + if tc.HasUmbracoFolder { + fileInfo := &platform.FileInfo{ + Path: "/workspace/MyProject/Umbraco", + ParentFolder: "/workspace/MyProject", + IsDir: true, + } + + env.On("HasParentFilePath", "umbraco").Return(fileInfo, nil) + } else { + env.On("HasParentFilePath", "Umbraco").Return(&platform.FileInfo{}, errors.New("no such file or directory")) + env.On("HasParentFilePath", "umbraco").Return(&platform.FileInfo{}, errors.New("no such file or directory")) + } + + dirEntries := []fs.DirEntry{} + if tc.HasCsproj { + dirEntries = append(dirEntries, &MockDirEntry{ + name: "MyProject.csproj", + isDir: false, + }) + } + + if tc.HasWebConfig { + dirEntries = append(dirEntries, &MockDirEntry{ + name: "web.config", + isDir: false, + }) + } + + env.On("LsDir", umbracoProjectDirectory).Return(dirEntries) + + // Setup the Umbraco segment with the mocked environment & properties + umb := &Umbraco{ + env: env, + } + + // Assert the test results + // Check if the segment should be enabled and + // the rendered string matches what we expect when specifying a template for the segment + assert.Equal(t, tc.ExpectedEnabled, umb.Enabled(), tc.Case) + assert.Equal(t, tc.ExpectedString, renderTemplate(env, tc.Template, umb), tc.Case) + } +} diff --git a/src/test/umbraco/MyProject.csproj b/src/test/umbraco/MyProject.csproj new file mode 100644 index 00000000..9f990a6e --- /dev/null +++ b/src/test/umbraco/MyProject.csproj @@ -0,0 +1,35 @@ + + + net7.0 + enable + enable + + + + + + + + + + + + + + + + + + + + + true + + + + + false + false + + + diff --git a/src/test/umbraco/web.config b/src/test/umbraco/web.config new file mode 100644 index 00000000..7cb15417 --- /dev/null +++ b/src/test/umbraco/web.config @@ -0,0 +1,325 @@ + + + + + +
+ + +
+
+ + + +
+
+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/themes/schema.json b/themes/schema.json index 02a08af1..1e4bd352 100644 --- a/themes/schema.json +++ b/themes/schema.json @@ -310,6 +310,7 @@ "text", "terraform", "ui5tooling", + "umbraco", "unity", "upgrade", "wakatime", @@ -3284,6 +3285,29 @@ } } } + }, + { + "if": { + "properties": { + "type": { "const": "umbraco" } + } + }, + "then": { + "title": "Display something umbraco", + "description": "https://ohmyposh.dev/docs/segments/umbraco", + "properties": { + "properties": { + "properties": { + "newprop": { + "type": "string", + "title": "New Property", + "description": "the default text to display", + "default": "Hello" + } + } + } + } + } } ] } diff --git a/website/docs/segments/umbraco.mdx b/website/docs/segments/umbraco.mdx new file mode 100644 index 00000000..cc403fe0 --- /dev/null +++ b/website/docs/segments/umbraco.mdx @@ -0,0 +1,56 @@ +--- +id: umbraco +title: Umbraco +sidebar_label: Umbraco +--- + +## What + +Display current Umbraco Version if found inside the current working directory. +The segment will only show based on the following logic + +* The current folder contains the folder named umbraco +* Modern Umbraco (.NET Core) + * Check to see if current folder contains one or more .csproj files + * Open .csproj XML files and check to see if Umbraco is installed as a PackageReference + * Read the installed version +* Legacy Umbraco (.NET Framework) + * Check to see if the current folder contains a web.config + * Open the XML and look for AppSettings keys + * If umbraco is installed it has a setting called umbraco.core.configurationstatus + * Read the value inside this AppSetting to get its version + +## Sample Configuration + +import Config from '@site/src/components/Config.js'; + + + +## Template ([info][templates]) + +:::note default template + +```template +{{ .Version }} +``` + +::: + +## Properties + +| Name | Type | Description | +| -------------- | --------- | ------------------------------------------------------------------------------------------------------------------- | +| `.Modern` | `boolean` | a boolean to detemine if this is modern Umbraco V9+ using modern .NET or if its legacy Umbraco using .NET Framework | +| `.Version` | `string` | the version of umbraco found | diff --git a/website/sidebars.js b/website/sidebars.js index 22bfabad..dfeba6bb 100644 --- a/website/sidebars.js +++ b/website/sidebars.js @@ -120,6 +120,7 @@ module.exports = { "segments/text", "segments/time", "segments/ui5tooling", + "segments/umbraco", "segments/unity", "segments/upgrade", "segments/vala",