From fb698dd20a0d2f23d34421483d366d4463e850b9 Mon Sep 17 00:00:00 2001 From: Jan De Dobbeleer Date: Sat, 16 Apr 2022 19:56:03 +0200 Subject: [PATCH] feat(project): display nuspec information resolves #2098 --- docs/docs/segment-project.md | 1 + src/segments/project.go | 54 +++++-- src/segments/project_test.go | 287 ++++++++++++++++++++++++++++++----- src/test/empty.nuspec | 31 ++++ src/test/invalid.nuspec | 1 + src/test/valid.nuspec | 33 ++++ 6 files changed, 351 insertions(+), 56 deletions(-) create mode 100644 src/test/empty.nuspec create mode 100644 src/test/invalid.nuspec create mode 100644 src/test/valid.nuspec diff --git a/docs/docs/segment-project.md b/docs/docs/segment-project.md index 456ebc5f..78095ff9 100644 --- a/docs/docs/segment-project.md +++ b/docs/docs/segment-project.md @@ -14,6 +14,7 @@ Supports: - Cargo project (`Cargo.toml`) - Poetry project (`pyproject.toml`) - PHP project (`composer.json`) +- Any nuspec based project (`*.nuspec`, first file match info is displayed) ## Sample Configuration diff --git a/src/segments/project.go b/src/segments/project.go index 23b0b162..2e177aae 100644 --- a/src/segments/project.go +++ b/src/segments/project.go @@ -2,8 +2,10 @@ package segments import ( "encoding/json" + "encoding/xml" "oh-my-posh/environment" "oh-my-posh/properties" + "path/filepath" "github.com/BurntSushi/toml" ) @@ -33,6 +35,14 @@ type PyProjectToolTOML struct { Poetry ProjectData } +type NuSpec struct { + XMLName xml.Name `xml:"package"` + MetaData struct { + Title string `xml:"title"` + Version string `xml:"version"` + } `xml:"metadata"` +} + type Project struct { props properties.Properties env environment.Environment @@ -44,14 +54,13 @@ type Project struct { } func (n *Project) Enabled() bool { - var enabled = false for _, item := range n.projects { - if !enabled { - enabled = n.hasProjectFile(item) + if n.hasProjectFile(item) { + n.Version, n.Name = item.Fetcher(*item) + return len(n.Version) > 0 || len(n.Name) > 0 } } - - return enabled + return false } func (n *Project) Template() string { @@ -83,15 +92,11 @@ func (n *Project) Init(props properties.Properties, env environment.Environment) File: "composer.json", Fetcher: n.getNodePackage, }, - } - - n.Version = "" - n.Name = "" - for _, item := range n.projects { - if n.hasProjectFile(item) { - n.Version, n.Name = item.Fetcher(*item) - break - } + { + Name: "nuspec", + File: "*.nuspec", + Fetcher: n.getNuSpecPackage, + }, } } @@ -137,3 +142,24 @@ func (n *Project) getPoetryPackage(item ProjectItem) (string, string) { return data.Tool.Poetry.Version, data.Tool.Poetry.Name } + +func (n *Project) getNuSpecPackage(item ProjectItem) (string, string) { + files := n.env.LsDir(n.env.Pwd()) + var content string + // get the first match only + for _, file := range files { + if filepath.Ext(file.Name()) == ".nuspec" { + content = n.env.FileContent(file.Name()) + break + } + } + + var data NuSpec + err := xml.Unmarshal([]byte(content), &data) + if err != nil { + n.Error = err.Error() + return "", "" + } + + return data.MetaData.Version, data.MetaData.Title +} diff --git a/src/segments/project_test.go b/src/segments/project_test.go index e83634b9..f6c0a69d 100644 --- a/src/segments/project_test.go +++ b/src/segments/project_test.go @@ -1,7 +1,8 @@ package segments import ( - "fmt" + "io/fs" + "io/ioutil" "oh-my-posh/mock" "oh-my-posh/properties" "testing" @@ -11,55 +12,257 @@ import ( testify_mock "github.com/stretchr/testify/mock" ) -type MockData struct { - Name string - Case string - ExpectedString string - PackageContents string - File string +type MockDirEntry struct { + name string + isDir bool + fileMode fs.FileMode + fileInfo fs.FileInfo + err error } -func getMockedPackageEnv(tc *MockData) (*mock.MockedEnvironment, properties.Map) { - env := new(mock.MockedEnvironment) - props := properties.Map{} - env.On("HasFiles", testify_mock.Anything).Run(func(args testify_mock.Arguments) { - for _, c := range env.ExpectedCalls { - if c.Method == "HasFiles" { - c.ReturnArguments = testify_mock.Arguments{args.Get(0).(string) == tc.File} - } - } - }) - env.On("FileContent", tc.File).Return(tc.PackageContents) - return env, props +func (m *MockDirEntry) Name() string { + return m.name +} + +func (m *MockDirEntry) IsDir() bool { + return m.isDir +} + +func (m *MockDirEntry) Type() fs.FileMode { + return m.fileMode +} + +func (m *MockDirEntry) Info() (fs.FileInfo, error) { + return m.fileInfo, m.err } func TestPackage(t *testing.T) { - cases := []*MockData{ - {Case: "1.0.0 node.js", ExpectedString: "\uf487 1.0.0 test", Name: "node", File: "package.json", PackageContents: "{\"version\":\"1.0.0\",\"name\":\"test\"}"}, - {Case: "1.0.0 php", ExpectedString: "\uf487 1.0.0 test", Name: "php", File: "composer.json", PackageContents: "{\"version\":\"1.0.0\",\"name\":\"test\"}"}, - {Case: "3.2.1 node.js", ExpectedString: "\uf487 3.2.1 test", Name: "node", File: "package.json", PackageContents: "{\"version\":\"3.2.1\",\"name\":\"test\"}"}, - {Case: "1.0.0 cargo", ExpectedString: "\uf487 1.0.0 test", Name: "cargo", File: "Cargo.toml", PackageContents: "[package]\nname=\"test\"\nversion=\"1.0.0\"\n"}, - {Case: "3.2.1 cargo", ExpectedString: "\uf487 3.2.1 test", Name: "cargo", File: "Cargo.toml", PackageContents: "[package]\nname=\"test\"\nversion=\"3.2.1\"\n"}, - {Case: "1.0.0 poetry", ExpectedString: "\uf487 1.0.0 test", Name: "poetry", File: "pyproject.toml", PackageContents: "[tool.poetry]\nname=\"test\"\nversion=\"1.0.0\"\n"}, - {Case: "3.2.1 poetry", ExpectedString: "\uf487 3.2.1 test", Name: "poetry", File: "pyproject.toml", PackageContents: "[tool.poetry]\nname=\"test\"\nversion=\"3.2.1\"\n"}, - {Case: "No version present node.js", ExpectedString: "test", Name: "node", File: "package.json", PackageContents: "{\"name\":\"test\"}"}, - {Case: "No version present cargo", ExpectedString: "test", Name: "cargo", File: "Cargo.toml", PackageContents: "[package]\nname=\"test\"\n"}, - {Case: "No version present poetry", ExpectedString: "test", Name: "poetry", File: "pyproject.toml", PackageContents: "[tool.poetry]\nname=\"test\"\n"}, - {Case: "No name present node.js", ExpectedString: "\uf487 1.0.0", Name: "node", File: "package.json", PackageContents: "{\"version\":\"1.0.0\"}"}, - {Case: "No name present cargo", ExpectedString: "\uf487 1.0.0", Name: "cargo", File: "Cargo.toml", PackageContents: "[package]\nversion=\"1.0.0\"\n"}, - {Case: "No name present poetry", ExpectedString: "\uf487 1.0.0", Name: "poetry", File: "pyproject.toml", PackageContents: "[tool.poetry]\nversion=\"1.0.0\"\n"}, - {Case: "Empty project package node.js", ExpectedString: "", Name: "node", File: "package.json", PackageContents: "{}"}, - {Case: "Empty project package cargo", ExpectedString: "", Name: "cargo", File: "Cargo.toml", PackageContents: ""}, - {Case: "Empty project package poetry", ExpectedString: "", Name: "poetry", File: "pyproject.toml", PackageContents: ""}, - {Case: "Invalid json", ExpectedString: "invalid character '}' looking for beginning of value", Name: "node", File: "package.json", PackageContents: "}"}, - {Case: "Invalid toml", ExpectedString: "toml: line 1: unexpected end of table name (table names cannot be empty)", Name: "cargo", File: "Cargo.toml", PackageContents: "["}, + cases := []struct { + Name string + Case string + File string + PackageContents string + ExpectedString string + ExpectedEnabled bool + }{ + { + Case: "1.0.0 node.js", + ExpectedEnabled: true, + ExpectedString: "\uf487 1.0.0 test", + Name: "node", + File: "package.json", + PackageContents: "{\"version\":\"1.0.0\",\"name\":\"test\"}", + }, + { + Case: "1.0.0 php", + ExpectedEnabled: true, + ExpectedString: "\uf487 1.0.0 test", + Name: "php", + File: "composer.json", + PackageContents: "{\"version\":\"1.0.0\",\"name\":\"test\"}", + }, + { + Case: "3.2.1 node.js", + ExpectedEnabled: true, + ExpectedString: "\uf487 3.2.1 test", + Name: "node", File: "package.json", + PackageContents: "{\"version\":\"3.2.1\",\"name\":\"test\"}", + }, + { + Case: "1.0.0 cargo", + ExpectedEnabled: true, + ExpectedString: "\uf487 1.0.0 test", + Name: "cargo", + File: "Cargo.toml", + PackageContents: "[package]\nname=\"test\"\nversion=\"1.0.0\"\n", + }, + { + Case: "3.2.1 cargo", + ExpectedEnabled: true, + ExpectedString: "\uf487 3.2.1 test", + Name: "cargo", + File: "Cargo.toml", + PackageContents: "[package]\nname=\"test\"\nversion=\"3.2.1\"\n", + }, + { + Case: "1.0.0 poetry", + ExpectedEnabled: true, + ExpectedString: "\uf487 1.0.0 test", + Name: "poetry", + File: "pyproject.toml", + PackageContents: "[tool.poetry]\nname=\"test\"\nversion=\"1.0.0\"\n", + }, + { + Case: "3.2.1 poetry", + ExpectedEnabled: true, + ExpectedString: "\uf487 3.2.1 test", + Name: "poetry", + File: "pyproject.toml", + PackageContents: "[tool.poetry]\nname=\"test\"\nversion=\"3.2.1\"\n", + }, + { + Case: "No version present node.js", + ExpectedEnabled: true, + ExpectedString: "test", + Name: "node", + File: "package.json", + PackageContents: "{\"name\":\"test\"}", + }, + { + Case: "No version present cargo", + ExpectedEnabled: true, + ExpectedString: "test", + Name: "cargo", + File: "Cargo.toml", + PackageContents: "[package]\nname=\"test\"\n", + }, + { + Case: "No version present poetry", + ExpectedEnabled: true, + ExpectedString: "test", + Name: "poetry", + File: "pyproject.toml", + PackageContents: "[tool.poetry]\nname=\"test\"\n", + }, + { + Case: "No name present node.js", + ExpectedEnabled: true, + ExpectedString: "\uf487 1.0.0", + Name: "node", + File: "package.json", + PackageContents: "{\"version\":\"1.0.0\"}", + }, + { + Case: "No name present cargo", + ExpectedEnabled: true, + ExpectedString: "\uf487 1.0.0", + Name: "cargo", + File: "Cargo.toml", + PackageContents: "[package]\nversion=\"1.0.0\"\n", + }, + { + Case: "No name present poetry", + ExpectedEnabled: true, + ExpectedString: "\uf487 1.0.0", + Name: "poetry", + File: "pyproject.toml", + PackageContents: "[tool.poetry]\nversion=\"1.0.0\"\n", + }, + { + Case: "Empty project package node.js", + ExpectedEnabled: false, + Name: "node", + File: "package.json", + PackageContents: "{}", + }, + { + Case: "Empty project package cargo", + Name: "cargo", + File: "Cargo.toml", + PackageContents: "", + }, + { + Case: "Empty project package poetry", + Name: "poetry", + File: "pyproject.toml", + PackageContents: "", + }, + { + Case: "Invalid json", + ExpectedString: "invalid character '}' looking for beginning of value", + Name: "node", + File: "package.json", + PackageContents: "}", + }, + { + Case: "Invalid toml", + ExpectedString: "toml: line 1: unexpected end of table name (table names cannot be empty)", + Name: "cargo", + File: "Cargo.toml", + PackageContents: "[", + }, } for _, tc := range cases { - env, props := getMockedPackageEnv(tc) + env := new(mock.MockedEnvironment) + env.On("HasFiles", testify_mock.Anything).Run(func(args testify_mock.Arguments) { + for _, c := range env.ExpectedCalls { + if c.Method == "HasFiles" { + c.ReturnArguments = testify_mock.Arguments{args.Get(0).(string) == tc.File} + } + } + }) + env.On("FileContent", tc.File).Return(tc.PackageContents) pkg := &Project{} - pkg.Init(props, env) - assert.True(t, pkg.Enabled(), fmt.Sprintf("Failed in case: %s", tc.Case)) - assert.Equal(t, tc.ExpectedString, renderTemplate(env, pkg.Template(), pkg), fmt.Sprintf("Failed in case: %s", tc.Case)) + pkg.Init(properties.Map{}, env) + assert.Equal(t, tc.ExpectedEnabled, pkg.Enabled(), tc.Case) + if tc.ExpectedEnabled { + assert.Equal(t, tc.ExpectedString, renderTemplate(env, pkg.Template(), pkg), tc.Case) + } + } +} + +func TestNuspecPackage(t *testing.T) { + cases := []struct { + Case string + HasFiles bool + FileName string + ExpectedString string + ExpectedEnabled bool + }{ + { + Case: "valid file", + FileName: "../test/valid.nuspec", + HasFiles: true, + ExpectedEnabled: true, + ExpectedString: "\uf487 0.1.0 Az.Compute", + }, + { + Case: "invalid file", + FileName: "../test/invalid.nuspec", + HasFiles: true, + ExpectedEnabled: false, + }, + { + Case: "no info in file", + FileName: "../test/empty.nuspec", + HasFiles: true, + ExpectedEnabled: false, + }, + { + Case: "no files", + HasFiles: false, + ExpectedEnabled: false, + }, + } + + for _, tc := range cases { + env := new(mock.MockedEnvironment) + env.On("HasFiles", testify_mock.Anything).Run(func(args testify_mock.Arguments) { + for _, c := range env.ExpectedCalls { + if c.Method != "HasFiles" { + continue + } + if args.Get(0).(string) == "*.nuspec" { + c.ReturnArguments = testify_mock.Arguments{tc.HasFiles} + continue + } + c.ReturnArguments = testify_mock.Arguments{false} + } + }) + env.On("Pwd").Return("posh") + env.On("LsDir", "posh").Return([]fs.DirEntry{ + &MockDirEntry{ + name: tc.FileName, + }, + }) + content, _ := ioutil.ReadFile(tc.FileName) + env.On("FileContent", tc.FileName).Return(string(content)) + pkg := &Project{} + pkg.Init(properties.Map{}, env) + assert.Equal(t, tc.ExpectedEnabled, pkg.Enabled(), tc.Case) + if tc.ExpectedEnabled { + assert.Equal(t, tc.ExpectedString, renderTemplate(env, pkg.Template(), pkg), tc.Case) + } } } diff --git a/src/test/empty.nuspec b/src/test/empty.nuspec new file mode 100644 index 00000000..aab6bbee --- /dev/null +++ b/src/test/empty.nuspec @@ -0,0 +1,31 @@ + + + + Az.Compute + Microsoft Corporation + Microsoft Corporation + true + https://aka.ms/azps-license + https://github.com/Azure/azure-powershell + Microsoft Azure PowerShell: $(service-name) cmdlets + + Microsoft Corporation. All rights reserved. + Azure ResourceManager ARM PSModule $(service-name) + + + + + + + + + + + + + + + + + + diff --git a/src/test/invalid.nuspec b/src/test/invalid.nuspec new file mode 100644 index 00000000..0967ef42 --- /dev/null +++ b/src/test/invalid.nuspec @@ -0,0 +1 @@ +{} diff --git a/src/test/valid.nuspec b/src/test/valid.nuspec new file mode 100644 index 00000000..0bfe7a68 --- /dev/null +++ b/src/test/valid.nuspec @@ -0,0 +1,33 @@ + + + + Az.Compute + Az.Compute + 0.1.0 + Microsoft Corporation + Microsoft Corporation + true + https://aka.ms/azps-license + https://github.com/Azure/azure-powershell + Microsoft Azure PowerShell: $(service-name) cmdlets + + Microsoft Corporation. All rights reserved. + Azure ResourceManager ARM PSModule $(service-name) + + + + + + + + + + + + + + + + + +