feat(project): display nuspec information

resolves #2098
This commit is contained in:
Jan De Dobbeleer 2022-04-16 19:56:03 +02:00 committed by Jan De Dobbeleer
parent 0cd8bfe6b6
commit fb698dd20a
6 changed files with 351 additions and 56 deletions

View file

@ -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

View file

@ -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
}

View file

@ -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)
}
}
}

31
src/test/empty.nuspec Normal file
View file

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>Az.Compute</id>
<authors>Microsoft Corporation</authors>
<owners>Microsoft Corporation</owners>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<licenseUrl>https://aka.ms/azps-license</licenseUrl>
<projectUrl>https://github.com/Azure/azure-powershell</projectUrl>
<description>Microsoft Azure PowerShell: $(service-name) cmdlets</description>
<releaseNotes></releaseNotes>
<copyright>Microsoft Corporation. All rights reserved.</copyright>
<tags>Azure ResourceManager ARM PSModule $(service-name)</tags>
<dependencies>
<dependency id="Az.Accounts" version="2.2.3" />
</dependencies>
</metadata>
<files>
<file src="Az.Compute.format.ps1xml" />
<file src="Az.Compute.psd1" />
<file src="Az.Compute.psm1" />
<!-- https://github.com/NuGet/Home/issues/3584 -->
<file src="bin/Az.Compute.private.dll" target="bin" />
<file src="bin\Az.Compute.private.deps.json" target="bin" />
<file src="internal\**\*.*" exclude="internal\README.md" target="internal" />
<file src="custom\**\*.*" exclude="custom\README.md;custom\**\*.cs" target="custom" />
<file src="docs\**\*.md" exclude="docs\README.md" target="docs" />
<file src="exports\**\ProxyCmdletDefinitions.ps1" target="exports" />
<file src="utils\**\*.*" target="utils" />
</files>
</package>

1
src/test/invalid.nuspec Normal file
View file

@ -0,0 +1 @@
{}

33
src/test/valid.nuspec Normal file
View file

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<package xmlns="http://schemas.microsoft.com/packaging/2013/05/nuspec.xsd">
<metadata>
<id>Az.Compute</id>
<title>Az.Compute</title>
<version>0.1.0</version>
<authors>Microsoft Corporation</authors>
<owners>Microsoft Corporation</owners>
<requireLicenseAcceptance>true</requireLicenseAcceptance>
<licenseUrl>https://aka.ms/azps-license</licenseUrl>
<projectUrl>https://github.com/Azure/azure-powershell</projectUrl>
<description>Microsoft Azure PowerShell: $(service-name) cmdlets</description>
<releaseNotes></releaseNotes>
<copyright>Microsoft Corporation. All rights reserved.</copyright>
<tags>Azure ResourceManager ARM PSModule $(service-name)</tags>
<dependencies>
<dependency id="Az.Accounts" version="2.2.3" />
</dependencies>
</metadata>
<files>
<file src="Az.Compute.format.ps1xml" />
<file src="Az.Compute.psd1" />
<file src="Az.Compute.psm1" />
<!-- https://github.com/NuGet/Home/issues/3584 -->
<file src="bin/Az.Compute.private.dll" target="bin" />
<file src="bin\Az.Compute.private.deps.json" target="bin" />
<file src="internal\**\*.*" exclude="internal\README.md" target="internal" />
<file src="custom\**\*.*" exclude="custom\README.md;custom\**\*.cs" target="custom" />
<file src="docs\**\*.md" exclude="docs\README.md" target="docs" />
<file src="exports\**\ProxyCmdletDefinitions.ps1" target="exports" />
<file src="utils\**\*.*" target="utils" />
</files>
</package>