Skip to content

Commit

Permalink
feature: parsing
Browse files Browse the repository at this point in the history
  • Loading branch information
mprimeaux committed Nov 25, 2024
1 parent 333c388 commit 493216c
Show file tree
Hide file tree
Showing 6 changed files with 478 additions and 334 deletions.
14 changes: 13 additions & 1 deletion CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,17 @@ Date format: `YYYY-MM-DD`
### Fixed
### Security

---
## [1.1.0] - 2024-10-25

### Added

### Changed
### Deprecated
### Removed
### Fixed
### Security

---
## [1.0.0] - 2024-10-24

Expand All @@ -28,7 +39,8 @@ Date format: `YYYY-MM-DD`
### Fixed
### Security

[Unreleased]: https://github.com/sixafter/semver/compare/v1.0.0...HEAD
[Unreleased]: https://github.com/sixafter/semver/compare/v1.1.0...HEAD
[1.0.0]: https://github.com/sixafter/semver/compare/v1.0.0...v1.1.0
[1.0.0]: https://github.com/sixafter/semver/compare/d63ed577e7e841fb2209cfdcf4269fac6a57c85e...v1.0.0

[MUST]: https://datatracker.ietf.org/doc/html/rfc2119
Expand Down
12 changes: 6 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -209,13 +209,13 @@ goos: darwin
goarch: arm64
pkg: github.com/sixafter/semver
cpu: Apple M2 Ultra
BenchmarkParseVersionSerial-24 1577372 744.4 ns/op 608 B/op 16 allocs/op
BenchmarkParseVersionConcurrent-24 3696235 337.3 ns/op 608 B/op 16 allocs/op
BenchmarkParseVersionAllocations-24 7339026 162.1 ns/op 160 B/op 4 allocs/op
BenchmarkParseVersionLargeSerial-24 208 5825681 ns/op 4640079 B/op 120000 allocs/op
BenchmarkParseVersionLargeConcurrent-24 589 2047877 ns/op 4640185 B/op 120000 allocs/op
BenchmarkParseVersionSerial-24 1642676 725.2 ns/op 544 B/op 14 allocs/op
BenchmarkParseVersionConcurrent-24 4059534 303.9 ns/op 544 B/op 14 allocs/op
BenchmarkParseVersionAllocations-24 7236553 162.4 ns/op 144 B/op 4 allocs/op
BenchmarkParseVersionLargeSerial-24 208 5747548 ns/op 4160079 B/op 110000 allocs/op
BenchmarkParseVersionLargeConcurrent-24 656 1874606 ns/op 4160163 B/op 110000 allocs/op
PASS
ok github.com/sixafter/semver 8.366s
ok github.com/sixafter/semver 8.274s
```
</details>

Expand Down
147 changes: 147 additions & 0 deletions marshalers.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,147 @@
// Copyright (c) 2024 Six After, Inc
//
// This source code is licensed under the Apache 2.0 License found in the
// LICENSE file in the root directory of this source tree.

package semver

import (
"database/sql/driver"
"encoding/json"
)

// MarshalText implements encoding.TextMarshaler.
// It returns the string representation of the Version.
//
// Example:
//
// v := semver.MustParse("1.2.3-alpha+build.456")
// text, err := v.MarshalText()
// if err != nil {
// log.Fatal(err)
// }
// fmt.Println(string(text)) // Output: 1.2.3-alpha+build.456
func (v Version) MarshalText() ([]byte, error) {
return []byte(v.String()), nil
}

// UnmarshalText implements encoding.TextUnmarshaler.
// It parses the given text into a Version.
//
// Example:
//
// var v semver.Version
// err := v.UnmarshalText([]byte("1.2.3-alpha+build.456"))
// if err != nil {
// log.Fatal(err)
// }
// fmt.Println(v) // Output: 1.2.3-alpha+build.456
func (v *Version) UnmarshalText(text []byte) error {
parsed, err := Parse(string(text))
if err != nil {
return err
}
*v = parsed
return nil
}

// MarshalBinary implements encoding.BinaryMarshaler.
// It returns the binary encoding of the Version.
//
// Example:
//
// v := semver.MustParse("1.2.3-alpha")
// binaryData, err := v.MarshalBinary()
// if err != nil {
// log.Fatal(err)
// }
// fmt.Printf("%s\n", binaryData) // Output: 1.2.3-alpha
func (v Version) MarshalBinary() ([]byte, error) {
return v.MarshalText()
}

// UnmarshalBinary implements encoding.BinaryUnmarshaler.
// It decodes the given binary data into a Version.
//
// Example:
//
// var v semver.Version
// err := v.UnmarshalBinary([]byte("1.2.3+build.123"))
// if err != nil {
// log.Fatal(err)
// }
// fmt.Println(v) // Output: 1.2.3+build.123
func (v *Version) UnmarshalBinary(data []byte) error {
return v.UnmarshalText(data)
}

// MarshalJSON implements json.Marshaler.
// It returns the JSON encoding of the Version.
//
// Example:
//
// v := semver.MustParse("1.2.3-beta")
// jsonData, err := v.MarshalJSON()
// if err != nil {
// log.Fatal(err)
// }
// fmt.Println(string(jsonData)) // Output: "1.2.3-beta"
func (v Version) MarshalJSON() ([]byte, error) {
return json.Marshal(v.String())
}

// UnmarshalJSON implements json.Unmarshaler.
// It decodes JSON data into a Version.
//
// Example:
//
// var v semver.Version
// err := v.UnmarshalJSON([]byte("\"1.2.3-beta+build\""))
// if err != nil {
// log.Fatal(err)
// }
// fmt.Println(v) // Output: 1.2.3-beta+build
func (v *Version) UnmarshalJSON(data []byte) error {
var text string
if err := json.Unmarshal(data, &text); err != nil {
return err
}
return v.UnmarshalText([]byte(text))
}

// Value implements database/sql/driver.Valuer.
// It returns the string representation of the Version as a database value.
//
// Example:
//
// v := semver.MustParse("1.2.3-alpha")
// dbValue, err := v.Value()
// if err != nil {
// log.Fatal(err)
// }
// fmt.Println(dbValue) // Output: 1.2.3-alpha
func (v Version) Value() (driver.Value, error) {
return v.String(), nil
}

// Scan implements database/sql.Scanner.
// It scans a database value into a Version.
//
// Example:
//
// var v semver.Version
// err := v.Scan("1.2.3-alpha+build")
// if err != nil {
// log.Fatal(err)
// }
// fmt.Println(v) // Output: 1.2.3-alpha+build
func (v *Version) Scan(value interface{}) error {
switch t := value.(type) {
case string:
return v.UnmarshalText([]byte(t))
case []byte:
return v.UnmarshalText(t)
default:
return ErrUnsupportedType
}
}
86 changes: 11 additions & 75 deletions prerelease_version.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,19 +6,10 @@
package semver

import (
"errors"
"fmt"
"strconv"
"strings"
)

// Error messages for prerelease validation.
var (
ErrEmptyPrerelease = errors.New("prerelease is empty")
ErrLeadingZeroInNumeric = "numeric prerelease version must not contain leading zeroes: %q"
ErrInvalidPrereleaseChars = "invalid character(s) found in prerelease: %q"
)

// PrereleaseVersion represents a semantic version prerelease identifier.
//
// A prerelease version can be either numeric or alphanumeric.
Expand Down Expand Up @@ -47,40 +38,25 @@ type PrereleaseVersion struct {
// if err != nil {
// fmt.Println("Error:", err) // Output: numeric prerelease version must not contain leading zeroes: "01"
// }
func NewPrereleaseVersion(s string) (PrereleaseVersion, error) {
if len(s) == 0 {
return PrereleaseVersion{}, errors.New("prerelease is empty")
func NewPrereleaseVersion(part string) (PrereleaseVersion, error) {
if len(part) == 0 {
return PrereleaseVersion{}, ErrEmptyPrereleaseIdentifier
}

// Check if the string contains only numbers
if containsOnlyNumbers(s) {
// Check for leading zeroes
if len(s) > 1 && s[0] == '0' {
return PrereleaseVersion{}, fmt.Errorf("numeric prerelease version must not contain leading zeroes: %q", s)
if isNumeric(part) {
if part[0] == '0' && len(part) > 1 {
return PrereleaseVersion{}, ErrLeadingZeroInNumericIdentifier
}

// Parse numeric string
number, err := strconv.ParseUint(s, 10, 64)
x, err := strconv.ParseUint(part, 10, 64)
if err != nil {
return PrereleaseVersion{}, err
return PrereleaseVersion{}, ErrInvalidNumericIdentifier
}

return PrereleaseVersion{
partNumeric: number,
isNumeric: true,
}, nil
}

// Check if the string contains only alphanumeric characters
if containsOnlyAlphanumeric(s) {
return PrereleaseVersion{
partString: s,
isNumeric: false,
}, nil
return PrereleaseVersion{partNumeric: x, isNumeric: true}, nil
}

// If neither numeric nor alphanumeric, return an error
return PrereleaseVersion{}, fmt.Errorf("invalid character(s) found in prerelease: %q", s)
return PrereleaseVersion{partString: part, isNumeric: false}, nil
}

// IsNumeric checks if the prerelease version is numeric.
Expand Down Expand Up @@ -152,46 +128,6 @@ func (v PrereleaseVersion) String() string {
if v.isNumeric {
return strconv.FormatUint(v.partNumeric, 10)
}
return v.partString
}

// containsOnlyNumbers checks if the string contains only numeric characters.
//
// Example:
//
// fmt.Println(containsOnlyNumbers("123")) // Output: true
// fmt.Println(containsOnlyNumbers("abc")) // Output: false
func containsOnlyNumbers(s string) bool {
for i := 0; i < len(s); i++ {
if s[i] < '0' || s[i] > '9' {
return false
}
}
return true
}

// containsOnlyAlphanumeric checks if the string contains only ASCII letters and numbers.
//
// Example:
//
// fmt.Println(containsOnlyAlphanumeric("abc123")) // Output: true
// fmt.Println(containsOnlyAlphanumeric("abc-123")) // Output: false
func containsOnlyAlphanumeric(s string) bool {
for i := 0; i < len(s); i++ {
c := s[i]
if !((c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z')) {
return false
}
}
return true
}

// hasLeadingZeroes checks if the string has leading zeroes.
//
// Example:
//
// fmt.Println(hasLeadingZeroes("0123")) // Output: true
// fmt.Println(hasLeadingZeroes("123")) // Output: false
func hasLeadingZeroes(s string) bool {
return len(s) > 1 && s[0] == '0'
return v.partString
}
Loading

0 comments on commit 493216c

Please sign in to comment.