mirror of
https://github.com/JanDeDobbeleer/oh-my-posh.git
synced 2025-02-21 02:55:37 -08:00
feat(project): enhance project segment with .NET
This commit is contained in:
parent
69822e5c63
commit
7ae14646d7
|
@ -3,22 +3,33 @@ package segments
|
||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"encoding/xml"
|
"encoding/xml"
|
||||||
|
"errors"
|
||||||
"oh-my-posh/environment"
|
"oh-my-posh/environment"
|
||||||
"oh-my-posh/properties"
|
"oh-my-posh/properties"
|
||||||
|
"oh-my-posh/regex"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
|
"strings"
|
||||||
|
|
||||||
"github.com/BurntSushi/toml"
|
"github.com/BurntSushi/toml"
|
||||||
)
|
)
|
||||||
|
|
||||||
type ProjectItem struct {
|
type ProjectItem struct {
|
||||||
Name string
|
Name string
|
||||||
File string
|
Files []string
|
||||||
Fetcher func(item ProjectItem) (string, string)
|
Fetcher func(item ProjectItem) *ProjectData
|
||||||
}
|
}
|
||||||
|
|
||||||
type ProjectData struct {
|
type ProjectData struct {
|
||||||
Version string
|
Version string
|
||||||
Name string
|
Name string
|
||||||
|
Target string
|
||||||
|
}
|
||||||
|
|
||||||
|
func (p *ProjectData) enabled() bool {
|
||||||
|
if p == nil {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
return len(p.Version) > 0 || len(p.Name) > 0 || len(p.Target) > 0
|
||||||
}
|
}
|
||||||
|
|
||||||
// Rust Cargo package
|
// Rust Cargo package
|
||||||
|
@ -56,15 +67,19 @@ type Project struct {
|
||||||
func (n *Project) Enabled() bool {
|
func (n *Project) Enabled() bool {
|
||||||
for _, item := range n.projects {
|
for _, item := range n.projects {
|
||||||
if n.hasProjectFile(item) {
|
if n.hasProjectFile(item) {
|
||||||
n.Version, n.Name = item.Fetcher(*item)
|
data := item.Fetcher(*item)
|
||||||
return len(n.Version) > 0 || len(n.Name) > 0
|
if data == nil {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
n.ProjectData = *data
|
||||||
|
return n.enabled()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Project) Template() string {
|
func (n *Project) Template() string {
|
||||||
return " {{ if .Error }}{{ .Error }}{{ else }}{{ if .Version }}\uf487 {{.Version}}{{ end }} {{ if .Name }}{{ .Name }}{{ end }}{{ end }} "
|
return " {{ if .Error }}{{ .Error }}{{ else }}{{ if .Version }}\uf487 {{.Version}} {{ end }}{{ if .Name }}{{ .Name }} {{ end }}{{ if .Target }}\uf9fd {{.Target}} {{ end }}{{ end }}" //nolint:lll
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Project) Init(props properties.Properties, env environment.Environment) {
|
func (n *Project) Init(props properties.Properties, env environment.Environment) {
|
||||||
|
@ -74,76 +89,92 @@ func (n *Project) Init(props properties.Properties, env environment.Environment)
|
||||||
n.projects = []*ProjectItem{
|
n.projects = []*ProjectItem{
|
||||||
{
|
{
|
||||||
Name: "node",
|
Name: "node",
|
||||||
File: "package.json",
|
Files: []string{"package.json"},
|
||||||
Fetcher: n.getNodePackage,
|
Fetcher: n.getNodePackage,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "cargo",
|
Name: "cargo",
|
||||||
File: "Cargo.toml",
|
Files: []string{"Cargo.toml"},
|
||||||
Fetcher: n.getCargoPackage,
|
Fetcher: n.getCargoPackage,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "poetry",
|
Name: "poetry",
|
||||||
File: "pyproject.toml",
|
Files: []string{"pyproject.toml"},
|
||||||
Fetcher: n.getPoetryPackage,
|
Fetcher: n.getPoetryPackage,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "php",
|
Name: "php",
|
||||||
File: "composer.json",
|
Files: []string{"composer.json"},
|
||||||
Fetcher: n.getNodePackage,
|
Fetcher: n.getNodePackage,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
Name: "nuspec",
|
Name: "nuspec",
|
||||||
File: "*.nuspec",
|
Files: []string{"*.nuspec"},
|
||||||
Fetcher: n.getNuSpecPackage,
|
Fetcher: n.getNuSpecPackage,
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
Name: "dotnet",
|
||||||
|
Files: []string{"*.vbproj", "*.fsproj", "*.csproj"},
|
||||||
|
Fetcher: n.getDotnetProject,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Project) hasProjectFile(p *ProjectItem) bool {
|
func (n *Project) hasProjectFile(p *ProjectItem) bool {
|
||||||
return n.env.HasFiles(p.File)
|
for _, file := range p.Files {
|
||||||
|
if n.env.HasFiles(file) {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Project) getNodePackage(item ProjectItem) (string, string) {
|
func (n *Project) getNodePackage(item ProjectItem) *ProjectData {
|
||||||
content := n.env.FileContent(item.File)
|
content := n.env.FileContent(item.Files[0])
|
||||||
|
|
||||||
var data ProjectData
|
var data ProjectData
|
||||||
err := json.Unmarshal([]byte(content), &data)
|
err := json.Unmarshal([]byte(content), &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.Error = err.Error()
|
n.Error = err.Error()
|
||||||
return "", ""
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.Version, data.Name
|
return &data
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Project) getCargoPackage(item ProjectItem) (string, string) {
|
func (n *Project) getCargoPackage(item ProjectItem) *ProjectData {
|
||||||
content := n.env.FileContent(item.File)
|
content := n.env.FileContent(item.Files[0])
|
||||||
|
|
||||||
var data CargoTOML
|
var data CargoTOML
|
||||||
_, err := toml.Decode(content, &data)
|
_, err := toml.Decode(content, &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.Error = err.Error()
|
n.Error = err.Error()
|
||||||
return "", ""
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.Package.Version, data.Package.Name
|
return &ProjectData{
|
||||||
|
Version: data.Package.Version,
|
||||||
|
Name: data.Package.Name,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Project) getPoetryPackage(item ProjectItem) (string, string) {
|
func (n *Project) getPoetryPackage(item ProjectItem) *ProjectData {
|
||||||
content := n.env.FileContent(item.File)
|
content := n.env.FileContent(item.Files[0])
|
||||||
|
|
||||||
var data PyProjectTOML
|
var data PyProjectTOML
|
||||||
_, err := toml.Decode(content, &data)
|
_, err := toml.Decode(content, &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.Error = err.Error()
|
n.Error = err.Error()
|
||||||
return "", ""
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.Tool.Poetry.Version, data.Tool.Poetry.Name
|
return &ProjectData{
|
||||||
|
Version: data.Tool.Poetry.Version,
|
||||||
|
Name: data.Tool.Poetry.Name,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (n *Project) getNuSpecPackage(item ProjectItem) (string, string) {
|
func (n *Project) getNuSpecPackage(item ProjectItem) *ProjectData {
|
||||||
files := n.env.LsDir(n.env.Pwd())
|
files := n.env.LsDir(n.env.Pwd())
|
||||||
var content string
|
var content string
|
||||||
// get the first match only
|
// get the first match only
|
||||||
|
@ -158,8 +189,40 @@ func (n *Project) getNuSpecPackage(item ProjectItem) (string, string) {
|
||||||
err := xml.Unmarshal([]byte(content), &data)
|
err := xml.Unmarshal([]byte(content), &data)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
n.Error = err.Error()
|
n.Error = err.Error()
|
||||||
return "", ""
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
return data.MetaData.Version, data.MetaData.Title
|
return &ProjectData{
|
||||||
|
Version: data.MetaData.Version,
|
||||||
|
Name: data.MetaData.Title,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func (n *Project) getDotnetProject(item ProjectItem) *ProjectData {
|
||||||
|
files := n.env.LsDir(n.env.Pwd())
|
||||||
|
var name string
|
||||||
|
var content string
|
||||||
|
// get the first match only
|
||||||
|
for _, file := range files {
|
||||||
|
extension := filepath.Ext(file.Name())
|
||||||
|
if extension == ".csproj" || extension == ".fsproj" || extension == ".vbproj" {
|
||||||
|
name = strings.TrimSuffix(file.Name(), filepath.Ext(file.Name()))
|
||||||
|
content = n.env.FileContent(file.Name())
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// the name of the parameter may differ depending on the version,
|
||||||
|
// so instead of xml.Unmarshal() we use regex:
|
||||||
|
tag := "(?P<TAG><.*TargetFramework.*>(?P<TFM>.*)</.*TargetFramework.*>)"
|
||||||
|
values := regex.FindNamedRegexMatch(tag, content)
|
||||||
|
if len(values) == 0 {
|
||||||
|
n.Error = errors.New("cannot extract TFM from " + name + " project file").Error()
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
target := values["TFM"]
|
||||||
|
|
||||||
|
return &ProjectData{
|
||||||
|
Target: target,
|
||||||
|
Name: name,
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,6 +5,7 @@ import (
|
||||||
"oh-my-posh/mock"
|
"oh-my-posh/mock"
|
||||||
"oh-my-posh/properties"
|
"oh-my-posh/properties"
|
||||||
"os"
|
"os"
|
||||||
|
"path/filepath"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
"github.com/alecthomas/assert"
|
"github.com/alecthomas/assert"
|
||||||
|
@ -12,6 +13,10 @@ import (
|
||||||
testify_mock "github.com/stretchr/testify/mock"
|
testify_mock "github.com/stretchr/testify/mock"
|
||||||
)
|
)
|
||||||
|
|
||||||
|
const (
|
||||||
|
hasFiles = "HasFiles"
|
||||||
|
)
|
||||||
|
|
||||||
type MockDirEntry struct {
|
type MockDirEntry struct {
|
||||||
name string
|
name string
|
||||||
isDir bool
|
isDir bool
|
||||||
|
@ -185,9 +190,9 @@ func TestPackage(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
env := new(mock.MockedEnvironment)
|
env := new(mock.MockedEnvironment)
|
||||||
env.On("HasFiles", testify_mock.Anything).Run(func(args testify_mock.Arguments) {
|
env.On(hasFiles, testify_mock.Anything).Run(func(args testify_mock.Arguments) {
|
||||||
for _, c := range env.ExpectedCalls {
|
for _, c := range env.ExpectedCalls {
|
||||||
if c.Method == "HasFiles" {
|
if c.Method == hasFiles {
|
||||||
c.ReturnArguments = testify_mock.Arguments{args.Get(0).(string) == tc.File}
|
c.ReturnArguments = testify_mock.Arguments{args.Get(0).(string) == tc.File}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -238,9 +243,9 @@ func TestNuspecPackage(t *testing.T) {
|
||||||
|
|
||||||
for _, tc := range cases {
|
for _, tc := range cases {
|
||||||
env := new(mock.MockedEnvironment)
|
env := new(mock.MockedEnvironment)
|
||||||
env.On("HasFiles", testify_mock.Anything).Run(func(args testify_mock.Arguments) {
|
env.On(hasFiles, testify_mock.Anything).Run(func(args testify_mock.Arguments) {
|
||||||
for _, c := range env.ExpectedCalls {
|
for _, c := range env.ExpectedCalls {
|
||||||
if c.Method != "HasFiles" {
|
if c.Method != hasFiles {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
if args.Get(0).(string) == "*.nuspec" {
|
if args.Get(0).(string) == "*.nuspec" {
|
||||||
|
@ -266,3 +271,76 @@ func TestNuspecPackage(t *testing.T) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestDotnetProject(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
Case string
|
||||||
|
FileName string
|
||||||
|
HasFiles bool
|
||||||
|
ProjectContents string
|
||||||
|
ExpectedString string
|
||||||
|
ExpectedEnabled bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
Case: "valid .csproj file",
|
||||||
|
FileName: "Valid.csproj",
|
||||||
|
HasFiles: true,
|
||||||
|
ProjectContents: "...<TargetFramework>net7.0</TargetFramework>...",
|
||||||
|
ExpectedEnabled: true,
|
||||||
|
ExpectedString: "Valid \uf9fd net7.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Case: "valid .fsproj file",
|
||||||
|
FileName: "Valid.fsproj",
|
||||||
|
HasFiles: true,
|
||||||
|
ProjectContents: "...<TargetFramework>net6.0</TargetFramework>...",
|
||||||
|
ExpectedEnabled: true,
|
||||||
|
ExpectedString: "Valid \uf9fd net6.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Case: "valid .vbproj file",
|
||||||
|
FileName: "Valid.vbproj",
|
||||||
|
HasFiles: true,
|
||||||
|
ProjectContents: "...<TargetFramework>net5.0</TargetFramework>...",
|
||||||
|
ExpectedEnabled: true,
|
||||||
|
ExpectedString: "Valid \uf9fd net5.0",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
Case: "invalid or empty contents",
|
||||||
|
FileName: "Invalid.csproj",
|
||||||
|
HasFiles: true,
|
||||||
|
ExpectedEnabled: false,
|
||||||
|
ExpectedString: "cannot extract TFM from Invalid project file",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
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 {
|
||||||
|
pattern := "*" + filepath.Ext(tc.FileName)
|
||||||
|
c.ReturnArguments = testify_mock.Arguments{args.Get(0).(string) == pattern}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
env.On("Pwd").Return("posh")
|
||||||
|
env.On("LsDir", "posh").Return([]fs.DirEntry{
|
||||||
|
&MockDirEntry{
|
||||||
|
name: tc.FileName,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
env.On("FileContent", tc.FileName).Return(tc.ProjectContents)
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
|
@ -15,6 +15,7 @@ Supports:
|
||||||
- Poetry project (`pyproject.toml`)
|
- Poetry project (`pyproject.toml`)
|
||||||
- PHP project (`composer.json`)
|
- PHP project (`composer.json`)
|
||||||
- Any nuspec based project (`*.nuspec`, first file match info is displayed)
|
- Any nuspec based project (`*.nuspec`, first file match info is displayed)
|
||||||
|
- .NET project (`*.csproj`, `*.vbproj` or `*.fsproj`, first file match info is displayed)
|
||||||
|
|
||||||
## Sample Configuration
|
## Sample Configuration
|
||||||
|
|
||||||
|
@ -42,8 +43,9 @@ Supports:
|
||||||
### Properties
|
### Properties
|
||||||
|
|
||||||
| Name | Type | Description |
|
| Name | Type | Description |
|
||||||
| ---------- | -------- | --------------------------- |
|
| ---------- | -------- | ---------------------------------------------------- |
|
||||||
| `.Version` | `string` | The version of your project |
|
| `.Version` | `string` | The version of your project |
|
||||||
|
| `.Target` | `string` | The target framwork/language version of your project |
|
||||||
| `.Name` | `string` | The name of your project |
|
| `.Name` | `string` | The name of your project |
|
||||||
|
|
||||||
[templates]: /docs/configuration/templates
|
[templates]: /docs/configuration/templates
|
||||||
|
|
Loading…
Reference in a new issue