diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000..7745062 --- /dev/null +++ b/.gitattributes @@ -0,0 +1 @@ +* -text diff --git a/.github/workflows/go.yml b/.github/workflows/go.yml new file mode 100644 index 0000000..63e9b4b --- /dev/null +++ b/.github/workflows/go.yml @@ -0,0 +1,29 @@ +name: Go + +on: [push] + +env: + GO_VERSION: '>=1.21.0' + +jobs: + + build: + name: Build + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ ubuntu-latest, macos-latest, windows-latest ] + steps: + + - uses: actions/checkout@v4 + + - uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + + - run: go test + + - run: go build + +# based on: github.com/koron-go/_skeleton/.github/workflows/go.yml diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml new file mode 100644 index 0000000..90f34a2 --- /dev/null +++ b/.github/workflows/release.yml @@ -0,0 +1,131 @@ +name: Release + +on: [push] + +permissions: + contents: write + +env: + GO_VERSION: '>=1.21.0' + NAME: 'inflater' + +jobs: + + build: + + name: Build + runs-on: ${{ matrix.os }} + + strategy: + matrix: + os: [ ubuntu-latest, macos-latest, windows-latest ] + arch: [ amd64, arm64 ] + exclude: + - os: windows-latest + arch: arm64 + + steps: + + - uses: actions/checkout@v4 + - uses: actions/setup-go@v5 + with: + go-version: ${{ env.GO_VERSION }} + + - name: Setup env + id: setup + shell: bash + run: | + if [[ ${GITHUB_REF} =~ ^refs/tags/v[0-9]+\.[0-9]+ ]] ; then + export VERSION=${GITHUB_REF_NAME} + else + export VERSION=SNAPSHOT + fi + echo "VERSION=${VERSION}" >> $GITHUB_ENV + case ${{ matrix.os }} in + ubuntu-*) + if [ "${{ matrix.arch }}" = "arm64" ] ; then + sudo apt-get update + sudo apt-get -y install gcc-12-aarch64-linux-gnu + echo "CC=aarch64-linux-gnu-gcc-12" >> $GITHUB_ENV + fi + export GOOS=linux + export PKGEXT=.tar.gz + ;; + macos-*) + export GOOS=darwin + export PKGEXT=.zip + ;; + windows-*) + choco install zip + export GOOS=windows + export PKGEXT=.zip + ;; + esac + export GOARCH=${{ matrix.arch }} + echo "GOOS=${GOOS}" >> $GITHUB_ENV + echo "GOARCH=${GOARCH}" >> $GITHUB_ENV + echo "CGO_ENABLED=1" >> $GITHUB_ENV + echo "PKGNAME=${NAME}_${VERSION}_${GOOS}_${GOARCH}" >> $GITHUB_ENV + echo "PKGEXT=${PKGEXT}" >> $GITHUB_ENV + + - name: Build + shell: bash + run: | + go build + + - name: Archive + shell: bash + run: | + rm -rf _build/${PKGNAME} + mkdir -p _build/${PKGNAME} + cp -p ${NAME} _build/${PKGNAME} + cp -p LICENSE _build/${PKGNAME} + cp -p README.md _build/${PKGNAME} + + case "${PKGEXT}" in + ".tar.gz") + tar cf _build/${PKGNAME}${PKGEXT} -C _build ${PKGNAME} + ;; + ".zip") + (cd _build && zip -r9q ${PKGNAME}${PKGEXT} ${PKGNAME}) + ;; + esac + ls -laFR _build + + - name: Artifact upload + uses: actions/upload-artifact@v4 + with: + name: ${{ env.GOOS }}_${{ env.GOARCH }} + path: _build/${{ env.PKGNAME }}${{ env.PKGEXT }} + + create-release: + name: Create release + runs-on: ubuntu-latest + if: startsWith(github.ref, 'refs/tags/') + needs: + - build + steps: + - uses: actions/download-artifact@v4 + with: { name: darwin_amd64 } + - uses: actions/download-artifact@v4 + with: { name: darwin_arm64 } + - uses: actions/download-artifact@v4 + with: { name: linux_amd64 } + - uses: actions/download-artifact@v4 + with: { name: linux_arm64 } + - uses: actions/download-artifact@v4 + with: { name: windows_amd64 } + - run: ls -lafR + - name: Release + uses: softprops/action-gh-release@v2 + with: + draft: true + prerelease: ${{ contains(github.ref_name, '-alpha.') || contains(github.ref_name, '-beta.') }} + files: | + *.tar.gz + *.zip + fail_on_unmatched_files: true + generate_release_notes: true + append_body: true + +# based on: github.com/koron-go/_skeleton/.github/workflows/release.yml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..1519234 --- /dev/null +++ b/.gitignore @@ -0,0 +1,4 @@ +*~ +default.pgo +tags +tmp/ diff --git a/LICENSE b/LICENSE new file mode 100644 index 0000000..4a73ff2 --- /dev/null +++ b/LICENSE @@ -0,0 +1,21 @@ +The MIT License (MIT) + +Copyright (c) 2024 MURAOKA Taro + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/Makefile b/Makefile new file mode 100644 index 0000000..876c2e5 --- /dev/null +++ b/Makefile @@ -0,0 +1,42 @@ +TEST_PACKAGE ?= ./... + +.PHONY: build +build: + go build -gcflags '-e' + +.PHONY: test +test: + go test $(TEST_PACKAGE) + +.PHONY: bench +bench: + go test -bench $(TEST_PACKAGE) + +.PHONY: tags +tags: + gotags -f tags -R . + +.PHONY: cover +cover: + mkdir -p tmp + go test -coverprofile tmp/_cover.out $(TEST_PACKAGE) + go tool cover -html tmp/_cover.out -o tmp/cover.html + +.PHONY: checkall +checkall: vet staticcheck + +.PHONY: vet +vet: + go vet $(TEST_PACKAGE) + +.PHONY: staticcheck +staticcheck: + staticcheck $(TEST_PACKAGE) + +.PHONY: clean +clean: + go clean + rm -f tags + rm -f tmp/_cover.out tmp/cover.html + +# based on: github.com/koron-go/_skeleton/Makefile diff --git a/README.md b/README.md new file mode 100644 index 0000000..3fcb7f5 --- /dev/null +++ b/README.md @@ -0,0 +1,15 @@ +# koron-go/inflater + +[![PkgGoDev](https://pkg.go.dev/badge/github.com/koron-go/inflater)](https://pkg.go.dev/github.com/koron-go/inflater) +[![Actions/Go](https://github.com/koron-go/inflater/workflows/Go/badge.svg)](https://github.com/koron-go/inflater/actions?query=workflow%3AGo) +[![Go Report Card](https://goreportcard.com/badge/github.com/koron-go/inflater)](https://goreportcard.com/report/github.com/koron-go/inflater) + +## Operations + +* V -> S (inflate) +* V -> V +* S -> S (V -> S) +* S -> S (V -> V: map) +* S -> S (V -> discard: filter) +* S + S -> S (join) +* S * S -> S diff --git a/go.mod b/go.mod new file mode 100644 index 0000000..2099072 --- /dev/null +++ b/go.mod @@ -0,0 +1,3 @@ +module github.com/koron-go/inflater + +go 1.21 diff --git a/go.sum b/go.sum new file mode 100644 index 0000000..e69de29 diff --git a/inflater.go b/inflater.go new file mode 100644 index 0000000..04e3e05 --- /dev/null +++ b/inflater.go @@ -0,0 +1,145 @@ +/* +Package inflater provides ... +*/ +package inflater + +import ( + "iter" +) + +type Inflater[V any] interface { + Inflate(seed V) iter.Seq[V] +} + +type InflaterFunc[V any] func(seed V) iter.Seq[V] + +func (f InflaterFunc[V]) Inflate(seed V) iter.Seq[V] { + return f(seed) +} + +// None provides an Inflater which not inflate anything. +func None[V any]() Inflater[V] { + return InflaterFunc[V](func(seed V) iter.Seq[V] { + return func(yield func(V) bool) {} + }) +} + +// Keep provides an Inflater which inflate just only seed. +func Keep[V any]() Inflater[V] { + return InflaterFunc[V](func(seed V) iter.Seq[V] { + return func(yield func(V) bool) { + yield(seed) + } + }) +} + +func Map[V any] (fn func(V) V) Inflater[V] { + if fn == nil { + return Keep[V](fn) + } + return InflaterFunc[V](func(seed V) iter.Seq[V] { + return func(yield func(V) bool) { + yield(apply(seed)) + } + }) +} + +// Filter provides an Inflater which inflate a seed if check(seed) returns true. +func Filter[V any](check func(V) bool) Inflater[V] { + return InflaterFunc[V](func(seed V) iter.Seq[V] { + return func(yield func(V) bool) { + if check(seed) { + yield(seed) + } + } + }) +} + +// Distribute2 creates an Inflater with distribute a seed to two Inflaters. +func Distribute2[V any](first, second Inflater[V]) Inflater[V] { + return InflaterFunc[V](func(seed V) iter.Seq[V] { + return func(yield func(V) bool) { + for s := range first.Inflate(seed) { + if !yield(s) { + return + } + } + for s := range second.Inflate(seed) { + if !yield(s) { + return + } + } + } + }) +} + +// Distibute creates an Inflater which distibute a seed to multiple Inflaters. +func Distribute[V any](inflaters ...Inflater[V]) Inflater[V] { + switch len(inflaters) { + case 0: + return None[V]() + case 1: + return inflaters[0] + case 2: + return Distribute2(inflaters[0], inflaters[1]) + default: + return Distribute2(inflaters[0], Distribute(inflaters[1:]...)) + } +} + +// Reinflate2 creates an Inflater that inflates the result of the first +// Inflater with the second Inflater. +func Reinflate2[V any](first, second Inflater[V]) Inflater[V] { + return InflaterFunc[V](func(seed V) iter.Seq[V] { + return func(yield func(V) bool) { + for s := range first.Inflate(seed) { + for t := range second.Inflate(s) { + if !yield(t) { + return + } + } + } + } + }) +} + +// Reinflate creates an Inflater that applies multiple Inflaters in sequence to +// its result repeatedly. +func Reinflate[V any](inflaters ...Inflater[V]) Inflater[V] { + switch len(inflaters) { + case 0: + return None[V]() + case 1: + return inflaters[0] + case 2: + return Reinflate2[V](inflaters[0], inflaters[1]) + default: + return Reinflate2[V](inflaters[0], Reinflate(inflaters[1:]...)) + } +} + +// Prefix provides an Inflater which inflate with prefixes. +func Prefix[V ~string](prefixes ...V) Inflater[V] { + return InflaterFunc[V](func(seed V) iter.Seq[V] { + return func(yield func(V) bool) { + for _, prefix := range prefixes { + if !yield(prefix + seed) { + return + } + } + } + }) +} + +// Suffix provides an Inflater which inflate with suffixes. +func Suffix[V ~string](suffixes ...V) Inflater[V] { + return InflaterFunc[V](func(seed V) iter.Seq[V] { + return func(yield func(V) bool) { + for _, suffix := range suffixes { + if !yield(seed + suffix) { + return + } + } + } + }) +} diff --git a/staticcheck.conf b/staticcheck.conf new file mode 100644 index 0000000..8b70bd6 --- /dev/null +++ b/staticcheck.conf @@ -0,0 +1,5 @@ +# vim:set ft=toml: + +checks = ["all"] + +# based on: github.com/koron-go/_skeleton/staticcheck.conf