extract parse.go

This commit is contained in:
Denis Bilenko 2025-03-03 21:00:50 +01:00
parent 97109ec438
commit c3b659c5e3
4 changed files with 113 additions and 99 deletions

45
libs/patchwheel/parse.go Normal file
View File

@ -0,0 +1,45 @@
package patchwheel
import (
"fmt"
"path/filepath"
"strings"
)
// WheelInfo contains information extracted from a wheel filename
type WheelInfo struct {
Distribution string // Package distribution name
Version string // Package version
Tags []string // Python tags (python_tag, abi_tag, platform_tag)
}
// ParseWheelFilename parses a wheel filename and extracts its components.
// Wheel filenames follow the pattern: {distribution}-{version}-{python_tag}-{abi_tag}-{platform_tag}.whl
func ParseWheelFilename(filename string) (*WheelInfo, error) {
base := filepath.Base(filename)
parts := strings.Split(base, "-")
if len(parts) < 5 || !strings.HasSuffix(parts[len(parts)-1], ".whl") {
return nil, fmt.Errorf("invalid wheel filename format: %s", filename)
}
// The last three parts are always tags
tagStartIdx := len(parts) - 3
// Everything before the tags except the version is the distribution
versionIdx := tagStartIdx - 1
// Distribution may contain hyphens, so join all parts before the version
distribution := strings.Join(parts[:versionIdx], "-")
version := parts[versionIdx]
// Extract tags (remove .whl from the last one)
tags := make([]string, 3)
copy(tags, parts[tagStartIdx:])
tags[2] = strings.TrimSuffix(tags[2], ".whl")
return &WheelInfo{
Distribution: distribution,
Version: version,
Tags: tags,
}, nil
}

View File

@ -0,0 +1,68 @@
package patchwheel
import (
"testing"
"github.com/stretchr/testify/require"
)
// TestParseWheelFilename tests the ParseWheelFilename function.
func TestParseWheelFilename(t *testing.T) {
tests := []struct {
filename string
wantDistribution string
wantVersion string
wantTags []string
wantErr bool
}{
{
filename: "myproj-0.1.0-py3-none-any.whl",
wantDistribution: "myproj",
wantVersion: "0.1.0",
wantTags: []string{"py3", "none", "any"},
wantErr: false,
},
{
filename: "myproj-0.1.0+20240303123456-py3-none-any.whl",
wantDistribution: "myproj",
wantVersion: "0.1.0+20240303123456",
wantTags: []string{"py3", "none", "any"},
wantErr: false,
},
{
filename: "my-proj-with-hyphens-0.1.0-py3-none-any.whl",
wantDistribution: "my-proj-with-hyphens",
wantVersion: "0.1.0",
wantTags: []string{"py3", "none", "any"},
wantErr: false,
},
{
filename: "invalid-filename.txt",
wantDistribution: "",
wantVersion: "",
wantTags: nil,
wantErr: true,
},
{
filename: "not-enough-parts-py3.whl",
wantDistribution: "",
wantVersion: "",
wantTags: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.filename, func(t *testing.T) {
info, err := ParseWheelFilename(tt.filename)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, tt.wantDistribution, info.Distribution)
require.Equal(t, tt.wantVersion, info.Version)
require.Equal(t, tt.wantTags, info.Tags)
}
})
}
}

View File

@ -125,44 +125,6 @@ func readFile(file *zip.File) ([]byte, error) {
return io.ReadAll(rc)
}
// WheelInfo contains information extracted from a wheel filename
type WheelInfo struct {
Distribution string // Package distribution name
Version string // Package version
Tags []string // Python tags (python_tag, abi_tag, platform_tag)
}
// ParseWheelFilename parses a wheel filename and extracts its components.
// Wheel filenames follow the pattern: {distribution}-{version}-{python_tag}-{abi_tag}-{platform_tag}.whl
func ParseWheelFilename(filename string) (*WheelInfo, error) {
base := filepath.Base(filename)
parts := strings.Split(base, "-")
if len(parts) < 5 || !strings.HasSuffix(parts[len(parts)-1], ".whl") {
return nil, fmt.Errorf("invalid wheel filename format: %s", filename)
}
// The last three parts are always tags
tagStartIdx := len(parts) - 3
// Everything before the tags except the version is the distribution
versionIdx := tagStartIdx - 1
// Distribution may contain hyphens, so join all parts before the version
distribution := strings.Join(parts[:versionIdx], "-")
version := parts[versionIdx]
// Extract tags (remove .whl from the last one)
tags := make([]string, 3)
copy(tags, parts[tagStartIdx:])
tags[2] = strings.TrimSuffix(tags[2], ".whl")
return &WheelInfo{
Distribution: distribution,
Version: version,
Tags: tags,
}, nil
}
// PatchWheel patches a Python wheel file by updating its version in METADATA and RECORD.
// It returns the path to the new wheel.
// The function is idempotent: repeated calls with the same input will produce the same output.

View File

@ -105,67 +105,6 @@ func getWheel(t *testing.T, dir string) string {
return matches[0]
}
// TestParseWheelFilename tests the ParseWheelFilename function.
func TestParseWheelFilename(t *testing.T) {
tests := []struct {
filename string
wantDistribution string
wantVersion string
wantTags []string
wantErr bool
}{
{
filename: "myproj-0.1.0-py3-none-any.whl",
wantDistribution: "myproj",
wantVersion: "0.1.0",
wantTags: []string{"py3", "none", "any"},
wantErr: false,
},
{
filename: "myproj-0.1.0+20240303123456-py3-none-any.whl",
wantDistribution: "myproj",
wantVersion: "0.1.0+20240303123456",
wantTags: []string{"py3", "none", "any"},
wantErr: false,
},
{
filename: "my-proj-with-hyphens-0.1.0-py3-none-any.whl",
wantDistribution: "my-proj-with-hyphens",
wantVersion: "0.1.0",
wantTags: []string{"py3", "none", "any"},
wantErr: false,
},
{
filename: "invalid-filename.txt",
wantDistribution: "",
wantVersion: "",
wantTags: nil,
wantErr: true,
},
{
filename: "not-enough-parts-py3.whl",
wantDistribution: "",
wantVersion: "",
wantTags: nil,
wantErr: true,
},
}
for _, tt := range tests {
t.Run(tt.filename, func(t *testing.T) {
info, err := ParseWheelFilename(tt.filename)
if tt.wantErr {
require.Error(t, err)
} else {
require.NoError(t, err)
require.Equal(t, tt.wantDistribution, info.Distribution)
require.Equal(t, tt.wantVersion, info.Version)
require.Equal(t, tt.wantTags, info.Tags)
}
})
}
}
func TestPatchWheel(t *testing.T) {
pythonVersions := []string{"python3.9", "python3.10", "python3.11", "python3.12"}
for _, py := range pythonVersions {