From 264c2a8d331cef37f3aefe339eb8992e493990d2 Mon Sep 17 00:00:00 2001 From: KN4CK3R Date: Sun, 7 Apr 2024 18:46:59 +0200 Subject: [PATCH] Some NuGet package enhancements (#30280) Fixes #30265 1. Read second type of dependencies 2. Render `Description` and `ReleaseNotes` old: ![grafik](https://github.com/go-gitea/gitea/assets/1666336/abac057c-11cd-4d25-b196-01ff899d948e) new: ![grafik](https://github.com/go-gitea/gitea/assets/1666336/35302273-740c-481a-a031-1f80d2d7d336) The NuGet spec does not specify what kind of text can be stored in the description but we can best guess markdown. The official NuGet registry just [converts the newlines to html lines](https://www.nuget.org/packages/rb.Firefox#readme-body-tab). 3. Extract and render the readme. This is the new and better place to store larger text than in the description. The content is markdown. ![grafik](https://github.com/go-gitea/gitea/assets/1666336/f442264e-3735-4b55-92c4-3b89a8ebafb0) --------- Co-authored-by: Benjamin Heemann --- modules/packages/nuget/metadata.go | 34 +++++++++++++- modules/packages/nuget/metadata_test.go | 62 +++++++++++++++---------- templates/package/content/nuget.tmpl | 9 ++-- 3 files changed, 73 insertions(+), 32 deletions(-) diff --git a/modules/packages/nuget/metadata.go b/modules/packages/nuget/metadata.go index 3c478b1c0288c..6769c514cc2ff 100644 --- a/modules/packages/nuget/metadata.go +++ b/modules/packages/nuget/metadata.go @@ -58,6 +58,7 @@ type Package struct { type Metadata struct { Description string `json:"description,omitempty"` ReleaseNotes string `json:"release_notes,omitempty"` + Readme string `json:"readme,omitempty"` Authors string `json:"authors,omitempty"` ProjectURL string `json:"project_url,omitempty"` RepositoryURL string `json:"repository_url,omitempty"` @@ -71,6 +72,7 @@ type Dependency struct { Version string `json:"version"` } +// https://learn.microsoft.com/en-us/nuget/reference/nuspec type nuspecPackage struct { Metadata struct { ID string `xml:"id"` @@ -80,6 +82,7 @@ type nuspecPackage struct { ProjectURL string `xml:"projectUrl"` Description string `xml:"description"` ReleaseNotes string `xml:"releaseNotes"` + Readme string `xml:"readme"` PackageTypes struct { PackageType []struct { Name string `xml:"name,attr"` @@ -89,6 +92,11 @@ type nuspecPackage struct { URL string `xml:"url,attr"` } `xml:"repository"` Dependencies struct { + Dependency []struct { + ID string `xml:"id,attr"` + Version string `xml:"version,attr"` + Exclude string `xml:"exclude,attr"` + } `xml:"dependency"` Group []struct { TargetFramework string `xml:"targetFramework,attr"` Dependency []struct { @@ -122,14 +130,14 @@ func ParsePackageMetaData(r io.ReaderAt, size int64) (*Package, error) { } defer f.Close() - return ParseNuspecMetaData(f) + return ParseNuspecMetaData(archive, f) } } return nil, ErrMissingNuspecFile } // ParseNuspecMetaData parses a Nuspec file to retrieve the metadata of a Nuget package -func ParseNuspecMetaData(r io.Reader) (*Package, error) { +func ParseNuspecMetaData(archive *zip.Reader, r io.Reader) (*Package, error) { var p nuspecPackage if err := xml.NewDecoder(r).Decode(&p); err != nil { return nil, err @@ -166,6 +174,28 @@ func ParseNuspecMetaData(r io.Reader) (*Package, error) { Dependencies: make(map[string][]Dependency), } + if p.Metadata.Readme != "" { + f, err := archive.Open(p.Metadata.Readme) + if err == nil { + buf, _ := io.ReadAll(f) + m.Readme = string(buf) + _ = f.Close() + } + } + + if len(p.Metadata.Dependencies.Dependency) > 0 { + deps := make([]Dependency, 0, len(p.Metadata.Dependencies.Dependency)) + for _, dep := range p.Metadata.Dependencies.Dependency { + if dep.ID == "" || dep.Version == "" { + continue + } + deps = append(deps, Dependency{ + ID: dep.ID, + Version: dep.Version, + }) + } + m.Dependencies[""] = deps + } for _, group := range p.Metadata.Dependencies.Group { deps := make([]Dependency, 0, len(group.Dependency)) for _, dep := range group.Dependency { diff --git a/modules/packages/nuget/metadata_test.go b/modules/packages/nuget/metadata_test.go index bba2bff4a538a..f466492f8a85e 100644 --- a/modules/packages/nuget/metadata_test.go +++ b/modules/packages/nuget/metadata_test.go @@ -6,7 +6,6 @@ package nuget import ( "archive/zip" "bytes" - "strings" "testing" "github.com/stretchr/testify/assert" @@ -19,6 +18,7 @@ const ( projectURL = "https://gitea.io" description = "Package Description" releaseNotes = "Package Release Notes" + readme = "Readme" repositoryURL = "https://gitea.io/gitea/gitea" targetFramework = ".NETStandard2.1" dependencyID = "System.Text.Json" @@ -36,6 +36,7 @@ const nuspecContent = ` ` + description + ` ` + releaseNotes + ` + README.md @@ -60,17 +61,19 @@ const symbolsNuspecContent = ` ` func TestParsePackageMetaData(t *testing.T) { - createArchive := func(name, content string) []byte { + createArchive := func(files map[string]string) []byte { var buf bytes.Buffer archive := zip.NewWriter(&buf) - w, _ := archive.Create(name) - w.Write([]byte(content)) + for name, content := range files { + w, _ := archive.Create(name) + w.Write([]byte(content)) + } archive.Close() return buf.Bytes() } t.Run("MissingNuspecFile", func(t *testing.T) { - data := createArchive("dummy.txt", "") + data := createArchive(map[string]string{"dummy.txt": ""}) np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) assert.Nil(t, np) @@ -78,7 +81,7 @@ func TestParsePackageMetaData(t *testing.T) { }) t.Run("MissingNuspecFileInRoot", func(t *testing.T) { - data := createArchive("sub/package.nuspec", "") + data := createArchive(map[string]string{"sub/package.nuspec": ""}) np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) assert.Nil(t, np) @@ -86,7 +89,7 @@ func TestParsePackageMetaData(t *testing.T) { }) t.Run("InvalidNuspecFile", func(t *testing.T) { - data := createArchive("package.nuspec", "") + data := createArchive(map[string]string{"package.nuspec": ""}) np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) assert.Nil(t, np) @@ -94,10 +97,10 @@ func TestParsePackageMetaData(t *testing.T) { }) t.Run("InvalidPackageId", func(t *testing.T) { - data := createArchive("package.nuspec", ` + data := createArchive(map[string]string{"package.nuspec": ` - `) + `}) np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) assert.Nil(t, np) @@ -105,30 +108,34 @@ func TestParsePackageMetaData(t *testing.T) { }) t.Run("InvalidPackageVersion", func(t *testing.T) { - data := createArchive("package.nuspec", ` + data := createArchive(map[string]string{"package.nuspec": ` - `+id+` + ` + id + ` - `) + `}) np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) assert.Nil(t, np) assert.ErrorIs(t, err, ErrNuspecInvalidVersion) }) - t.Run("Valid", func(t *testing.T) { - data := createArchive("package.nuspec", nuspecContent) + t.Run("MissingReadme", func(t *testing.T) { + data := createArchive(map[string]string{"package.nuspec": nuspecContent}) np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) assert.NoError(t, err) assert.NotNil(t, np) + assert.Empty(t, np.Metadata.Readme) }) -} -func TestParseNuspecMetaData(t *testing.T) { t.Run("Dependency Package", func(t *testing.T) { - np, err := ParseNuspecMetaData(strings.NewReader(nuspecContent)) + data := createArchive(map[string]string{ + "package.nuspec": nuspecContent, + "README.md": readme, + }) + + np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) assert.NoError(t, err) assert.NotNil(t, np) assert.Equal(t, DependencyPackage, np.PackageType) @@ -139,6 +146,7 @@ func TestParseNuspecMetaData(t *testing.T) { assert.Equal(t, projectURL, np.Metadata.ProjectURL) assert.Equal(t, description, np.Metadata.Description) assert.Equal(t, releaseNotes, np.Metadata.ReleaseNotes) + assert.Equal(t, readme, np.Metadata.Readme) assert.Equal(t, repositoryURL, np.Metadata.RepositoryURL) assert.Len(t, np.Metadata.Dependencies, 1) assert.Contains(t, np.Metadata.Dependencies, targetFramework) @@ -148,13 +156,15 @@ func TestParseNuspecMetaData(t *testing.T) { assert.Equal(t, dependencyVersion, deps[0].Version) t.Run("NormalizedVersion", func(t *testing.T) { - np, err := ParseNuspecMetaData(strings.NewReader(` - - - test - 1.04.5.2.5-rc.1+metadata - -`)) + data := createArchive(map[string]string{"package.nuspec": ` + + + test + 1.04.5.2.5-rc.1+metadata + + `}) + + np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) assert.NoError(t, err) assert.NotNil(t, np) assert.Equal(t, "1.4.5.2-rc.1", np.Version) @@ -162,7 +172,9 @@ func TestParseNuspecMetaData(t *testing.T) { }) t.Run("Symbols Package", func(t *testing.T) { - np, err := ParseNuspecMetaData(strings.NewReader(symbolsNuspecContent)) + data := createArchive(map[string]string{"package.nuspec": symbolsNuspecContent}) + + np, err := ParsePackageMetaData(bytes.NewReader(data), int64(len(data))) assert.NoError(t, err) assert.NotNil(t, np) assert.Equal(t, SymbolsPackage, np.PackageType) diff --git a/templates/package/content/nuget.tmpl b/templates/package/content/nuget.tmpl index 0911260fbaa21..f1fe420c0ba24 100644 --- a/templates/package/content/nuget.tmpl +++ b/templates/package/content/nuget.tmpl @@ -16,12 +16,11 @@ - {{if or .PackageDescriptor.Metadata.Description .PackageDescriptor.Metadata.ReleaseNotes}} + {{if or .PackageDescriptor.Metadata.Description .PackageDescriptor.Metadata.ReleaseNotes .PackageDescriptor.Metadata.Readme}}

{{ctx.Locale.Tr "packages.about"}}

-
- {{if .PackageDescriptor.Metadata.Description}}{{.PackageDescriptor.Metadata.Description}}{{end}} - {{if .PackageDescriptor.Metadata.ReleaseNotes}}{{.PackageDescriptor.Metadata.ReleaseNotes}}{{end}} -
+ {{if .PackageDescriptor.Metadata.Description}}
{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.Description}}
{{end}} + {{if .PackageDescriptor.Metadata.Readme}}
{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.Readme}}
{{end}} + {{if .PackageDescriptor.Metadata.ReleaseNotes}}
{{RenderMarkdownToHtml $.Context .PackageDescriptor.Metadata.ReleaseNotes}}
{{end}} {{end}} {{if .PackageDescriptor.Metadata.Dependencies}}