Repository: JanDeDobbeleer/oh-my-posh
Pull request #2015 (2022-03-31 13:09:59)

feat: cargo and poetry project packages support
### 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
see #2014 

cc @JanDeDobbeleer 


JanDeDobbeleer: @hadrizi I will address the commitment issue ;-)

Patch:
diff --git a/docs/docs/segment-project.md b/docs/docs/segment-project.md
index e8a12077..31f8f3d1 100644
--- a/docs/docs/segment-project.md
+++ b/docs/docs/segment-project.md
@@ -11,6 +11,8 @@ Display the current version of your project defined in the package file.
 Supports:
 
 - Node.js project (`package.json`)
+- Cargo project (`Cargo.toml`)
+- Poetry project (`pyproject.toml`)
 
 ## Sample Configuration
 
@@ -21,7 +23,7 @@ Supports:
   "powerline_symbol": "\uE0B0",
   "foreground": "#193549",
   "background": "#ffeb3b",
-  "template": " {{ if .Error }}{{ .Error }}{{ else }}{{ if .Version }}\uf487 {{.Version}}{{ end }}{{ end }} "
+  "template": " {{ if .Error }}{{ .Error }}{{ else }}{{ if .Version }}\uf487 {{.Version}}{{ end }} {{ if .Name }}{{ .Name }}{{ end }}{{ end }} "
 }
 ```
 
@@ -30,7 +32,7 @@ Supports:
 :::note default template
 
 ``` template
-{{ if .Error }}{{ .Error }}{{ else }}{{ if .Version }}\uf487 {{.Version}}{{ end }}{{ end }}
+ {{ if .Error }}{{ .Error }}{{ else }}{{ if .Version }}\uf487 {{.Version}}{{ end }} {{ if .Name }}{{ .Name }}{{ end }}{{ end }} 
 ```
 
 :::
@@ -38,5 +40,6 @@ Supports:
 ### Properties
 
 - `.Version`: `string` - The version of your project
+- `.Name`: `string` - The name of your project
 
 [templates]: /docs/config-templates
diff --git a/src/go.mod b/src/go.mod
index 9f59a9b6..62cd8828 100644
--- a/src/go.mod
+++ b/src/go.mod
@@ -40,7 +40,7 @@ require (
 )
 
 require (
-	github.com/BurntSushi/toml v1.0.0 // indirect
+	github.com/BurntSushi/toml v1.0.0
 	github.com/Masterminds/goutils v1.1.1 // indirect
 	github.com/Masterminds/semver/v3 v3.1.1 // indirect
 	github.com/davecgh/go-spew v1.1.1 // indirect
diff --git a/src/segments/project.go b/src/segments/project.go
index 58a0f25a..b269c354 100644
--- a/src/segments/project.go
+++ b/src/segments/project.go
@@ -4,18 +4,33 @@ import (
 	"encoding/json"
 	"oh-my-posh/environment"
 	"oh-my-posh/properties"
+
+	"github.com/BurntSushi/toml"
 )
 
 type ProjectItem struct {
 	Name    string
 	File    string
-	Fetcher func(item ProjectItem) string
+	Fetcher func(item ProjectItem) (string, string)
+}
+
+type ProjectData struct {
+	Version string
+	Name    string
+}
+
+// Rust Cargo package
+type CargoTOML struct {
+	Package ProjectData
+}
+
+// Python Poetry package
+type PyProjectTOML struct {
+	Tool PyProjectToolTOML
 }
 
-type NodePackageJSON struct {
-	Name        string `json:"name"`
-	Description string `json:"description"`
-	Version     string `json:"version"`
+type PyProjectToolTOML struct {
+	Poetry ProjectData
 }
 
 type Project struct {
@@ -23,8 +38,9 @@ type Project struct {
 	env   environment.Environment
 
 	projects []*ProjectItem
-	Version  string
 	Error    string
+
+	ProjectData
 }
 
 func (n *Project) Enabled() bool {
@@ -39,7 +55,7 @@ func (n *Project) Enabled() bool {
 }
 
 func (n *Project) Template() string {
-	return " {{ if .Error }}{{ .Error }}{{ else }}{{ if .Version }}\uf487 {{.Version}}{{ end }}{{ end }} "
+	return " {{ if .Error }}{{ .Error }}{{ else }}{{ if .Version }}\uf487 {{.Version}}{{ end }} {{ if .Name }}{{ .Name }}{{ end }}{{ end }} "
 }
 
 func (n *Project) Init(props properties.Properties, env environment.Environment) {
@@ -50,14 +66,25 @@ func (n *Project) Init(props properties.Properties, env environment.Environment)
 		{
 			Name:    "node",
 			File:    "package.json",
-			Fetcher: n.getNodePackageVersion,
+			Fetcher: n.getNodePackage,
+		},
+		{
+			Name:    "cargo",
+			File:    "Cargo.toml",
+			Fetcher: n.getCargoPackage,
+		},
+		{
+			Name:    "poetry",
+			File:    "pyproject.toml",
+			Fetcher: n.getPoetryPackage,
 		},
 	}
 
 	n.Version = ""
+	n.Name = ""
 	for _, item := range n.projects {
 		if n.hasProjectFile(item) {
-			n.Version = item.Fetcher(*item)
+			n.Version, n.Name = item.Fetcher(*item)
 			break
 		}
 	}
@@ -67,15 +94,41 @@ func (n *Project) hasProjectFile(p *ProjectItem) bool {
 	return n.env.HasFiles(p.File)
 }
 
-func (n *Project) getNodePackageVersion(item ProjectItem) string {
+func (n *Project) getNodePackage(item ProjectItem) (string, string) {
 	content := n.env.FileContent(item.File)
 
-	var data NodePackageJSON
+	var data ProjectData
 	err := json.Unmarshal([]byte(content), &data)
 	if err != nil {
 		n.Error = err.Error()
-		return ""
+		return "", ""
+	}
+
+	return data.Version, data.Name
+}
+
+func (n *Project) getCargoPackage(item ProjectItem) (string, string) {
+	content := n.env.FileContent(item.File)
+
+	var data CargoTOML
+	_, err := toml.Decode(content, &data)
+	if err != nil {
+		n.Error = err.Error()
+		return "", ""
+	}
+
+	return data.Package.Version, data.Package.Name
+}
+
+func (n *Project) getPoetryPackage(item ProjectItem) (string, string) {
+	content := n.env.FileContent(item.File)
+
+	var data PyProjectTOML
+	_, err := toml.Decode(content, &data)
+	if err != nil {
+		n.Error = err.Error()
+		return "", ""
 	}
 
-	return data.Version
+	return data.Tool.Poetry.Version, data.Tool.Poetry.Name
 }


Test patch:
diff --git a/src/segments/project_test.go b/src/segments/project_test.go
index 4e096eba..ab1b1451 100644
--- a/src/segments/project_test.go
+++ b/src/segments/project_test.go
@@ -7,6 +7,8 @@ import (
 	"testing"
 
 	"github.com/alecthomas/assert"
+
+	testify_mock "github.com/stretchr/testify/mock"
 )
 
 type MockData struct {
@@ -20,17 +22,36 @@ type MockData struct {
 func getMockedPackageEnv(tc *MockData) (*mock.MockedEnvironment, properties.Map) {
 	env := new(mock.MockedEnvironment)
 	props := properties.Map{}
-	env.On("HasFiles", tc.File).Return(true)
+	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 TestPackage(t *testing.T) {
 	cases := []*MockData{
-		{Case: "1.0.0", ExpectedString: "\uf487 1.0.0", Name: "node", File: "package.json", PackageContents: "{\"version\":\"1.0.0\"}"},
-		{Case: "3.2.1", ExpectedString: "\uf487 3.2.1", Name: "node", File: "package.json", PackageContents: "{\"version\":\"3.2.1\"}"},
-		{Case: "No version present", ExpectedString: "", Name: "node", File: "package.json", PackageContents: "{}"},
+		{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: "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: "["},
 	}
 
 	for _, tc := range cases {