diff --git a/.forgejo/workflows/pull_request.yaml b/.forgejo/workflows/pull_request.yaml new file mode 100644 index 0000000..d331681 --- /dev/null +++ b/.forgejo/workflows/pull_request.yaml @@ -0,0 +1,67 @@ +on: + pull_request: + types: [opened, synchronize, reopened] + +defaults: + run: + shell: bash + +jobs: + library_snapshot: + runs-on: docker + container: + image: registry.garrity.co:8443/gs/ci-scala:latest + name: 'Build and Test Library Snapshot' + env: + GS_MAVEN_USER: ${{ vars.GS_MAVEN_USER }} + GS_MAVEN_TOKEN: ${{ secrets.GS_MAVEN_TOKEN }} + steps: + - uses: actions/checkout@v4 + name: 'Checkout Repository' + with: + fetch-depth: 0 + - name: 'Pre-Commit' + run: | + pre-commit install + pre-commit run --all-files + - name: 'Prepare Versioned Build' + run: | + latest_git_tag="$(git describe --tags --abbrev=0 || echo 'No Tags')" + latest_commit_message="$(git show -s --format=%s HEAD)" + if [[ "$latest_commit_message" == *"(major)"* ]]; then + export GS_RELEASE_TYPE="major" + elif [[ "$latest_commit_message" == *"(minor)"* ]]; then + export GS_RELEASE_TYPE="minor" + elif [[ "$latest_commit_message" == *"(patch)"* ]]; then + export GS_RELEASE_TYPE="patch" + elif [[ "$latest_commit_message" == *"(docs)"* ]]; then + export GS_RELEASE_TYPE="norelease" + elif [[ "$latest_commit_message" == *"(norelease)"* ]]; then + export GS_RELEASE_TYPE="norelease" + else + export GS_RELEASE_TYPE="norelease" + fi + echo "GS_RELEASE_TYPE=$GS_RELEASE_TYPE" >> $GITHUB_ENV + echo "Previous Git Tag: $latest_git_tag" + echo "Latest Commit: $latest_commit_message ($GS_RELEASE_TYPE) (SNAPSHOT)" + if [ "$GS_RELEASE_TYPE" = "norelease" ]; then + sbtn -Dsnapshot=true -Drelease="patch" semVerInfo + else + sbtn -Dsnapshot=true -Drelease="$GS_RELEASE_TYPE" semVerInfo + fi + - name: 'Unit Tests and Code Coverage' + run: | + sbtn clean + sbtn coverage + sbtn test + sbtn coverageReport + - name: 'Publish Snapshot' + run: | + echo "Testing env var propagation = ${{ env.GS_RELEASE_TYPE }}" + if [ "${{ env.GS_RELEASE_TYPE }}" = "norelease" ]; then + echo "Skipping publish due to GS_RELEASE_TYPE=norelease" + else + sbtn coverageOff + sbtn clean + sbtn publish + fi diff --git a/.forgejo/workflows/release.yaml b/.forgejo/workflows/release.yaml new file mode 100644 index 0000000..72fac7d --- /dev/null +++ b/.forgejo/workflows/release.yaml @@ -0,0 +1,84 @@ +on: + push: + branches: + - main + +defaults: + run: + shell: bash + +jobs: + library_release: + runs-on: docker + container: + image: registry.garrity.co:8443/gs/ci-scala:latest + name: 'Build and Release Library' + env: + GS_MAVEN_USER: ${{ vars.GS_MAVEN_USER }} + GS_MAVEN_TOKEN: ${{ secrets.GS_MAVEN_TOKEN }} + steps: + - uses: actions/checkout@v4 + name: 'Checkout Repository' + with: + fetch-depth: 0 + - name: 'Pre-Commit' + run: | + pre-commit install + pre-commit run --all-files + - name: 'Prepare Versioned Build' + run: | + latest_git_tag="$(git describe --tags --abbrev=0 || echo 'No Tags')" + latest_commit_message="$(git show -s --format=%s HEAD)" + if [[ "$latest_commit_message" == *"(major)"* ]]; then + export GS_RELEASE_TYPE="major" + elif [[ "$latest_commit_message" == *"(minor)"* ]]; then + export GS_RELEASE_TYPE="minor" + elif [[ "$latest_commit_message" == *"(patch)"* ]]; then + export GS_RELEASE_TYPE="patch" + elif [[ "$latest_commit_message" == *"(docs)"* ]]; then + export GS_RELEASE_TYPE="norelease" + elif [[ "$latest_commit_message" == *"(norelease)"* ]]; then + export GS_RELEASE_TYPE="norelease" + else + export GS_RELEASE_TYPE="norelease" + fi + + echo "GS_RELEASE_TYPE=$GS_RELEASE_TYPE" >> $GITHUB_ENV + echo "Previous Git Tag: $latest_git_tag" + echo "Latest Commit: $latest_commit_message" + echo "Selected Release Type: '$GS_RELEASE_TYPE'" + + if [ "$GS_RELEASE_TYPE" = "norelease" ]; then + echo "Skipping all versioning for 'norelease' commit." + else + sbtn -Drelease="$GS_RELEASE_TYPE" semVerInfo + fi + - name: 'Unit Tests and Code Coverage' + run: | + if [ "${{ env.GS_RELEASE_TYPE }}" = "norelease" ]; then + echo "Skipping build/test for 'norelease' commit." + else + sbtn clean + sbtn coverage + sbtn test + sbtn coverageReport + fi + - name: 'Publish Release' + run: | + if [ "${{ env.GS_RELEASE_TYPE }}" = "norelease" ]; then + echo "Skipping publish for 'norelease' commit." + else + sbtn coverageOff + sbtn clean + sbtn semVerWriteVersionToFile + sbtn publish + fi + - name: 'Create Git Tag' + run: | + if [ "${{ env.GS_RELEASE_TYPE }}" = "norelease" ]; then + echo "Skipping Git tag for 'norelease' commit." + else + selected_version="$(cat .version)" + git tag "$selected_version" + git push origin "$selected_version" + fi diff --git a/README.md b/README.md index 9adc379..6200566 100644 --- a/README.md +++ b/README.md @@ -3,10 +3,13 @@ [GS Open Source](https://garrity.co/open-source.html) | [License (MIT)](./LICENSE) -Serializable predicates for Scala 3. +Serializable predicates for Scala 3. [Circe](https://github.com/circe/circe) is +used for JSON representations. - [Usage](#usage) - [Dependency](#dependency) + - [JSON Predicates](#json-predicates) +- [List of Predicates](#list-of-predicates) - [Donate](#donate) ## Usage @@ -23,6 +26,90 @@ val GsPredicate: ModuleID = "gs" %% "gs-predicate-api-v0" % "$VERSION" ``` +### JSON Predicates + +The following example evaluates whether some JSON object: + +- Contains an object `foo`... +- That contains an array `bar` such that any one element of `bar`... +- Contains an array `baz` such that all elements of `baz`... +- Are objects that contain some property `xyz` with value `"example"`. + +```scala +import io.circe.Json +import gs.predicate.v0.api.* +import gs.predicate.v0.json.* +import gs.predicate.v0.json.query.JsonQuery + +val predicate = JsonComparisonPredicate( + JsonQuery.compile("foo.bar[any].baz[all].xyz"), + JsonComparison.Eq(Json.fromString("example")) +) + +val json = io.circe.parser.parse(""" +{ + "foo": { + "bar": [ + { + "baz": [ + { "xyz": "example" }, + { "xyz": "example" } + ] + }, + { + "baz": [ + { "xyz": "oops" }, + { "xyz": "example" } + ] + } + ] + } +} +""".stripMargin) + +assertEquals(predicate.eval(json), Predicate.Result.matched()) +``` + +## List of Predicates + +- `true` +- `false` +- `and` +- `or` +- string comparison +- json comparison + +### List of Comparison Operations + +- `true` +- `false` +- `and` +- `or` +- `eq` +- `neq` + +#### For Strings + +- `contains` +- `prefix` +- `suffix` + +#### When Parsed as Integer + +- `<` +- `<=` +- `>` +- `>=` +- Inclusive Range +- Exclusive Range + +#### When Parsed as Date + +- `before` +- `after` +- Inclusive Range +- Exclusive Range + ## Donate Enjoy this project or want to help me achieve my [goals](https://garrity.co)? diff --git a/src/main/scala/gs/predicate/v0/api/Predicate.scala b/src/main/scala/gs/predicate/v0/api/Predicate.scala index c07080c..dcd8688 100644 --- a/src/main/scala/gs/predicate/v0/api/Predicate.scala +++ b/src/main/scala/gs/predicate/v0/api/Predicate.scala @@ -3,8 +3,7 @@ package gs.predicate.v0.api /** A _Predicate_ is some function that accepts any input and emits some * [[Predicate.Result]] (whether the predicate matched). * - * Predicates evaluate input extracted from context to see if the predicate - * matches that input. + * Predicates evaluate input to see if the logic matches that input. */ trait Predicate: /** @return