Skip to content

Commit

Permalink
Refactor exported API
Browse files Browse the repository at this point in the history
  • Loading branch information
tangrufus committed Nov 29, 2024
1 parent dc72db4 commit 4dcf0e5
Show file tree
Hide file tree
Showing 33 changed files with 4,053 additions and 3,682 deletions.
63 changes: 32 additions & 31 deletions .github/workflows/go.yml
Original file line number Diff line number Diff line change
Expand Up @@ -25,44 +25,14 @@ jobs:
only-new-issues: true
args: --verbose

diff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- run: go mod tidy -diff
- run: go mod download
- run: go mod verify
- run: go generate ./...
- name: Detect uncommitted changes
run: |
changes=$(git status --porcelain)
if [[ -n "$changes" ]]; then
{
echo "## :construction: Uncommitted changes"
echo "\`\`\`console"
echo "\$ git status --porcelain"
echo "$changes"
echo "\`\`\`"
} >> "$GITHUB_STEP_SUMMARY"
echo "::group::Uncommitted changes"
echo "$changes"
echo "::endgroup::"

exit 1
fi

test:
needs: diff
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- run: go mod tidy -diff
- run: go mod download
- run: go mod verify
- run: go test -v -count=1 -race -shuffle=on -coverprofile=coverage.out -covermode=atomic ./...
Expand All @@ -83,3 +53,34 @@ jobs:
- uses: codecov/codecov-action@v4
with:
use_oidc: ${{ !(github.event_name == 'pull_request' && github.event.pull_request.head.repo.fork) }}

generate-diff:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: actions/setup-go@v5
with:
go-version-file: 'go.mod'
- run: go mod tidy -diff
- run: go mod download
- run: go mod verify
- run: go generate ./...
- name: Detect uncommitted changes
run: |
changes=$(git status --porcelain)
if [[ -n "$changes" ]]; then
{
echo '## :construction: Uncommitted changes'
echo 'Run `$ go generate ./...` to re-generate the files.'
echo '```console'
echo '$ git status --porcelain'
echo "$changes"
echo '```'
} >> "$GITHUB_STEP_SUMMARY"
echo "::group::Uncommitted changes"
echo "$changes"
echo "::endgroup::"
exit 1
fi
12 changes: 8 additions & 4 deletions .golangci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,17 +16,21 @@ linters:

disable:
- dupl
- depguard
# -
- testpackage
- varnamelen
- wsl
- nlreturn
# - nlreturn
- revive

issues:
exclude-rules:
- path: '(.+)_test\.go'
linters:
- dupl
- lll
- depguard
- funlen
- maintidx

- path: 'version_test\.go'
linters:
- lll
139 changes: 8 additions & 131 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,11 +6,11 @@

<div align="center">

[![Go Reference](https://pkg.go.dev/badge/github.com/typisttech/comver.svg)](https://pkg.go.dev/github.com/typisttech/comver)
[![GitHub Release](https://img.shields.io/github/v/release/typisttech/comver?style=flat-square&)](https://github.com/typisttech/comver/releases/latest)
[![Go](https://github.com/typisttech/comver/actions/workflows/go.yml/badge.svg)](https://github.com/typisttech/comver/actions/workflows/go.yml)
[![codecov](https://codecov.io/gh/typisttech/comver/graph/badge.svg?token=GVO7RV80TJ)](https://codecov.io/gh/typisttech/comver)
[![Go Report Card](https://goreportcard.com/badge/github.com/typisttech/comver)](https://goreportcard.com/report/github.com/typisttech/comver)
[![GitHub Release](https://img.shields.io/github/v/release/typisttech/comver?style=flat-square&)](https://github.com/typisttech/comver/releases/latest)
[![Go Reference](https://pkg.go.dev/badge/github.com/typisttech/comver.svg)](https://pkg.go.dev/github.com/typisttech/comver)
[![license](https://img.shields.io/github/license/typisttech/comver.svg?style=flat-square)](https://github.com/typisttech/comver/blob/master/LICENSE)
[![X Follow @TangRufus](https://img.shields.io/badge/Follow-%40TangRufus-black?style=flat-square&logo=x&logoColor=white)](https://x.com/tangrufus)
[![Hire Typist Tech](https://img.shields.io/badge/Hire-Typist%20Tech-ff69b4.svg?style=flat-square)](https://typist.tech/contact/)
Expand All @@ -29,138 +29,15 @@
## Usage

> [!NOTE]
> See full API documentation at [pkg.go.dev](https://pkg.go.dev/github.com/typisttech/comver).
### `Version`

[`NewVersion`](https://pkg.go.dev/github.com/typisttech/comver#NewVersion) parses a given version string, attempts to coerce a version string into a [`Version`](https://pkg.go.dev/github.com/typisttech/comver#Version) object or return an error if unable to parse the version string.

If there is a leading **v** or a version listed without all parts (e.g. **v1.2.p5+foo**) it will attempt to coerce it into a valid composer version (e.g. **1.2.0.0-patch5**). In both cases a [`Version`](https://pkg.go.dev/github.com/typisttech/comver#Version) object is returned that can be sorted, compared, and used in constraints.
>
> See full API documentation on [pkg.go.dev](https://pkg.go.dev/github.com/typisttech/comver).
## Known Issues

> [!WARNING]
> Due to implementation complexity, it only supports a subset of [composer versioning](https://github.com/composer/semver/).
> [!CAUTION]
>
> Refer to the [`version_test.go`](version_test.go) for examples.

```go
ss := []string{
"1.2.3",
"v1.2.p5+foo",
"v1.2.3.4.p5+foo",
"2010-01-02",
"2010-01-02.5",
"not a version",
"1.0.0-meh",
"20100102.0.3.4",
"1.0.0-alpha.beta",
}

for _, s := range ss {
v, err := comver.NewVersion(s)
if err != nil {
fmt.Println(s, " => ", err)
continue
}
fmt.Println(s, " => ", v)
}

// Output:
// 1.2.3 => 1.2.3.0
// v1.2.p5+foo => 1.2.0.0-patch5
// v1.2.3.4.p5+foo => 1.2.3.4-patch5
// 2010-01-02 => 2010.1.2.0
// 2010-01-02.5 => 2010.1.2.5
// not a version => error parsing version string "not a version"
// 1.0.0-meh => error parsing version string "1.0.0-meh"
// 20100102.0.3.4 => error parsing version string "20100102.0.3.4"
// 1.0.0-alpha.beta => error parsing version string "1.0.0-alpha.beta"
```

### `constraint`

```go
v1, _ := comver.NewVersion("1")
v2, _ := comver.NewVersion("2")
v3, _ := comver.NewVersion("3")
v4, _ := comver.NewVersion("4")

cs := []any{
comver.NewGreaterThanConstraint(v1),
comver.NewGreaterThanOrEqualToConstraint(v2),
comver.NewLessThanOrEqualToConstraint(v3),
comver.NewLessThanConstraint(v4),
}

for _, c := range cs {
fmt.Println(c)
}

// Output:
// >1
// >=2
// <=3
// <4
```

### `interval`

`interval` represents the intersection (logical AND) of two constraints.

```go
v1, _ := comver.NewVersion("1")
v2, _ := comver.NewVersion("2")
v3, _ := comver.NewVersion("3")

g1l3, _ := comver.NewInterval(
comver.NewGreaterThanConstraint(v1),
comver.NewLessThanConstraint(v3),
)

if g1l3.Check(v2) {
fmt.Println(v2.Short(), "satisfies", g1l3)
}

if !g1l3.Check(v3) {
fmt.Println(v2.Short(), "doesn't satisfy", g1l3)
}

// Output:
// 2 satisfies >1 <3
// 2 doesn't satisfy >1 <3
```

### `Intervals`

[`Intervals`](https://pkg.go.dev/github.com/typisttech/comver#Intervals) represent the union (logical OR) of multiple intervals.

```go
v1, _ := comver.NewVersion("1")
v2, _ := comver.NewVersion("2")
v3, _ := comver.NewVersion("3")
v4, _ := comver.NewVersion("4")

g1l3, _ := comver.NewInterval(
comver.NewGreaterThanConstraint(v1),
comver.NewLessThanConstraint(v3),
)

ge2le4, _ := comver.NewInterval(
comver.NewGreaterThanOrEqualToConstraint(v2),
comver.NewLessThanOrEqualToConstraint(v4),
)

is := comver.Intervals{g1l3, ge2le4}
fmt.Println(is)

is = comver.Compact(is)
fmt.Println(is)

// Output:
// >1 <3 || >=2 <=4
// >1 <=4
```
> Due to implementation complexity, it only supports a subset of [composer versioning](https://github.com/composer/semver/).
> Refer to the [version_test.go](https://github.com/typisttech/comver/blob/main/version_test.go) for examples.
## Credits

Expand Down
109 changes: 109 additions & 0 deletions and.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,109 @@
package comver

import "slices"

const (
errNoEndlessGiven stringError = "no endless given"
errUnexpectedAndLogic stringError = "unexpected and logic"
errImpossibleInterval stringError = "impossible interval"
)

// And returns a [CeilingFloorConstrainter] instance representing the logical AND of
// the given [Endless] instances; or return an error if the given [Endless] instances
// could never be satisfied at the same time.
func And(es ...Endless) (CeilingFloorConstrainter, error) { //nolint:cyclop,ireturn
var nilC CeilingFloorConstrainter

if len(es) == 0 {
return nilC, errNoEndlessGiven
}

es = slices.Clone(es)
es = slices.DeleteFunc(es, Endless.wildcard)

if len(es) == 0 {
return NewWildcard(), nil
}
if len(es) == 1 {
return es[0], nil
}

ceiling, ceilingOk := minBoundedCeiling(es...)
floor, floorOk := maxBoundedFloor(es...)

if !ceilingOk && !floorOk {
// logic error! This should never happen
return nilC, errUnexpectedAndLogic
}
if ceilingOk && !floorOk {
return ceiling, nil
}
if !ceilingOk { // floorOk is always true here
return floor, nil
}

vCmp := floor.floor().versionCompare(ceiling.ceiling().version)

if vCmp > 0 {
return nilC, errImpossibleInterval
}

if vCmp == 0 {
if !floor.floor().inclusive() || !ceiling.ceiling().inclusive() {
return nilC, errImpossibleInterval
}

return NewExactConstraint(*floor.floor().version), nil
}

return interval{
upper: ceiling,
lower: floor,
}, nil
}

// MustAnd is like [And] but panics if an error occurs.
func MustAnd(es ...Endless) CeilingFloorConstrainter { //nolint:ireturn
c, err := And(es...)
if err != nil {
panic(err)
}

return c
}

func minBoundedCeiling(es ...Endless) (Endless, bool) {
es = slices.Clone(es)

bcs := slices.DeleteFunc(es, func(b Endless) bool {
return b.ceiling().version == nil
})

if len(bcs) == 0 {
var nilF Endless

return nilF, false
}

return slices.MinFunc(bcs, func(a, b Endless) int {
return a.ceiling().compare(b.ceiling())
}), true
}

func maxBoundedFloor(es ...Endless) (Endless, bool) {
es = slices.Clone(es)

bfs := slices.DeleteFunc(es, func(c Endless) bool {
return c.floor().wildcard()
})

if len(bfs) == 0 {
var nilF Endless

return nilF, false
}

return slices.MaxFunc(bfs, func(a, b Endless) int {
return a.floor().compare(b.floor())
}), true
}
Loading

0 comments on commit 4dcf0e5

Please sign in to comment.