├── website ├── static │ ├── .nojekyll │ ├── CNAME │ └── img │ │ ├── null.png │ │ ├── scala.png │ │ ├── favicon.png │ │ ├── poster.png │ │ ├── version-control.png │ │ ├── just-semver-logo-32x32.png │ │ ├── just-semver-logo-64x64.png │ │ └── just-semver-logo-96x96.png ├── algolia.config.json ├── google-analytics.config.json ├── babel.config.js ├── tsconfig.json ├── src │ ├── components │ │ └── HomepageFeatures │ │ │ ├── styles.module.css │ │ │ └── index.tsx │ ├── pages │ │ ├── index.module.css │ │ ├── versionsArchived.json │ │ ├── index.tsx │ │ └── versions.tsx │ └── css │ │ └── custom.css ├── .gitignore ├── sidebars.js ├── README.md ├── package.json └── docusaurus.config.ts ├── project ├── build.properties ├── plugins.sbt └── ProjectInfo.scala ├── codecov.yml ├── docs ├── decver │ ├── _category_.json │ └── decver.md ├── semver │ ├── _category_.json │ └── semver.md └── intro.md ├── .github ├── pr-labeler.yml ├── dependabot.yml ├── ISSUE_TEMPLATE │ ├── task-template.md │ └── bug_report.md └── workflows │ ├── pr-labeler.yml │ ├── sbt-build-test.sh │ ├── sbt-build-all-with-coverage.sh │ ├── sbt-build-all.sh │ ├── sbt-build.sh │ ├── doc-site-build-only.yml │ ├── doc-site-publish.yml │ ├── coverage.yml │ ├── build.yml │ └── release.yml ├── modules ├── just-semver-core │ └── shared │ │ └── src │ │ ├── main │ │ ├── scala-3 │ │ │ └── just │ │ │ │ ├── semver │ │ │ │ ├── Compat.scala │ │ │ │ ├── Anh.scala │ │ │ │ ├── expr │ │ │ │ │ └── ComparisonOperator.scala │ │ │ │ ├── matcher │ │ │ │ │ ├── SemVerComparison.scala │ │ │ │ │ └── SemVerMatcher.scala │ │ │ │ ├── Dsv.scala │ │ │ │ ├── AdditionalInfo.scala │ │ │ │ ├── ParseError.scala │ │ │ │ └── SemVer.scala │ │ │ │ └── Common.scala │ │ ├── scala-2.12_2.13 │ │ │ └── just │ │ │ │ └── semver │ │ │ │ └── Compat.scala │ │ ├── scala │ │ │ └── just │ │ │ │ └── semver │ │ │ │ └── parser │ │ │ │ ├── ParserError.scala │ │ │ │ ├── ComparisonOperatorParser.scala │ │ │ │ └── Parser.scala │ │ ├── scala-2.11 │ │ │ └── just │ │ │ │ └── semver │ │ │ │ └── Compat.scala │ │ └── scala-2 │ │ │ └── just │ │ │ ├── Common.scala │ │ │ └── semver │ │ │ ├── expr │ │ │ └── ComparisonOperator.scala │ │ │ ├── Anh.scala │ │ │ ├── matcher │ │ │ ├── SemVerMatcher.scala │ │ │ └── SemVerComparison.scala │ │ │ ├── Dsv.scala │ │ │ ├── AdditionalInfo.scala │ │ │ ├── ParseError.scala │ │ │ └── SemVer.scala │ │ └── test │ │ └── scala │ │ └── just │ │ ├── semver │ │ ├── CommonGens.scala │ │ ├── parser │ │ │ ├── ParserSpec.scala │ │ │ └── ComparisonOperatorParserSpec.scala │ │ ├── AnhSpec.scala │ │ ├── SemVerMajorSpec.scala │ │ ├── SemVerMinorSpec.scala │ │ ├── SemVerPatchSpec.scala │ │ ├── matcher │ │ │ ├── Gens.scala │ │ │ └── SemVerComparisonSpec.scala │ │ └── DsvSpec.scala │ │ └── GenPlus.scala └── just-semver-decver │ └── shared │ └── src │ ├── main │ ├── scala-3 │ │ └── just │ │ │ └── decver │ │ │ └── matcher │ │ │ ├── DecVerComparison.scala │ │ │ └── DecVerMatcher.scala │ └── scala-2 │ │ └── just │ │ └── decver │ │ └── matcher │ │ ├── DecVerMatcher.scala │ │ └── DecVerComparison.scala │ └── test │ └── scala │ └── just │ └── decver │ ├── matcher │ ├── Gens.scala │ └── DecVerComparisonSpec.scala │ └── DecVerGens.scala ├── changelogs ├── 1.1.0.md ├── 0.2.0.md ├── 0.13.0.md ├── 0.12.0.md ├── 1.1.1.md ├── 0.1.1.md ├── 0.1.2.md ├── 0.10.0.md ├── 0.6.0.md ├── 0.3.0.md ├── 0.11.0.md ├── 0.7.0.md ├── 0.8.0.md ├── 0.9.0.md ├── 0.1.0.md ├── 0.4.0.md ├── 0.5.0.md └── 1.0.0.md ├── PULL_REQUEST_TEMPLATE.md ├── .scalafix.conf ├── .build-scripts ├── env-info.sh ├── build-project-simple.sh └── build-project.sh ├── README.md ├── LICENSE ├── .gitignore └── .scalafmt.conf /website/static/.nojekyll: -------------------------------------------------------------------------------- 1 | -------------------------------------------------------------------------------- /website/algolia.config.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /website/google-analytics.config.json: -------------------------------------------------------------------------------- 1 | {} -------------------------------------------------------------------------------- /website/static/CNAME: -------------------------------------------------------------------------------- 1 | just-semver.kevinly.dev -------------------------------------------------------------------------------- /project/build.properties: -------------------------------------------------------------------------------- 1 | sbt.version=1.11.7 2 | -------------------------------------------------------------------------------- /codecov.yml: -------------------------------------------------------------------------------- 1 | comment: 2 | layout: "reach, diff, flags, files" 3 | behavior: default 4 | -------------------------------------------------------------------------------- /website/static/img/null.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevin-lee/just-semver/HEAD/website/static/img/null.png -------------------------------------------------------------------------------- /website/static/img/scala.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevin-lee/just-semver/HEAD/website/static/img/scala.png -------------------------------------------------------------------------------- /website/static/img/favicon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevin-lee/just-semver/HEAD/website/static/img/favicon.png -------------------------------------------------------------------------------- /website/static/img/poster.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevin-lee/just-semver/HEAD/website/static/img/poster.png -------------------------------------------------------------------------------- /website/babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [require.resolve('@docusaurus/core/lib/babel/preset')], 3 | }; 4 | -------------------------------------------------------------------------------- /website/static/img/version-control.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevin-lee/just-semver/HEAD/website/static/img/version-control.png -------------------------------------------------------------------------------- /docs/decver/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "DecVer", 3 | "position": 3, 4 | "collapsible": true, 5 | "collapsed": false 6 | } 7 | -------------------------------------------------------------------------------- /docs/semver/_category_.json: -------------------------------------------------------------------------------- 1 | { 2 | "label": "SemVer", 3 | "position": 2, 4 | "collapsible": true, 5 | "collapsed": false 6 | } 7 | -------------------------------------------------------------------------------- /website/static/img/just-semver-logo-32x32.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevin-lee/just-semver/HEAD/website/static/img/just-semver-logo-32x32.png -------------------------------------------------------------------------------- /website/static/img/just-semver-logo-64x64.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevin-lee/just-semver/HEAD/website/static/img/just-semver-logo-64x64.png -------------------------------------------------------------------------------- /website/static/img/just-semver-logo-96x96.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/kevin-lee/just-semver/HEAD/website/static/img/just-semver-logo-96x96.png -------------------------------------------------------------------------------- /.github/pr-labeler.yml: -------------------------------------------------------------------------------- 1 | dependencies: [ 'update/**', 'dependabot/**' ] 2 | documentation: [ 'docs/**' ] 3 | pr: '**' 4 | release: 'prepare-to-release' 5 | -------------------------------------------------------------------------------- /.github/dependabot.yml: -------------------------------------------------------------------------------- 1 | version: 2 2 | updates: 3 | - package-ecosystem: "github-actions" 4 | directory: "/" 5 | schedule: 6 | interval: "weekly" 7 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala-3/just/semver/Compat.scala: -------------------------------------------------------------------------------- 1 | package just.semver 2 | 3 | /** @author Kevin Lee 4 | * @since 2021-05-28 5 | */ 6 | trait Compat 7 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala-2.12_2.13/just/semver/Compat.scala: -------------------------------------------------------------------------------- 1 | package just.semver 2 | 3 | /** @author Kevin Lee 4 | * @since 2021-05-28 5 | */ 6 | trait Compat 7 | -------------------------------------------------------------------------------- /changelogs/1.1.0.md: -------------------------------------------------------------------------------- 1 | ## [1.1.0](https://github.com/Kevin-Lee/just-semver/issues?q=is%3Aissue+is%3Aclosed+milestone%3Amilestone15) - 2024-10-13 2 | 3 | ## New Feature 4 | 5 | * Support Scala Native (#239) 6 | -------------------------------------------------------------------------------- /changelogs/0.2.0.md: -------------------------------------------------------------------------------- 1 | ## [0.2.0](https://github.com/Kevin-Lee/just-semver/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+milestone%3Amilestone4) - 2021-05-15 2 | 3 | ### Done 4 | * Support Scala `3.0.0` (#78) 5 | -------------------------------------------------------------------------------- /changelogs/0.13.0.md: -------------------------------------------------------------------------------- 1 | ## [0.13.0](https://github.com/Kevin-Lee/just-semver/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+milestone%3Amilestone13) - 2023-10-12 2 | 3 | ## New Feature 4 | * Support Scala Native (#208) 5 | -------------------------------------------------------------------------------- /changelogs/0.12.0.md: -------------------------------------------------------------------------------- 1 | ## [0.12.0](https://github.com/Kevin-Lee/just-semver/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+milestone%3Amilestone12) - 2023-10-01 2 | 3 | ## Internal Housekeeping 4 | * Bump Scala 3 to `3.3.1` (LTS) (#202) 5 | -------------------------------------------------------------------------------- /website/tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // This file is not used in compilation. It is here just for a nice editor experience. 3 | "extends": "@tsconfig/docusaurus/tsconfig.json", 4 | "compilerOptions": { 5 | "baseUrl": "." 6 | } 7 | } 8 | -------------------------------------------------------------------------------- /changelogs/1.1.1.md: -------------------------------------------------------------------------------- 1 | ## [1.1.1](https://github.com/Kevin-Lee/just-semver/issues?q=is%3Aissue%20is%3Aclosed%20milestone%3Amilestone16) - 2025-03-30 2 | 3 | ## Fixed 4 | 5 | * Fix: `Dsv.parse` doesn't fail when the given input contains non-ascii or non-digit chars (#245) 6 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/task-template.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Task template 3 | about: For a task ticket 4 | title: '' 5 | labels: task 6 | assignees: 'Kevin-Lee' 7 | 8 | --- 9 | 10 | # Task 11 | ## Summary 12 | 13 | ## Project Details 14 | Version: 15 | 16 | ## Description 17 | -------------------------------------------------------------------------------- /changelogs/0.1.1.md: -------------------------------------------------------------------------------- 1 | ## [0.1.1](https://github.com/Kevin-Lee/just-semver/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+milestone%3Amilestone2) - 2020-12-24 🎄 2 | 3 | ### Done 4 | * Support Dotty (#58) 5 | * Support Scala `3.0.0-M1` and `3.0.0-M2` (#62) 6 | * Support Scala `3.0.0-M3` (#64) 7 | -------------------------------------------------------------------------------- /changelogs/0.1.2.md: -------------------------------------------------------------------------------- 1 | ## [0.1.2](https://github.com/Kevin-Lee/just-semver/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+milestone%3Amilestone3) - 2021-04-02 2 | 3 | ### Done 4 | * Support Scala `3.0.0-RC1` (#69) 5 | * Support Scala `3.0.0-RC2` (#72) 6 | * Use `sbt-ci-release` to release (#75) 7 | -------------------------------------------------------------------------------- /changelogs/0.10.0.md: -------------------------------------------------------------------------------- 1 | ## [0.10.0](https://github.com/Kevin-Lee/just-semver/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+milestone%3Amilestone10) - 2023-10-01 2 | 3 | ## Internal Housekeeping 4 | * Bump Scala (#191) 5 | 6 | Bump Scala to 7 | * `2.12.18` 8 | * `2.13.12` 9 | * `3.2.2` 10 | -------------------------------------------------------------------------------- /website/src/components/HomepageFeatures/styles.module.css: -------------------------------------------------------------------------------- 1 | .features { 2 | display: flex; 3 | align-items: center; 4 | padding: 2rem 0; 5 | width: 100%; 6 | } 7 | 8 | .featureSvg { 9 | height: 200px; 10 | width: 200px; 11 | } 12 | 13 | .featurePng { 14 | height: 200px; 15 | width: 200px; 16 | } 17 | -------------------------------------------------------------------------------- /changelogs/0.6.0.md: -------------------------------------------------------------------------------- 1 | ## [0.6.0](https://github.com/Kevin-Lee/just-semver/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+milestone%3Amilestone8) - 2022-10-10 2 | 3 | ## New Support 4 | * Support Scala.js (#160) 5 | 6 | ## Internal Housekeeping 7 | * Create a doc site (#156) - https://just-semver.kevinly.dev 8 | * Modularize the project (#150) -------------------------------------------------------------------------------- /PULL_REQUEST_TEMPLATE.md: -------------------------------------------------------------------------------- 1 | # Summary 2 | Close #0 - Summary 3 | * Details 4 | * 5 | 6 | *** 7 | 8 | # just-semver v0.0.0 9 | 10 | ## Done 11 | 12 | * Blah (#999) 13 | * 14 | * 15 | 16 | *** 17 | 18 | # Release v0.0.0 => master 19 | 20 | Details: [v0.0.0](https://github.com/Kevin-Lee/just-semver/releases/tag/v0.0.0) 21 | 22 | *** -------------------------------------------------------------------------------- /changelogs/0.3.0.md: -------------------------------------------------------------------------------- 1 | ## [0.3.0](https://github.com/Kevin-Lee/just-semver/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+milestone%3Amilestone5) - 2021-05-29 2 | 3 | ### Done 4 | * Add some extension methods (#81) 5 | * Add a method to get `(Major, Minor, Patch)` (#82) 6 | * Make `just-semver` 0-dependency project (#86) 7 | * Removed: `just-fp` 8 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/test/scala/just/semver/CommonGens.scala: -------------------------------------------------------------------------------- 1 | package just.semver 2 | 3 | import hedgehog.{Gen, Range} 4 | 5 | /** 6 | * @author Kevin Lee 7 | * @since 2024-07-27 8 | */ 9 | object CommonGens { 10 | 11 | def genVersionNumberWithRange(range: Range[Int]): Gen[Int] = 12 | Gen.int(range) 13 | 14 | } 15 | -------------------------------------------------------------------------------- /changelogs/0.11.0.md: -------------------------------------------------------------------------------- 1 | ## [0.11.0](https://github.com/Kevin-Lee/just-semver/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+milestone%3Amilestone11) - 2023-10-01 2 | 3 | ## Internal Housekeeping 4 | * Bump bump Scala 3 to `3.3.0` (LTS) to solve #186 (#198) 5 | * Bump Scala version to 3.3.0+ to avoid lazy val not working properly under GraalVM Native Image (#186) 6 | -------------------------------------------------------------------------------- /website/.gitignore: -------------------------------------------------------------------------------- 1 | # Dependencies 2 | /node_modules 3 | 4 | # Production 5 | /build 6 | 7 | # Generated files 8 | .docusaurus 9 | .cache-loader 10 | 11 | # Misc 12 | .DS_Store 13 | .env.local 14 | .env.development.local 15 | .env.test.local 16 | .env.production.local 17 | 18 | npm-debug.log* 19 | yarn-debug.log* 20 | yarn-error.log* 21 | 22 | package-lock.json 23 | -------------------------------------------------------------------------------- /changelogs/0.7.0.md: -------------------------------------------------------------------------------- 1 | ## [0.7.0](https://github.com/Kevin-Lee/just-semver/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+milestone%3Amilestone9) - 2023-10-01 2 | 3 | ## Internal Housekeeping 4 | * Bump sbt and Scala, and drop Scala `2.11` (#187) 5 | * Bump sbt to `1.9.6` 6 | * Bump Scala to 7 | * `2.12.17` 8 | * `2.13.11` 9 | * `3.1.3` 10 | * Drop Scala `2.11` support 11 | -------------------------------------------------------------------------------- /.github/workflows/pr-labeler.yml: -------------------------------------------------------------------------------- 1 | name: "PR Labeler" 2 | on: 3 | pull_request_target: 4 | types: [ opened, reopened, edited ] 5 | 6 | jobs: 7 | pr-labeler: 8 | runs-on: ubuntu-latest 9 | steps: 10 | - uses: TimonVS/pr-labeler-action@v5 11 | with: 12 | configuration-path: .github/pr-labeler.yml # optional, .github/pr-labeler.yml is the default value 13 | env: 14 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 15 | -------------------------------------------------------------------------------- /website/src/pages/index.module.css: -------------------------------------------------------------------------------- 1 | /** 2 | * CSS files with the .module.css suffix will be treated as CSS modules 3 | * and scoped locally. 4 | */ 5 | 6 | .heroBanner { 7 | padding: 4rem 0; 8 | text-align: center; 9 | position: relative; 10 | overflow: hidden; 11 | } 12 | 13 | @media screen and (max-width: 996px) { 14 | .heroBanner { 15 | padding: 2rem; 16 | } 17 | } 18 | 19 | .buttons { 20 | display: flex; 21 | align-items: center; 22 | justify-content: center; 23 | } 24 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/test/scala/just/GenPlus.scala: -------------------------------------------------------------------------------- 1 | package just 2 | 3 | import hedgehog._ 4 | 5 | /** @author 6 | * Kevin Lee 7 | * @since 8 | * 2018-11-04 9 | */ 10 | object GenPlus { 11 | 12 | def range[A](r: Range[A])(f: Range[A] => Gen[A]): Gen[A] = { 13 | val (min, max) = r.bounds(Size(Size.max)) 14 | Gen.frequency1( 15 | (1, Gen.constant(min)), 16 | (1, Gen.constant(max)), 17 | (1, Gen.constant(r.origin)), 18 | (97, f(r)) 19 | ) 20 | } 21 | 22 | } 23 | -------------------------------------------------------------------------------- /changelogs/0.8.0.md: -------------------------------------------------------------------------------- 1 | ## [0.8.0](https://github.com/Kevin-Lee/just-semver/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+milestone%3Amilestone9) - 2023-10-01 2 | 3 | > NOTE: This release is exactly the same as `0.7.0`. 4 | > 5 | > `0.8.0` was released because the release of `0.7.0` was not done properly. 6 | 7 | ## Internal Housekeeping 8 | * Bump sbt and Scala, and drop Scala `2.11` (#187) 9 | * Bump sbt to `1.9.6` 10 | * Bump Scala to 11 | * `2.12.17` 12 | * `2.13.11` 13 | * `3.1.3` 14 | * Drop Scala `2.11` support 15 | -------------------------------------------------------------------------------- /changelogs/0.9.0.md: -------------------------------------------------------------------------------- 1 | ## [0.9.0](https://github.com/Kevin-Lee/just-semver/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+milestone%3Amilestone9) - 2023-10-01 2 | 3 | > NOTE: This release is exactly the same as `0.7.0` and `0.8.0`. 4 | > 5 | > `0.9.0` was released because `0.7.0` and `0.8.0` were not released properly. 6 | 7 | ## Internal Housekeeping 8 | * Bump sbt and Scala, and drop Scala `2.11` (#187) 9 | * Bump sbt to `1.9.6` 10 | * Bump Scala to 11 | * `2.12.17` 12 | * `2.13.11` 13 | * `3.1.3` 14 | * Drop Scala `2.11` support 15 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala/just/semver/parser/ParserError.scala: -------------------------------------------------------------------------------- 1 | package just.semver.parser 2 | 3 | /** @author Kevin Lee 4 | * @since 2022-04-03 5 | */ 6 | final case class ParserError(message: String, parsed: Option[String], rest: Option[String]) 7 | object ParserError { 8 | implicit class ParserErrorOps(private val parserError: ParserError) extends AnyVal { 9 | def render: String = 10 | s"ParserError: ${parserError.message} after ${parserError.parsed.fold("Nothing")("parsing " + _)}. " + 11 | s"The rest: ${parserError.rest.getOrElse("")}" 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /.scalafix.conf: -------------------------------------------------------------------------------- 1 | rules = [ 2 | DisableSyntax 3 | LeakingImplicitClassVal 4 | NoValInForComprehension 5 | ] 6 | DisableSyntax.noVars = true 7 | DisableSyntax.noThrows = true 8 | DisableSyntax.noNulls = true 9 | DisableSyntax.noReturns = true 10 | DisableSyntax.noWhileLoops = true 11 | DisableSyntax.noAsInstanceOf = true 12 | DisableSyntax.noIsInstanceOf = true 13 | DisableSyntax.noXml = true 14 | DisableSyntax.noDefaultArgs = true 15 | DisableSyntax.noFinalVal = true 16 | DisableSyntax.noFinalize = true 17 | DisableSyntax.noValPatterns = true 18 | DisableSyntax.noUniversalEquality = false // replaced by strictEquality in Scala 3 19 | DisableSyntax.regex = [] 20 | -------------------------------------------------------------------------------- /.build-scripts/env-info.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | echo "============================================" 4 | echo "Java Info" 5 | java -version 6 | echo "--------------------------------------------" 7 | echo "Scala Info" 8 | scala -version 9 | echo "--------------------------------------------" 10 | echo "SBT Info" 11 | sbt sbtVersion || : 12 | echo "--------------------------------------------" 13 | echo "Memory Info" 14 | echo "--------------------------------------------" 15 | echo "free -m" 16 | free -m 17 | echo "--------------------------------------------" 18 | echo "grep MemTotal /proc/meminfo" 19 | grep MemTotal /proc/meminfo 20 | echo "--------------------------------------------" 21 | -------------------------------------------------------------------------------- /changelogs/0.1.0.md: -------------------------------------------------------------------------------- 1 | ## [0.1.0](https://github.com/Kevin-Lee/just-semver/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+milestone%3Amilestone1) - 2019-10-23 2 | 3 | ### Done 4 | * Add `SemVer` 5 | * Remove `Identifier` (#18) 6 | * Refactor `compare` method for `SemVer` (#20) 7 | * Move instance member methods to the companion object (#22) 8 | * Remove `SequenceBasedVersion` (#26) 9 | * Change AlphaNumHyphen to Anh and AlphaNumHyphenGroup to Dsv (#28) 10 | * Add increase for `SemVer` (#36) 11 | 12 | ### Fixed 13 | * `SemVer` compare does not work for `SemVer` containing `PreRelease` (#32) 14 | 15 | ### Project Setting Changes 16 | * Change group id for publishing to `io.kevinlee` (#12) 17 | * Set up GitHub Actions for CI (#14) 18 | * Add PR templates (#24) 19 | -------------------------------------------------------------------------------- /.github/workflows/sbt-build-test.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | set -x 4 | 5 | if [ -z "$1" ] 6 | then 7 | echo "Missing parameters. Please enter the [Scala version]." 8 | echo "sbt-build-test.sh 2.13.6" 9 | exit 1 10 | else 11 | 12 | scala_version=$1 13 | sbt_params="$2" 14 | echo "============================================" 15 | echo "Testing projects" 16 | echo "--------------------------------------------" 17 | echo "" 18 | 19 | sbt \ 20 | -J-XX:MaxMetaspaceSize=1024m \ 21 | -J-Xmx2048m \ 22 | ++${scala_version}! \ 23 | -v "${sbt_params}" \ 24 | clean \ 25 | test 26 | 27 | echo "============================================" 28 | echo "Testing projects: Done" 29 | echo "============================================" 30 | fi 31 | -------------------------------------------------------------------------------- /changelogs/0.4.0.md: -------------------------------------------------------------------------------- 1 | ## [0.4.0](https://github.com/Kevin-Lee/just-semver/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+milestone%3Amilestone6) - 2022-04-11 2 | 3 | ### Done 4 | * Rename `SemVer.parseUnsafe` to `SemVer.unsafeParse` to keep the consistency with other `unsafe` methods (#131) 5 | * Replace `Major`, `Minor` and `Patch` value classes with `opaque type` in Scala 3 (#129) 6 | * Add `SemVerMatchers`, `SemVer.matches()` and `SemVer.unsafeMatches()` (#125) 7 | * Remove `can-equal` (#102) 8 | * Use Scala 3 syntax (#91) 9 | *** 10 | * Publish to `s01.oss.sonatype.org` (the new Maven central) (#115) 11 | * Stop uploading artifacts to GitHub Release (#113) 12 | * Set up Codecov in GitHub Actions (#111) 13 | * Upgrade `sbt-devoops` from `2.6.0` to `2.14.0` (#103) `2.14.0` => `2.15.0` (#121) `2.15.0` => `2.16.0` (#126) 14 | -------------------------------------------------------------------------------- /.build-scripts/build-project-simple.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | set -x 4 | 5 | if [ -z "$1" ] 6 | then 7 | echo "Scala version is missing. Please enter the Scala version." 8 | echo "build-project.sh 2.11.12" 9 | else 10 | scala_version=$1 11 | echo "============================================" 12 | echo "Build projects" 13 | echo "--------------------------------------------" 14 | echo "" 15 | if [[ "$BRANCH_NAME" == "rc" ]] 16 | then 17 | sbt -d -J-Xmx2048m "; ++ ${scala_version}!; clean; test" 18 | sbt -d -J-Xmx2048m "; ++ ${scala_version}!; packageBin; packageSrc; packageDoc" 19 | else 20 | sbt -d -J-Xmx2048m "; ++ ${scala_version}!; clean; test; package" 21 | fi 22 | 23 | echo "============================================" 24 | echo "Building projects: Done" 25 | echo "============================================" 26 | fi 27 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.md: -------------------------------------------------------------------------------- 1 | --- 2 | name: Bug report 3 | about: Create a report to help us improve 4 | title: '' 5 | labels: bug 6 | assignees: 'Kevin-Lee' 7 | 8 | --- 9 | 10 | # Bug 11 | 12 | # Summary 13 | 14 | # Project Details 15 | Version: 16 | Scala Version: 17 | Java Version: 18 | 19 | **Additional context** 20 | Add any other context about the problem here. 21 | 22 | # Description 23 | **Describe the bug** 24 | A clear and concise description of what the bug is. 25 | 26 | **To Reproduce** 27 | Steps to reproduce the behavior: 28 | 1. Go to '...' 29 | 2. Click on '....' 30 | 3. Scroll down to '....' 31 | 4. See error 32 | 33 | **Expected behavior** 34 | A clear and concise description of what you expected to happen. 35 | 36 | **Screenshots** 37 | If applicable, add screenshots to help explain your problem. 38 | 39 | # Possible Cause 40 | # Cause 41 | 42 | # Possible Solution 43 | # Solution 44 | -------------------------------------------------------------------------------- /website/sidebars.js: -------------------------------------------------------------------------------- 1 | /** 2 | * Creating a sidebar enables you to: 3 | - create an ordered group of docs 4 | - render a sidebar for each doc of that group 5 | - provide next/previous navigation 6 | 7 | The sidebars can be generated from the filesystem, or explicitly defined here. 8 | 9 | Create as many sidebars as you want. 10 | */ 11 | 12 | // @ts-check 13 | 14 | /** @type {import('@docusaurus/plugin-content-docs').SidebarsConfig} */ 15 | const sidebars = { 16 | // By default, Docusaurus generates a sidebar from the docs folder structure 17 | tutorialSidebar: [{type: 'autogenerated', dirName: '.'}], 18 | 19 | // But you can create a sidebar manually 20 | /* 21 | tutorialSidebar: [ 22 | 'intro', 23 | 'hello', 24 | { 25 | type: 'category', 26 | label: 'Tutorial', 27 | items: ['tutorial-basics/create-a-document'], 28 | }, 29 | ], 30 | */ 31 | }; 32 | 33 | module.exports = sidebars; 34 | -------------------------------------------------------------------------------- /website/README.md: -------------------------------------------------------------------------------- 1 | # Website 2 | 3 | This website is built using [Docusaurus 2](https://docusaurus.io/), a modern static website generator. 4 | 5 | ### Installation 6 | 7 | ``` 8 | $ yarn 9 | ``` 10 | 11 | ### Local Development 12 | 13 | ``` 14 | $ yarn start 15 | ``` 16 | 17 | This command starts a local development server and opens up a browser window. Most changes are reflected live without having to restart the server. 18 | 19 | ### Build 20 | 21 | ``` 22 | $ yarn build 23 | ``` 24 | 25 | This command generates static content into the `build` directory and can be served using any static contents hosting service. 26 | 27 | ### Deployment 28 | 29 | Using SSH: 30 | 31 | ``` 32 | $ USE_SSH=true yarn deploy 33 | ``` 34 | 35 | Not using SSH: 36 | 37 | ``` 38 | $ GIT_USER= yarn deploy 39 | ``` 40 | 41 | If you are using GitHub pages for hosting, this command is a convenient way to build the website and push to the `gh-pages` branch. 42 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala/just/semver/parser/ComparisonOperatorParser.scala: -------------------------------------------------------------------------------- 1 | package just.semver.parser 2 | 3 | import just.semver.Compat 4 | import just.semver.expr.ComparisonOperator 5 | 6 | /** @author Kevin Lee 7 | * @since 2022-04-03 8 | */ 9 | object ComparisonOperatorParser extends Compat { 10 | private val operators = "<>=!" 11 | private val parser = Parser.charsWhile(c => operators.contains(c)) 12 | 13 | def parse(s: String): Either[ParserError, (ComparisonOperator, String)] = { 14 | parser.parse(s).flatMap { 15 | case (rest, parsed) => 16 | ComparisonOperator 17 | .parse(parsed) 18 | .map(operator => (operator, rest)) 19 | .left 20 | .map(err => 21 | ParserError( 22 | s"Parsed successfully but failed to create ComparisonOperator: Error: $err", 23 | Some(parsed), 24 | Some(rest) 25 | ) 26 | ) 27 | } 28 | } 29 | } 30 | -------------------------------------------------------------------------------- /.build-scripts/build-project.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | set -x 4 | 5 | if [ -z "$1" ] 6 | then 7 | echo "Scala version is missing. Please enter the Scala version." 8 | echo "build-project.sh 2.11.12" 9 | else 10 | scala_version=$1 11 | echo "============================================" 12 | echo "Build projects" 13 | echo "--------------------------------------------" 14 | echo "" 15 | if [[ "$BRANCH_NAME" == "rc" ]] 16 | then 17 | sbt -d -J-Xmx2048m "; ++ ${scala_version}!; clean; coverage; test; coverageReport; coverageAggregate" 18 | sbt -d -J-Xmx2048m "; ++ ${scala_version}!; packageBin; packageSrc; packageDoc" 19 | else 20 | sbt -d -J-Xmx2048m "; ++ ${scala_version}!; clean; coverage; test; coverageReport; coverageAggregate; package" 21 | fi 22 | 23 | sbt -d -J-Xmx2048m "; ++ ${scala_version}!; coveralls" 24 | 25 | echo "============================================" 26 | echo "Building projects: Done" 27 | echo "============================================" 28 | fi 29 | -------------------------------------------------------------------------------- /website/src/pages/versionsArchived.json: -------------------------------------------------------------------------------- 1 | [ 2 | { 3 | "name": "1.1.0", 4 | "label": "1.1.0" 5 | }, 6 | { 7 | "name": "1.0.0", 8 | "label": "1.0.0" 9 | }, 10 | { 11 | "name": "0.13.0", 12 | "label": "0.13.0" 13 | }, 14 | { 15 | "name": "0.12.0", 16 | "label": "0.12.0" 17 | }, 18 | { 19 | "name": "0.11.0", 20 | "label": "0.11.0" 21 | }, 22 | { 23 | "name": "0.10.0", 24 | "label": "0.10.0" 25 | }, 26 | { 27 | "name": "0.9.0", 28 | "label": "0.9.0" 29 | }, 30 | { 31 | "name": "0.6.0", 32 | "label": "0.6.0" 33 | }, 34 | { 35 | "name": "0.5.0", 36 | "label": "0.5.0" 37 | }, 38 | { 39 | "name": "0.4.0", 40 | "label": "0.4.0" 41 | }, 42 | { 43 | "name": "0.3.0", 44 | "label": "0.3.0" 45 | }, 46 | { 47 | "name": "0.2.0", 48 | "label": "0.2.0" 49 | }, 50 | { 51 | "name": "0.1.2", 52 | "label": "0.1.2" 53 | }, 54 | { 55 | "name": "0.1.1", 56 | "label": "0.1.1" 57 | }, 58 | { 59 | "name": "0.1.0", 60 | "label": "0.1.0" 61 | } 62 | ] -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # just-semver 2 | 3 | [![Build Status](https://github.com/Kevin-Lee/just-semver/workflows/Build%20All/badge.svg)](https://github.com/Kevin-Lee/just-semver/actions?workflow=Build+All) 4 | [![Release Status](https://github.com/Kevin-Lee/just-semver/workflows/Release/badge.svg)](https://github.com/Kevin-Lee/just-semver/actions?workflow=Release) 5 | [![Coverage Status](https://coveralls.io/repos/github/Kevin-Lee/just-semver/badge.svg?branch=master)](https://coveralls.io/github/Kevin-Lee/just-semver?branch=master) 6 | 7 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.kevinlee/just-semver_2.13/badge.svg)](https://search.maven.org/artifact/io.kevinlee/just-semver_2.13) 8 | [![Latest version](https://index.scala-lang.org/kevin-lee/just-semver/just-semver/latest.svg)](https://index.scala-lang.org/kevin-lee/just-semver/just-semver) 9 | [![Scala.js](https://www.scala-js.org/assets/badges/scalajs-1.11.0.svg)](https://www.scala-js.org) 10 | 11 | 12 | Semantic Versioning (`SemVer`) for Scala 13 | 14 | Docs: [https://just-semver.kevinly.dev](https://just-semver.kevinly.dev) 15 | -------------------------------------------------------------------------------- /project/plugins.sbt: -------------------------------------------------------------------------------- 1 | logLevel := sbt.Level.Warn 2 | 3 | addSbtPlugin("com.github.sbt" % "sbt-ci-release" % "1.11.1") 4 | addSbtPlugin("org.scoverage" % "sbt-scoverage" % "2.3.1") 5 | addSbtPlugin("ch.epfl.scala" % "sbt-scalafix" % "0.12.0") 6 | 7 | addSbtPlugin("org.scalameta" % "sbt-mdoc" % "2.5.2") 8 | addSbtPlugin("io.kevinlee" % "sbt-docusaur" % "0.17.0") 9 | 10 | addSbtPlugin("org.scala-js" % "sbt-scalajs" % "1.18.2") 11 | addSbtPlugin("org.portable-scala" % "sbt-scalajs-crossproject" % "1.3.2") 12 | 13 | addSbtPlugin("org.scala-native" % "sbt-scala-native" % "0.5.7") 14 | addSbtPlugin("org.portable-scala" % "sbt-scala-native-crossproject" % "1.3.2") 15 | 16 | addSbtPlugin("org.scalameta" % "sbt-native-image" % "0.3.4") 17 | 18 | val sbtDevOopsVersion = "3.2.1" 19 | addSbtPlugin("io.kevinlee" % "sbt-devoops-scala" % sbtDevOopsVersion) 20 | addSbtPlugin("io.kevinlee" % "sbt-devoops-sbt-extra" % sbtDevOopsVersion) 21 | addSbtPlugin("io.kevinlee" % "sbt-devoops-github" % sbtDevOopsVersion) 22 | addSbtPlugin("io.kevinlee" % "sbt-devoops-starter" % sbtDevOopsVersion) 23 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Kevin Lee 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | target/ 2 | lib_managed/ 3 | src_managed/ 4 | project/boot/ 5 | project/plugins/project/ 6 | project/**/metals.sbt 7 | .bloop/ 8 | generated-docs*/ 9 | docs-gen-tmp/ 10 | 11 | .history 12 | .cache 13 | .lib/ 14 | ### Scala template 15 | *.class 16 | *.log 17 | ### Eclipse template 18 | 19 | .metadata 20 | bin/ 21 | tmp/ 22 | *.tmp 23 | *.bak 24 | *.swp 25 | *~.nib 26 | local.properties 27 | .settings/ 28 | .loadpath 29 | .recommenders 30 | 31 | # External tool builders 32 | .externalToolBuilders/ 33 | 34 | # Locally stored "Eclipse launch configurations" 35 | *.launch 36 | 37 | # sbteclipse plugin 38 | .target 39 | 40 | # Scala IDE specific (Scala & Java development for Eclipse) 41 | .cache-main 42 | .scala_dependencies 43 | .worksheet 44 | ### macOS template 45 | # General 46 | .DS_Store 47 | .AppleDouble 48 | .LSOverride 49 | 50 | # Thumbnails 51 | ._* 52 | 53 | # Package Files # 54 | *.jar 55 | *.war 56 | *.ear 57 | *.zip 58 | *.tar.gz 59 | *.rar 60 | 61 | # virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml 62 | hs_err_pid* 63 | ### JetBrains template 64 | .idea/ 65 | *.iml 66 | .idea_modules/ 67 | 68 | # IntelliJ 69 | out/ 70 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala-2.11/just/semver/Compat.scala: -------------------------------------------------------------------------------- 1 | package just.semver 2 | 3 | import scala.util.{Try, Success, Failure} 4 | 5 | trait Compat { 6 | 7 | @SuppressWarnings(Array("org.wartremover.warts.ImplicitConversion")) 8 | implicit def eitherCompat[A, B](either: Either[A, B]): Compat.EitherCompat[A, B] = new Compat.EitherCompat(either) 9 | @SuppressWarnings(Array("org.wartremover.warts.ImplicitConversion")) 10 | implicit def tryCompat[A](t: Try[A]): Compat.TryCompat[A] = new Compat.TryCompat(t) 11 | } 12 | 13 | object Compat { 14 | final class EitherCompat[A, B](private val either: Either[A, B]) extends AnyVal { 15 | 16 | @inline def map[C](f: B => C): Either[A, C] = 17 | either.right.map(f) 18 | 19 | @inline def flatMap[C >: A, D](f: B => Either[C, D]): Either[C, D] = 20 | either.right.flatMap(f) 21 | 22 | def filterOrElse[A1 >: A](p: B => Boolean, zero: => A1): Either[A1, B] = either match { 23 | case Right(b) if !p(b) => Left(zero) 24 | case _ => either 25 | } 26 | } 27 | 28 | final class TryCompat[A](private val t: Try[A]) extends AnyVal { 29 | def toEither: Either[Throwable, A] = t match { 30 | case Success(value) => Right(value) 31 | case Failure(err) => Left(err) 32 | } 33 | } 34 | } 35 | -------------------------------------------------------------------------------- /changelogs/0.5.0.md: -------------------------------------------------------------------------------- 1 | ## [0.5.0](https://github.com/Kevin-Lee/just-semver/issues?utf8=%E2%9C%93&q=is%3Aissue+is%3Aclosed+milestone%3Amilestone7) - 2022-06-11 2 | 3 | ### Done 4 | * Add decimal version `DecVer` (`major.minor`) (#140) 5 | ```scala 6 | import just.decver.DecVer 7 | 8 | val decVer1 = DecVer.parse("1.17") 9 | // Either[DecVer.ParseError, DecVer] = Right(DecVer(1,17)) 10 | 11 | decVer1.map(_.render) 12 | // Either[DecVer.ParseError, String] = Right(1.17) 13 | 14 | val decVer2 = DecVer.unsafeParse("1.17") 15 | // DecVer = DecVer(1,17) 16 | 17 | decVer2.render 18 | // String = 1.17 19 | 20 | val semVer = decVer2.toSemVer 21 | // just.semver.SemVer = SemVer(1,17,0,None,None) 22 | 23 | semVer.toDecVer 24 | // just.decver.DecVer = DecVer(1,17) 25 | 26 | DecVer.unsafeParse("1.16") < DecVer.unsafeParse("1.17") 27 | // Boolean = true 28 | 29 | DecVer.unsafeParse("1.16") == DecVer.unsafeParse("1.17") 30 | // Boolean = false 31 | 32 | DecVer.unsafeParse("1.16") > DecVer.unsafeParse("1.17") 33 | // Boolean = false 34 | 35 | val decVer = DecVer.unsafeParse("1.0") 36 | // DecVer = DecVer(1,0) 37 | 38 | decVer.increaseMinor 39 | // DecVer = DecVer(1,1) 40 | 41 | decVer.increaseMajor 42 | // DecVer = DecVer(2,0) 43 | ``` 44 | * Set up WartRemover for Scala 3 (#138) -------------------------------------------------------------------------------- /website/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "website", 3 | "version": "0.0.0", 4 | "private": true, 5 | "scripts": { 6 | "docusaurus": "docusaurus", 7 | "start": "docusaurus start", 8 | "build": "docusaurus build", 9 | "swizzle": "docusaurus swizzle", 10 | "deploy": "docusaurus deploy", 11 | "clear": "docusaurus clear", 12 | "serve": "docusaurus serve", 13 | "write-translations": "docusaurus write-translations", 14 | "write-heading-ids": "docusaurus write-heading-ids", 15 | "typecheck": "tsc" 16 | }, 17 | "dependencies": { 18 | "@docusaurus/core": "3.8.1", 19 | "@docusaurus/preset-classic": "3.8.1", 20 | "@mdx-js/react": "^3.0.0", 21 | "clsx": "^2.0.0", 22 | "prism-react-renderer": "^2.3.0", 23 | "react": "^19.0.0", 24 | "react-dom": "^19.0.0" 25 | }, 26 | "devDependencies": { 27 | "@docusaurus/module-type-aliases": "3.8.1", 28 | "@docusaurus/tsconfig": "3.8.1", 29 | "@docusaurus/types": "3.8.1", 30 | "typescript": "~5.6.2" 31 | }, 32 | "browserslist": { 33 | "production": [ 34 | ">0.5%", 35 | "not dead", 36 | "not op_mini all" 37 | ], 38 | "development": [ 39 | "last 3 chrome version", 40 | "last 3 firefox version", 41 | "last 5 safari version" 42 | ] 43 | }, 44 | "engines": { 45 | "node": ">=18.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /.github/workflows/sbt-build-all-with-coverage.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | set -x 4 | 5 | if [ -z "$1" ] 6 | then 7 | echo "Missing parameters. Please enter the [Scala version]." 8 | echo "sbt-build-all.sh 2.13.6" 9 | exit 1 10 | else 11 | : ${CURRENT_BRANCH_NAME:?"CURRENT_BRANCH_NAME is missing."} 12 | 13 | scala_version=$1 14 | sbt_params="$2" 15 | echo "============================================" 16 | echo "Build projects" 17 | echo "--------------------------------------------" 18 | echo "" 19 | export SOURCE_DATE_EPOCH=$(date +%s) 20 | echo "SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH" 21 | 22 | test_task="coverage test coverageReport coverageAggregate" 23 | 24 | if [[ "$CURRENT_BRANCH_NAME" == "main" || "$CURRENT_BRANCH_NAME" == "release" ]] 25 | then 26 | sbt \ 27 | -J-XX:MaxMetaspaceSize=1024m \ 28 | -J-Xmx2048m \ 29 | ++${scala_version}! \ 30 | -v "${sbt_params}" \ 31 | clean \ 32 | ${test_task} \ 33 | packagedArtifacts 34 | else 35 | sbt \ 36 | -J-XX:MaxMetaspaceSize=1024m \ 37 | -J-Xmx2048m \ 38 | ++${scala_version}! \ 39 | -v "${sbt_params}" \ 40 | clean \ 41 | ${test_task} \ 42 | package 43 | fi 44 | 45 | 46 | echo "============================================" 47 | echo "Building projects: Done" 48 | echo "============================================" 49 | fi 50 | -------------------------------------------------------------------------------- /website/src/pages/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import Link from '@docusaurus/Link'; 4 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 5 | import Layout from '@theme/Layout'; 6 | import HomepageFeatures from '@site/src/components/HomepageFeatures'; 7 | 8 | import styles from './index.module.css'; 9 | 10 | function HomepageHeader() { 11 | const {siteConfig} = useDocusaurusContext(); 12 | return ( 13 |
14 |
15 | Project Logo 16 |

{siteConfig.title}

17 |

{siteConfig.tagline}

18 |
19 | 22 | Get Started 23 | 24 |
25 |
26 |
27 | ); 28 | } 29 | 30 | export default function Home(): JSX.Element { 31 | const {siteConfig} = useDocusaurusContext(); 32 | return ( 33 | 36 | 37 |
38 | 39 |
40 |
41 | ); 42 | } 43 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/test/scala/just/semver/parser/ParserSpec.scala: -------------------------------------------------------------------------------- 1 | package just.semver.parser 2 | 3 | import just.semver.{Compat, SemVer, Gens => SemVerGens} 4 | import hedgehog._ 5 | import hedgehog.runner._ 6 | import just.semver.SemVer._ 7 | import just.semver.expr.ComparisonOperator 8 | import just.semver.matcher.Gens 9 | 10 | /** @author Kevin Lee 11 | * @since 2022-04-06 12 | */ 13 | object ParserSpec extends Properties with Compat { 14 | override def tests: List[Test] = List( 15 | property("test", testParser) 16 | ) 17 | 18 | @SuppressWarnings(Array("org.wartremover.warts.ToString")) 19 | def testParser: Property = for { 20 | op <- Gens.genComparisonOperator.log("op") 21 | v <- SemVerGens.genSemVer.log("v") 22 | } yield { 23 | Parser 24 | .charsIn(">!=<") 25 | .parse( 26 | s"${op.render}${v.render}" 27 | ) 28 | .left 29 | .map(_.render) 30 | .flatMap { 31 | case (rest, op) => 32 | ComparisonOperator 33 | .parse(op) 34 | .flatMap { operator => 35 | SemVer 36 | .parse(rest) 37 | .map { version => 38 | (operator, version) 39 | } 40 | .left 41 | .map(_.render) 42 | } 43 | } match { 44 | case Right((actualOp, actualV)) => 45 | actualOp ==== op and actualV ==== v 46 | 47 | case Left(err) => 48 | Result.failure.log(s"Parse failed with error: $err") 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala-3/just/Common.scala: -------------------------------------------------------------------------------- 1 | package just 2 | 3 | import scala.annotation.tailrec 4 | 5 | /** @author 6 | * Kevin Lee 7 | * @since 8 | * 2018-12-15 9 | */ 10 | object Common { 11 | // $COVERAGE-OFF$ 12 | 13 | extension [A](a1: A)(using CanEqual[A, A]) { 14 | @SuppressWarnings(Array("org.wartremover.warts.Equals")) 15 | inline def ===(a2: A): Boolean = a1 == a2 16 | @SuppressWarnings(Array("org.wartremover.warts.Equals")) 17 | inline def !==(a2: A): Boolean = a1 != a2 18 | } 19 | 20 | inline def none[A]: Option[A] = None 21 | 22 | extension [A](a: A) { 23 | inline def some: Option[A] = Some(a) 24 | 25 | inline def asRight[B]: Either[B, A] = Right[B, A](a) 26 | inline def asLeft[B]: Either[A, B] = Left[A, B](a) 27 | } 28 | 29 | extension [A](x: Seq[A]) { 30 | def compareElems(y: Seq[A])(using ordering: Ordering[A]): Int = { 31 | @tailrec 32 | def compareElems0(x: Seq[A], y: Seq[A]): Int = 33 | (x, y) match { 34 | case (head1 +: tail1, head2 +: tail2) => 35 | val result = ordering.compare(head1, head2) 36 | if (result == 0) { 37 | compareElems0(tail1, tail2) 38 | } else { 39 | result 40 | } 41 | 42 | case (Seq(), _ +: _) => 43 | -1 44 | case (_ +: _, Seq()) => 45 | 1 46 | case (Seq(), Seq()) => 47 | 0 48 | } 49 | compareElems0(x, y) 50 | } 51 | } 52 | // $COVERAGE-ON$ 53 | 54 | } 55 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala-2/just/Common.scala: -------------------------------------------------------------------------------- 1 | package just 2 | 3 | import scala.annotation.tailrec 4 | 5 | /** @author 6 | * Kevin Lee 7 | * @since 8 | * 2018-12-15 9 | */ 10 | object Common { 11 | // $COVERAGE-OFF$ 12 | 13 | @SuppressWarnings(Array("org.wartremover.warts.Equals")) 14 | implicit final class EqualA[A](private val a1: A) extends AnyVal { 15 | @inline def ===(a2: A): Boolean = a1 == a2 16 | @inline def !==(a2: A): Boolean = a1 != a2 17 | } 18 | 19 | @inline def none[A]: Option[A] = None 20 | 21 | implicit final class Ops[A](private val a: A) extends AnyVal { 22 | @inline def some: Option[A] = Some(a) 23 | 24 | @inline def asRight[B]: Either[B, A] = Right[B, A](a) 25 | @inline def asLeft[B]: Either[A, B] = Left[A, B](a) 26 | } 27 | 28 | implicit final class SeqPlus[A](private val x: Seq[A]) extends AnyVal { 29 | def compareElems(y: Seq[A])(implicit ordering: Ordering[A]): Int = 30 | Common.compareElems(x, y) 31 | } 32 | 33 | @tailrec 34 | private def compareElems[A: Ordering](x: Seq[A], y: Seq[A]): Int = { 35 | val ordering = implicitly[Ordering[A]] 36 | (x, y) match { 37 | case (head1 +: tail1, head2 +: tail2) => 38 | val result = ordering.compare(head1, head2) 39 | if (result === 0) { 40 | compareElems(tail1, tail2) 41 | } else { 42 | result 43 | } 44 | 45 | case (Seq(), _ +: _) => 46 | -1 47 | case (_ +: _, Seq()) => 48 | 1 49 | case (Seq(), Seq()) | (_, _) => 50 | 0 51 | } 52 | } 53 | // $COVERAGE-ON$ 54 | } 55 | -------------------------------------------------------------------------------- /.scalafmt.conf: -------------------------------------------------------------------------------- 1 | version = 3.9.8 2 | runner.dialect = scala3 3 | 4 | //align.preset=more 5 | align.arrowEnumeratorGenerator=true 6 | align.tokens = [ 7 | { 8 | code = "%" 9 | owners = [{ 10 | regex = "Term.ApplyInfix" 11 | }] 12 | }, { 13 | code = "%%" 14 | owners = [{ 15 | regex = "Term.ApplyInfix" 16 | }] 17 | // }, { 18 | // code = ":=" 19 | // owners = [{ 20 | // regex = "Term.ApplyInfix", 21 | // }] 22 | }, { 23 | code = "->" 24 | owners = [{ 25 | regex = "Term.ApplyInfix", 26 | }] 27 | }, { 28 | code = "=" 29 | owners = [{ 30 | regex = "(Enumerator.Val|Defn.(Va(l|r)|GivenAlias|Def|Type))" 31 | }] 32 | }, { 33 | code = "<-" 34 | owners = [{ 35 | regex = "Enumerator.Generator" 36 | }] 37 | }, 38 | ] 39 | 40 | maxColumn=120 41 | assumeStandardLibraryStripMargin=false 42 | 43 | continuationIndent.callSite=2 44 | continuationIndent.defnSite=2 45 | 46 | align.stripMargin=true 47 | align.multiline=true 48 | align.openParenCallSite=false 49 | align.openParenDefnSite=false 50 | 51 | includeCurlyBraceInSelectChains=true 52 | includeNoParensInSelectChains=true 53 | 54 | spaces.beforeContextBoundColon=Never 55 | 56 | //newlines.beforeMultiline=unfold 57 | newlines.beforeMultilineDef=keep 58 | newlines.alwaysBeforeElseAfterCurlyIf=false 59 | newlines.beforeCurlyLambdaParams=multilineWithCaseOnly 60 | newlines.afterCurlyLambdaParams=never 61 | newlines.implicitParamListModifierPrefer = before 62 | 63 | trailingCommas=keep 64 | 65 | docstrings.style = SpaceAsterisk 66 | docstrings.oneline = keep 67 | docstrings.wrap = false 68 | -------------------------------------------------------------------------------- /.github/workflows/sbt-build-all.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | set -x 4 | 5 | if [ -z "$1" ] 6 | then 7 | echo "Missing parameters. Please enter the [Scala version]." 8 | echo "sbt-build-all.sh 2.13.6" 9 | exit 1 10 | else 11 | : ${CURRENT_BRANCH_NAME:?"CURRENT_BRANCH_NAME is missing."} 12 | 13 | scala_version=$1 14 | sbt_params="$2" 15 | echo "============================================" 16 | echo "Build projects" 17 | echo "--------------------------------------------" 18 | echo "" 19 | export SOURCE_DATE_EPOCH=$(date +%s) 20 | echo "SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH" 21 | 22 | if [[ "$CURRENT_BRANCH_NAME" == "main" || "$CURRENT_BRANCH_NAME" == "release" ]] 23 | then 24 | # sbt -J-Xmx2048m ++${scala_version}! -v clean; coverage; test; coverageReport; coverageAggregate 25 | # sbt -J-Xmx2048m ++${scala_version}! -v coveralls 26 | # sbt -J-Xmx2048m ++${scala_version}! -v clean; packagedArtifacts 27 | sbt \ 28 | -J-XX:MaxMetaspaceSize=1024m \ 29 | -J-Xmx2048m \ 30 | ++${scala_version}! \ 31 | -v "${sbt_params}" \ 32 | clean \ 33 | test \ 34 | packagedArtifacts 35 | else 36 | # sbt -J-Xmx2048m ++${scala_version}! -v clean coverage test coverageReport coverageAggregate package 37 | # sbt -J-Xmx2048m ++${scala_version}! -v coveralls 38 | sbt \ 39 | -J-XX:MaxMetaspaceSize=1024m \ 40 | -J-Xmx2048m \ 41 | ++${scala_version}! \ 42 | -v "${sbt_params}" \ 43 | clean \ 44 | test \ 45 | package 46 | fi 47 | 48 | 49 | echo "============================================" 50 | echo "Building projects: Done" 51 | echo "============================================" 52 | fi 53 | -------------------------------------------------------------------------------- /.github/workflows/sbt-build.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash -e 2 | 3 | set -x 4 | 5 | if [ -z "$1" ] 6 | then 7 | echo "Scala version is missing. Please enter the Scala version." 8 | echo "sbt-build.sh 2.13.6" 9 | exit 1 10 | else 11 | scala_version=$1 12 | echo "============================================" 13 | echo "Build projects" 14 | echo "--------------------------------------------" 15 | echo "" 16 | : ${CURRENT_BRANCH_NAME:?"CURRENT_BRANCH_NAME is missing."} 17 | : ${CI_BRANCH:?"CI_BRANCH is missing."} 18 | export SOURCE_DATE_EPOCH=$(date +%s) 19 | echo "SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH" 20 | 21 | if [[ "$CURRENT_BRANCH_NAME" == "main" || "$CURRENT_BRANCH_NAME" == "release" ]] 22 | then 23 | sbt \ 24 | -J-XX:MaxMetaspaceSize=1024m \ 25 | -J-Xmx2048m \ 26 | ++${scala_version}! \ 27 | clean \ 28 | coverage \ 29 | test \ 30 | coverageReport \ 31 | coverageAggregate 32 | sbt \ 33 | -J-XX:MaxMetaspaceSize=1024m \ 34 | -J-Xmx2048m \ 35 | ++${scala_version}! 36 | sbt \ 37 | -J-XX:MaxMetaspaceSize=1024m \ 38 | -J-Xmx2048m \ 39 | ++${scala_version}! \ 40 | clean \ 41 | packagedArtifacts 42 | else 43 | sbt \ 44 | -J-XX:MaxMetaspaceSize=1024m \ 45 | -J-Xmx2048m \ 46 | ++${scala_version}! \ 47 | clean \ 48 | coverage \ 49 | test \ 50 | coverageReport \ 51 | coverageAggregate \ 52 | package 53 | sbt \ 54 | -J-XX:MaxMetaspaceSize=1024m \ 55 | -J-Xmx2048m \ 56 | ++${scala_version}! 57 | fi 58 | 59 | echo "============================================" 60 | echo "Building projects: Done" 61 | echo "============================================" 62 | fi 63 | -------------------------------------------------------------------------------- /website/src/css/custom.css: -------------------------------------------------------------------------------- 1 | /** 2 | * Any CSS included here will be global. The classic template 3 | * bundles Infima by default. Infima is a CSS framework designed to 4 | * work well for content-centric websites. 5 | */ 6 | 7 | /* You can override the default Infima variables here. */ 8 | 9 | @import url('https://content-fonts.kevinly.dev/nerd-fonts/font-fira-code-nerd-font/fonts.css'); 10 | 11 | @import url('https://content-fonts.kevinly.dev/nerd-fonts/font-caskaydia-cove-nerd-font/fonts.css'); 12 | 13 | :root { 14 | 15 | --ifm-color-primary: #294b89; 16 | --ifm-color-primary-dark: #25447b; 17 | --ifm-color-primary-darker: #234074; 18 | --ifm-color-primary-darkest: #1d3560; 19 | --ifm-color-primary-light: #2d5397; 20 | --ifm-color-primary-lighter: #2f569e; 21 | --ifm-color-primary-lightest: #3562b2; 22 | 23 | --ifm-code-font-size: 95%; 24 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.1); 25 | 26 | --ifm-code-font-size: 95%; 27 | --ifm-font-family-monospace: "FiraCode Retina Nerd Font"; 28 | 29 | --ifm-container-width-xl: 1920px; 30 | 31 | } 32 | 33 | .docusaurus-highlight-code-line { 34 | background-color: rgba(0, 0, 0, 0.1); 35 | display: block; 36 | margin: 0 calc(-1 * var(--ifm-pre-padding)); 37 | padding: 0 var(--ifm-pre-padding); 38 | } 39 | 40 | /* For readability concerns, you should choose a lighter palette in dark mode. */ 41 | [data-theme='dark'] { 42 | --ifm-color-primary: #315daf; 43 | --ifm-color-primary-dark: #2c549e; 44 | --ifm-color-primary-darker: #2a4f95; 45 | --ifm-color-primary-darkest: #22417a; 46 | --ifm-color-primary-light: #3666c1; 47 | --ifm-color-primary-lighter: #3a6bc8; 48 | --ifm-color-primary-lightest: #547fcf; 49 | 50 | --docusaurus-highlighted-code-line-bg: rgba(0, 0, 0, 0.3); 51 | } 52 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala-3/just/semver/Anh.scala: -------------------------------------------------------------------------------- 1 | package just.semver 2 | 3 | /** Alphabet / Number / Hyphen (Anh) 4 | * 5 | * @author 6 | * Kevin Lee 7 | * @since 8 | * 2018-10-21 9 | */ 10 | enum Anh extends Ordered[Anh] derives CanEqual { 11 | 12 | case Alphabet(value: String) 13 | case Num(value: String) 14 | case Hyphen 15 | 16 | import Anh._ 17 | 18 | override def compare(that: Anh): Int = 19 | (this, that) match { 20 | case (Num(thisValue), Num(thatValue)) => 21 | Ordering[Int].compare(thisValue.toInt, thatValue.toInt) 22 | 23 | case (Num(_), Alphabet(_)) => 24 | -1 25 | 26 | case (Num(_), Hyphen) => 27 | -1 28 | 29 | case (Alphabet(_), Num(_)) => 30 | 1 31 | 32 | case (Alphabet(thisValue), Alphabet(thatValue)) => 33 | thisValue.compareTo(thatValue) 34 | 35 | case (Alphabet(_), Hyphen) => 36 | 1 37 | 38 | case (Hyphen, Num(_)) => 39 | 1 40 | 41 | case (Hyphen, Alphabet(_)) => 42 | -1 43 | 44 | case (Hyphen, Hyphen) => 45 | 0 46 | } 47 | } 48 | 49 | object Anh { 50 | 51 | def alphabet(value: String): Anh = 52 | Anh.Alphabet(value) 53 | 54 | def num(value: Int): Anh = 55 | Anh.Num(value.toString) 56 | 57 | def numFromStringUnsafe(value: String): Anh = 58 | if (value.forall(_.isDigit)) 59 | Anh.Num(value) 60 | else 61 | sys.error(s"The Num value cannot contain any non-digit. value: $value") 62 | 63 | def hyphen: Anh = 64 | Anh.Hyphen 65 | 66 | extension (anh: Anh) { 67 | def render: String = anh match { 68 | case Num(value) => 69 | value 70 | case Alphabet(value) => 71 | value 72 | case Hyphen => 73 | "-" 74 | } 75 | } 76 | 77 | } 78 | -------------------------------------------------------------------------------- /changelogs/1.0.0.md: -------------------------------------------------------------------------------- 1 | ## [1.0.0](https://github.com/Kevin-Lee/just-semver/issues?q=is%3Aissue+is%3Aclosed+milestone%3Amilestone14) - 2024-08-26 2 | 3 | ## New Feature 4 | 5 | 6 | * Add `just-semver-decver` module (#221) 7 | 8 | *** 9 | * [`just-semver-decver`] Add `DecVerExt` which is `DecVer` with pre-release and build metadata (#223) 10 | 11 | *** 12 | * Add `DecVerExtMatcher`, the matcher for `DecVerExt` (#225) 13 | 14 | ```scala 15 | DecVerExtMatchers.unsafeParse("1.0 - 2.0").matches(DecVerExt.unsafeParse("1.0")) // true 16 | DecVerExtMatchers.unsafeParse("1.0 - 2.0").matches(DecVerExt.unsafeParse("0.9")) // false 17 | DecVerExtMatchers.unsafeParse("1.0 - 2.0").matches(DecVerExt.unsafeParse("2.0")) // true 18 | DecVerExtMatchers.unsafeParse("1.0 - 2.0").matches(DecVerExt.unsafeParse("2.1")) // false 19 | 20 | // and more... 21 | ``` 22 | *** 23 | 24 | * Replace `DecVer` with `DecVerExt`, and rename `DecVerExt` to `DecVer` (#230) 25 | *** 26 | 27 | * Unable to parse version "25.1-jre-graal-sub-1" (#216) 28 | 29 | ```scala 30 | SemVer.parse("25.1-jre-graal-sub-1") 31 | ``` 32 | Results in 33 | ``` 34 | InvalidVersionStringError(25.1-jre-graal-sub-1) 35 | ``` 36 | This can be handled by `DecVer` now. 37 | ```scala 38 | import just.decver.* 39 | 40 | DecVer.parse("25.1-jre-graal-sub-1") 41 | // Either[DecVer.ParseError, DecVer] = Right(DecVer(Major(25),Minor(1),Some(PreRelease(List(Dsv(List(Alphabet(jre), Hyphen, Alphabet(graal), Hyphen, Alphabet(sub), Hyphen, Num(1)))))),None)) 42 | 43 | val version = DecVer.unsafeParse("25.1-jre-graal-sub-1") 44 | // DecVer = DecVer(Major(25),Minor(1),Some(PreRelease(List(Dsv(List(Alphabet(jre), Hyphen, Alphabet(graal), Hyphen, Alphabet(sub), Hyphen, Num(1)))))),None) 45 | 46 | version.render 47 | // String = 25.1-jre-graal-sub-1 48 | ``` 49 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala-2/just/semver/expr/ComparisonOperator.scala: -------------------------------------------------------------------------------- 1 | package just.semver.expr 2 | 3 | import just.Common._ 4 | 5 | /** @author Kevin Lee 6 | * @since 2022-04-03 7 | */ 8 | sealed trait ComparisonOperator 9 | object ComparisonOperator { 10 | case object Lt extends ComparisonOperator 11 | case object Le extends ComparisonOperator 12 | case object Eql extends ComparisonOperator 13 | case object Ne extends ComparisonOperator 14 | case object Gt extends ComparisonOperator 15 | case object Ge extends ComparisonOperator 16 | 17 | def lt: ComparisonOperator = Lt 18 | def le: ComparisonOperator = Le 19 | def eql: ComparisonOperator = Eql 20 | def ne: ComparisonOperator = Ne 21 | def gt: ComparisonOperator = Gt 22 | def ge: ComparisonOperator = Ge 23 | 24 | def parse(s: String): Either[String, ComparisonOperator] = s match { 25 | case "<=" => Right(le) 26 | case "<" => Right(lt) 27 | case ">=" => Right(ge) 28 | case ">" => Right(gt) 29 | case "!=" => Right(ne) 30 | case "=" => Right(eql) 31 | case _ => Left("Unknown or invalid operator") 32 | } 33 | 34 | implicit class ComparisonOperatorOps(private val operator: ComparisonOperator) extends AnyVal { 35 | 36 | def eval[A: Ordering](a1: A, a2: A): Boolean = { 37 | val v1 = Ordered.orderingToOrdered(a1) 38 | val v2 = a2 39 | operator match { 40 | case Lt => v1 < v2 41 | case Le => v1 <= v2 42 | case Eql => v1.compare(v2) === 0 43 | case Ne => v1.compare(v2) !== 0 44 | case Gt => v1 > v2 45 | case Ge => v1 >= v2 46 | } 47 | } 48 | 49 | def render: String = operator match { 50 | case Lt => "<" 51 | case Le => "<=" 52 | case Eql => "=" 53 | case Ne => "!=" 54 | case Gt => ">" 55 | case Ge => ">=" 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala-3/just/semver/expr/ComparisonOperator.scala: -------------------------------------------------------------------------------- 1 | package just.semver.expr 2 | 3 | import just.Common._ 4 | 5 | /** @author Kevin Lee 6 | * @since 2022-04-03 7 | */ 8 | enum ComparisonOperator derives CanEqual { 9 | case Lt 10 | case Le 11 | case Eql 12 | case Ne 13 | case Gt 14 | case Ge 15 | } 16 | object ComparisonOperator { 17 | def lt: ComparisonOperator = ComparisonOperator.Lt 18 | def le: ComparisonOperator = ComparisonOperator.Le 19 | def eql: ComparisonOperator = ComparisonOperator.Eql 20 | def ne: ComparisonOperator = ComparisonOperator.Ne 21 | def gt: ComparisonOperator = ComparisonOperator.Gt 22 | def ge: ComparisonOperator = ComparisonOperator.Ge 23 | 24 | def parse(s: String): Either[String, ComparisonOperator] = s match { 25 | case "<=" => Right(le) 26 | case "<" => Right(lt) 27 | case ">=" => Right(ge) 28 | case ">" => Right(gt) 29 | case "!=" => Right(ne) 30 | case "=" => Right(eql) 31 | case _ => Left("Unknown or invalid operator") 32 | } 33 | 34 | extension (operator: ComparisonOperator) { 35 | 36 | def eval[A: Ordering](a1: A, a2: A): Boolean = { 37 | val v1 = Ordered.orderingToOrdered(a1) 38 | val v2 = a2 39 | operator match { 40 | case ComparisonOperator.Lt => v1 < v2 41 | case ComparisonOperator.Le => v1 <= v2 42 | case ComparisonOperator.Eql => v1.compare(v2) === 0 43 | case ComparisonOperator.Ne => v1.compare(v2) !== 0 44 | case ComparisonOperator.Gt => v1 > v2 45 | case ComparisonOperator.Ge => v1 >= v2 46 | } 47 | } 48 | 49 | def render: String = operator match { 50 | case ComparisonOperator.Lt => "<" 51 | case ComparisonOperator.Le => "<=" 52 | case ComparisonOperator.Eql => "=" 53 | case ComparisonOperator.Ne => "!=" 54 | case ComparisonOperator.Gt => ">" 55 | case ComparisonOperator.Ge => ">=" 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala-2/just/semver/Anh.scala: -------------------------------------------------------------------------------- 1 | package just.semver 2 | 3 | /** Alphabet / Number / Hyphen (Anh) 4 | * 5 | * @author 6 | * Kevin Lee 7 | * @since 8 | * 2018-10-21 9 | */ 10 | sealed trait Anh extends Ordered[Anh] { 11 | 12 | import Anh._ 13 | 14 | override def compare(that: Anh): Int = 15 | (this, that) match { 16 | case (Num(thisValue), Num(thatValue)) => 17 | Ordering[Int].compare(thisValue.toInt, thatValue.toInt) 18 | 19 | case (Num(_), Alphabet(_)) => 20 | -1 21 | 22 | case (Num(_), Hyphen) => 23 | -1 24 | 25 | case (Alphabet(_), Num(_)) => 26 | 1 27 | 28 | case (Alphabet(thisValue), Alphabet(thatValue)) => 29 | thisValue.compareTo(thatValue) 30 | 31 | case (Alphabet(_), Hyphen) => 32 | 1 33 | 34 | case (Hyphen, Num(_)) => 35 | 1 36 | 37 | case (Hyphen, Alphabet(_)) => 38 | -1 39 | 40 | case (Hyphen, Hyphen) => 41 | 0 42 | } 43 | } 44 | 45 | object Anh { 46 | 47 | final case class Alphabet(value: String) extends Anh 48 | final case class Num(value: String) extends Anh 49 | case object Hyphen extends Anh 50 | 51 | def alphabet(value: String): Anh = 52 | Alphabet(value) 53 | 54 | def num(value: Int): Anh = 55 | Num(value.toString) 56 | 57 | def numFromStringUnsafe(value: String): Anh = 58 | if (value.forall(_.isDigit)) 59 | Num(value) 60 | else 61 | sys.error(s"The Num value cannot contain any non-digit. value: $value") 62 | 63 | def hyphen: Anh = 64 | Hyphen 65 | 66 | implicit final class AnhOps(private val anh: Anh) extends AnyVal { 67 | @inline def render: String = Anh.render(anh) 68 | } 69 | 70 | def render(alphaNumHyphen: Anh): String = alphaNumHyphen match { 71 | case Num(value) => 72 | value 73 | case Alphabet(value) => 74 | value 75 | case Hyphen => 76 | "-" 77 | } 78 | 79 | } 80 | -------------------------------------------------------------------------------- /modules/just-semver-decver/shared/src/main/scala-3/just/decver/matcher/DecVerComparison.scala: -------------------------------------------------------------------------------- 1 | package just.decver.matcher 2 | 3 | import just.semver.expr.ComparisonOperator 4 | import just.semver.parser.Parser 5 | import just.semver.Compat 6 | import just.decver.DecVer 7 | 8 | /** @author Kevin Lee 9 | * @since 2022-04-05 10 | */ 11 | final case class DecVerComparison(comparisonOperator: ComparisonOperator, decVer: DecVer) 12 | object DecVerComparison extends Compat { 13 | 14 | def parse(s: String): Either[ParseError, DecVerComparison] = 15 | Parser 16 | .charsIn(">!=<") 17 | .parse(s) 18 | .left 19 | .map(err => DecVerComparison.ParseError(s"Failed to parse operator from $s", err.render, None)) 20 | .flatMap { 21 | case (rest, op) => 22 | ComparisonOperator 23 | .parse(op) 24 | .left 25 | .map(err => DecVerComparison.ParseError(s"Failed to parse operator from $s", err, None)) 26 | .flatMap { operator => 27 | DecVer 28 | .parse(rest) 29 | .map(version => DecVerComparison(operator, version)) 30 | .left 31 | .map(err => 32 | DecVerComparison.ParseError( 33 | s"Parsing operator succeeded but failed to parse DecVer from $s", 34 | err.render, 35 | Some(op) 36 | ) 37 | ) 38 | } 39 | } 40 | 41 | def unsafeParse(s: String): DecVerComparison = 42 | parse(s).fold(err => sys.error(err.render), identity) 43 | 44 | extension (decVerComparison: DecVerComparison) { 45 | def render: String = decVerComparison match { 46 | case DecVerComparison(op, v) => s"${op.render}${v.render}" 47 | } 48 | } 49 | 50 | final case class ParseError(message: String, error: String, success: Option[String]) 51 | object ParseError { 52 | 53 | extension (decVerComparisonParseError: DecVerComparison.ParseError) { 54 | def render: String = decVerComparisonParseError match { 55 | case ParseError(message, err, success) => 56 | s"DecVerComparison.ParseError($message: ParserError($err)${success 57 | .fold(", Success:")(succ => s", Success: $succ")})" 58 | } 59 | } 60 | } 61 | 62 | } 63 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala-3/just/semver/matcher/SemVerComparison.scala: -------------------------------------------------------------------------------- 1 | package just.semver.matcher 2 | 3 | import just.semver.expr.ComparisonOperator 4 | import just.semver.parser.Parser 5 | import just.semver.{Compat, SemVer} 6 | 7 | /** @author Kevin Lee 8 | * @since 2022-04-05 9 | */ 10 | final case class SemVerComparison(comparisonOperator: ComparisonOperator, semVer: SemVer) derives CanEqual 11 | object SemVerComparison extends Compat { 12 | 13 | def parse(s: String): Either[ParseError, SemVerComparison] = 14 | Parser 15 | .charsIn(">!=<") 16 | .parse(s) 17 | .left 18 | .map(err => SemVerComparison.ParseError(s"Failed to parse operator from $s", err.render, None)) 19 | .flatMap { 20 | case (rest, op) => 21 | ComparisonOperator 22 | .parse(op) 23 | .left 24 | .map(err => SemVerComparison.ParseError(s"Failed to parse operator from $s", err, None)) 25 | .flatMap { operator => 26 | SemVer 27 | .parse(rest) 28 | .map(version => SemVerComparison(operator, version)) 29 | .left 30 | .map(err => 31 | SemVerComparison.ParseError( 32 | s"Parsing operator succeeded but failed to parse SemVer from $s", 33 | err.render, 34 | Some(op) 35 | ) 36 | ) 37 | } 38 | } 39 | 40 | def unsafeParse(s: String): SemVerComparison = 41 | parse(s).fold(err => sys.error(err.render), identity) 42 | 43 | extension (semVerComparison: SemVerComparison) { 44 | def render: String = semVerComparison match { 45 | case SemVerComparison(op, v) => s"${op.render}${v.render}" 46 | } 47 | } 48 | 49 | final case class ParseError(message: String, error: String, success: Option[String]) derives CanEqual 50 | object ParseError { 51 | 52 | extension (semVerComparisonParseError: SemVerComparison.ParseError) { 53 | def render: String = semVerComparisonParseError match { 54 | case ParseError(message, err, success) => 55 | s"SemVerComparison.ParseError($message: ParserError($err)" + 56 | s"${success.fold(", Success:")(succ => s", Success: $succ")})" 57 | } 58 | } 59 | } 60 | 61 | } 62 | -------------------------------------------------------------------------------- /modules/just-semver-decver/shared/src/main/scala-3/just/decver/matcher/DecVerMatcher.scala: -------------------------------------------------------------------------------- 1 | package just.decver.matcher 2 | 3 | import just.decver.DecVer 4 | 5 | /** @author Kevin Lee 6 | * @since 2022-04-03 7 | */ 8 | enum DecVerMatcher { 9 | case Range(from: DecVer, to: DecVer) 10 | case Comparison(decVerComparison: DecVerComparison) 11 | } 12 | object DecVerMatcher { 13 | 14 | def range(from: DecVer, to: DecVer): DecVerMatcher = DecVerMatcher.Range(from, to) 15 | 16 | def comparison(decVerComparison: DecVerComparison): DecVerMatcher = 17 | DecVerMatcher.Comparison(decVerComparison) 18 | 19 | extension (decVerMatcher: DecVerMatcher) { 20 | 21 | def matches(decVer: DecVer): Boolean = decVerMatcher match { 22 | case DecVerMatcher.Range(from, to) => 23 | decVer >= from && decVer <= to 24 | 25 | case DecVerMatcher.Comparison(DecVerComparison(op, v)) => 26 | op.eval(decVer, v) 27 | } 28 | 29 | def render: String = decVerMatcher match { 30 | case DecVerMatcher.Range(v1, v2) => 31 | s"${v1.render} - ${v2.render}" 32 | 33 | case DecVerMatcher.Comparison(svc) => 34 | svc.render 35 | } 36 | } 37 | 38 | enum ParseError { 39 | case RangeParseFailure(message: String, parseErrors: List[String], success: Option[DecVer]) 40 | case DecVerComparisonParseFailure(decVerComparisonParseError: DecVerComparison.ParseError) 41 | } 42 | object ParseError { 43 | 44 | def rangeParseFailure( 45 | message: String, 46 | parseErrors: List[String], 47 | success: Option[DecVer] 48 | ): ParseError = 49 | ParseError.RangeParseFailure(message, parseErrors, success) 50 | 51 | def decVerComparisonParseFailure(decVerComparisonParseError: DecVerComparison.ParseError): ParseError = 52 | ParseError.DecVerComparisonParseFailure(decVerComparisonParseError) 53 | 54 | extension (parseError: ParseError) { 55 | def render: String = parseError match { 56 | case ParseError.RangeParseFailure(message, errors, success) => 57 | s"DecVerMatcher.ParseError($message: Errors: [${errors.mkString(", ")}]" + 58 | s"${success.fold(", Success:")(succ => s", Success: ${succ.render}")})" 59 | 60 | case ParseError.DecVerComparisonParseFailure(err) => 61 | err.render 62 | } 63 | } 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala-3/just/semver/matcher/SemVerMatcher.scala: -------------------------------------------------------------------------------- 1 | package just.semver.matcher 2 | 3 | import just.semver.SemVer 4 | 5 | /** @author Kevin Lee 6 | * @since 2022-04-03 7 | */ 8 | enum SemVerMatcher derives CanEqual { 9 | case Range(from: SemVer, to: SemVer) 10 | case Comparison(semVerComparison: SemVerComparison) 11 | } 12 | 13 | object SemVerMatcher { 14 | 15 | def range(from: SemVer, to: SemVer): SemVerMatcher = SemVerMatcher.Range(from, to) 16 | 17 | def comparison(semVerComparison: SemVerComparison): SemVerMatcher = SemVerMatcher.Comparison(semVerComparison) 18 | 19 | extension (semVerMatcher: SemVerMatcher) { 20 | 21 | def matches(semVer: SemVer): Boolean = semVerMatcher match { 22 | case SemVerMatcher.Range(from, to) => 23 | semVer >= from && semVer <= to 24 | case SemVerMatcher.Comparison(SemVerComparison(op, v)) => 25 | op.eval(semVer, v) 26 | } 27 | 28 | def render: String = semVerMatcher match { 29 | case SemVerMatcher.Range(v1, v2) => 30 | s"${v1.render} - ${v2.render}" 31 | case SemVerMatcher.Comparison(svc) => 32 | svc.render 33 | } 34 | } 35 | 36 | enum ParseError derives CanEqual { 37 | case RangeParseFailure(message: String, parseErrors: List[String], success: Option[SemVer]) 38 | case SemVerComparisonParseFailure(semVerComparisonParseError: SemVerComparison.ParseError) 39 | } 40 | object ParseError { 41 | 42 | def rangeParseFailure( 43 | message: String, 44 | parseErrors: List[String], 45 | success: Option[SemVer] 46 | ): ParseError = 47 | ParseError.RangeParseFailure(message, parseErrors, success) 48 | 49 | def semVerComparisonParseFailure(semVerComparisonParseError: SemVerComparison.ParseError): ParseError = 50 | ParseError.SemVerComparisonParseFailure(semVerComparisonParseError) 51 | 52 | extension (parseError: ParseError) { 53 | def render: String = parseError match { 54 | case ParseError.RangeParseFailure(message, errors, success) => 55 | s"SemVerMatcher.ParseError($message: Errors: [${errors.mkString(", ")}]" + 56 | s"${success.fold(", Success:")(succ => s", Success: ${succ.render}")})" 57 | 58 | case ParseError.SemVerComparisonParseFailure(err) => 59 | err.render 60 | } 61 | } 62 | } 63 | } 64 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/test/scala/just/semver/parser/ComparisonOperatorParserSpec.scala: -------------------------------------------------------------------------------- 1 | package just.semver.parser 2 | 3 | import hedgehog._ 4 | import hedgehog.runner._ 5 | import just.semver.matcher.Gens 6 | 7 | /** @author Kevin Lee 8 | * @since 2022-04-03 9 | */ 10 | object ComparisonOperatorParserSpec extends Properties { 11 | override def tests: List[Test] = List( 12 | property("test ComparisonOperatorParser.parse(valid)", testComparisonOperatorParserValidCase), 13 | property("test ComparisonOperatorParser.parse(invalid)", testComparisonOperatorParserInvalidCase), 14 | property( 15 | "test ComparisonOperatorParser.parse(invalid with only valid chars)", 16 | testComparisonOperatorParserInvalidWithValidCharsCase 17 | ) 18 | ) 19 | 20 | def testComparisonOperatorParserValidCase: Property = for { 21 | comparisonOperator <- Gens.genComparisonOperator.log("comparisonOperator") 22 | 23 | rest <- Gen.string(Gen.alphaNum, Range.linear(1, 10)).log("rest") 24 | } yield { 25 | val input = s"${comparisonOperator.render}$rest" 26 | 27 | ComparisonOperatorParser.parse(input) ==== Right((comparisonOperator, rest)) 28 | } 29 | 30 | def testComparisonOperatorParserInvalidCase: Property = for { 31 | rest <- Gen.string(Gen.alphaNum, Range.linear(1, 10)).log("rest") 32 | } yield { 33 | val input = rest 34 | 35 | ComparisonOperatorParser.parse(input) ==== Left(ParserError(s"Error at 0", None, Some(rest))) 36 | } 37 | 38 | def testComparisonOperatorParserInvalidWithValidCharsCase: Property = for { 39 | comparisonOperator1 <- Gens.genComparisonOperator.log("comparisonOperator1") 40 | comparisonOperator2 <- Gens.genComparisonOperator.log("comparisonOperator2") 41 | comparisonOperator3 <- Gens.genComparisonOperator.log("comparisonOperator3") 42 | 43 | rest <- Gen.string(Gen.alphaNum, Range.linear(1, 10)).log("rest") 44 | } yield { 45 | val input = s"${comparisonOperator1.render}${comparisonOperator2.render}${comparisonOperator3.render}$rest" 46 | 47 | ComparisonOperatorParser.parse(input) ==== Left( 48 | ParserError( 49 | s"Parsed successfully but failed to create ComparisonOperator: Error: Unknown or invalid operator", 50 | Some(s"${comparisonOperator1.render}${comparisonOperator2.render}${comparisonOperator3.render}"), 51 | Some(rest) 52 | ) 53 | ) 54 | } 55 | } 56 | -------------------------------------------------------------------------------- /.github/workflows/doc-site-build-only.yml: -------------------------------------------------------------------------------- 1 | name: "[Doc][A] Build Only" 2 | 3 | on: 4 | push: 5 | branches: 6 | - "**" 7 | 8 | jobs: 9 | build-docusaurus: 10 | 11 | runs-on: ubuntu-latest 12 | 13 | strategy: 14 | matrix: 15 | scala: 16 | - { version: "2.13.16", binary-version: "2.13", java-version: "11", java-distribution: "temurin" } 17 | node: 18 | - { version: "22.17.0" } 19 | 20 | steps: 21 | - uses: actions/checkout@v6 22 | - uses: actions/setup-java@v5 23 | with: 24 | java-version: ${{ matrix.scala.java-version }} 25 | distribution: ${{ matrix.scala.java-distribution }} 26 | cache: 'sbt' 27 | - uses: sbt/setup-sbt@v1 28 | - uses: actions/setup-node@v6 29 | with: 30 | node-version: ${{ matrix.node.version }} 31 | registry-url: 'https://registry.npmjs.org' 32 | 33 | - name: Cache SBT 34 | uses: actions/cache@v5 35 | with: 36 | path: | 37 | ~/.ivy2/cache 38 | ~/.cache/coursier 39 | ~/.sbt 40 | key: ${{ runner.os }}-sbt-${{ matrix.scala.binary-version }}-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('**/build.properties') }} 41 | restore-keys: | 42 | ${{ runner.os }}-sbt-${{ matrix.scala.binary-version }}- 43 | 44 | - name: Cache npm 45 | uses: actions/cache@v5 46 | with: 47 | path: ~/.npm 48 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 49 | restore-keys: | 50 | ${{ runner.os }}-node- 51 | 52 | - name: Render markdown and build website 53 | env: 54 | ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} 55 | ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }} 56 | ALGOLIA_INDEX_NAME: ${{ secrets.ALGOLIA_INDEX_NAME }} 57 | GA_TRACKING_ID: ${{ secrets.GA_TRACKING_ID }} 58 | GA_ANONYMIZE_IP: ${{ secrets.GA_ANONYMIZE_IP }} 59 | run: | 60 | sbt \ 61 | ++${{ matrix.scala.version }}! \ 62 | clean \ 63 | docs/clean \ 64 | docs/mdoc \ 65 | docs/docusaurGenerateAlgoliaConfigFile \ 66 | docs/docusaurGenerateGoogleAnalyticsConfigFile \ 67 | docs/docusaurInstall \ 68 | docs/docusaurCleanBuild \ 69 | docs/docusaurBuild 70 | -------------------------------------------------------------------------------- /website/src/components/HomepageFeatures/index.tsx: -------------------------------------------------------------------------------- 1 | import React from 'react'; 2 | import clsx from 'clsx'; 3 | import styles from './styles.module.css'; 4 | 5 | type FeatureItem = { 6 | title: string; 7 | Svg: React.ComponentType>; 8 | description: JSX.Element; 9 | }; 10 | 11 | const FeatureList: FeatureItem[] = [ 12 | { 13 | title: 'Just Semantic Version', 14 | Png: require('@site/static/img/version-control.png').default, 15 | description: ( 16 | <> 17 | It's just a semantic version library 18 | 19 | ), 20 | }, 21 | { 22 | title: 'Zero Dependency', 23 | Png: require('@site/static/img/null.png').default, 24 | description: ( 25 | <> 26 | just-semver has no external dependencies. 27 | 28 | ), 29 | }, 30 | { 31 | title: 'Support Scala 2 and 3', 32 | Png: require('@site/static/img/scala.png').default, 33 | description: ( 34 | <> 35 | It supports both Scala 2 and Scala 3. 36 | 37 | ), 38 | }, 39 | ]; 40 | 41 | function FeatureSvg({title, Svg, description}: FeatureItem) { 42 | return ( 43 |
44 |
45 | 46 |
47 |
48 |

{title}

49 |

{description}

50 |
51 |
52 | ); 53 | } 54 | 55 | function FeaturePng({Png, title, description}) { 56 | return ( 57 |
58 |
59 | {title} 60 |
61 |
62 |

{title}

63 |

{description}

64 |
65 |
66 | ); 67 | } 68 | 69 | export default function HomepageFeatures(): JSX.Element { 70 | return ( 71 |
72 |
73 |
74 | {FeatureList.map((props, idx) => ( 75 | props.hasOwnProperty('Svg') ? 76 | : 77 | 78 | ))} 79 |
80 |
81 |
82 | ); 83 | } 84 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala-2/just/semver/matcher/SemVerMatcher.scala: -------------------------------------------------------------------------------- 1 | package just.semver.matcher 2 | 3 | import just.semver.SemVer 4 | 5 | /** @author Kevin Lee 6 | * @since 2022-04-03 7 | */ 8 | sealed trait SemVerMatcher 9 | object SemVerMatcher { 10 | 11 | final case class Range(from: SemVer, to: SemVer) extends SemVerMatcher 12 | final case class Comparison( 13 | semVerComparison: SemVerComparison 14 | ) extends SemVerMatcher 15 | 16 | def range(from: SemVer, to: SemVer): SemVerMatcher = Range(from, to) 17 | 18 | def comparison(semVerComparison: SemVerComparison): SemVerMatcher = Comparison(semVerComparison) 19 | 20 | implicit class SemVerMatcherOps(private val semVerMatcher: SemVerMatcher) extends AnyVal { 21 | 22 | def matches(semVer: SemVer): Boolean = semVerMatcher match { 23 | case SemVerMatcher.Range(from, to) => 24 | semVer >= from && semVer <= to 25 | case SemVerMatcher.Comparison(SemVerComparison(op, v)) => 26 | op.eval(semVer, v) 27 | } 28 | 29 | def render: String = semVerMatcher match { 30 | case SemVerMatcher.Range(v1, v2) => 31 | s"${v1.render} - ${v2.render}" 32 | case SemVerMatcher.Comparison(svc) => 33 | svc.render 34 | } 35 | } 36 | 37 | sealed trait ParseError 38 | object ParseError { 39 | 40 | final case class RangeParseFailure(message: String, parseErrors: List[String], success: Option[SemVer]) 41 | extends ParseError 42 | final case class SemVerComparisonParseFailure(semVerComparisonParseError: SemVerComparison.ParseError) 43 | extends ParseError 44 | 45 | def rangeParseFailure( 46 | message: String, 47 | parseErrors: List[String], 48 | success: Option[SemVer] 49 | ): ParseError = 50 | RangeParseFailure(message, parseErrors, success) 51 | 52 | def semVerComparisonParseFailure(semVerComparisonParseError: SemVerComparison.ParseError): ParseError = 53 | SemVerComparisonParseFailure(semVerComparisonParseError) 54 | 55 | implicit class ParseErrorOps(private val parseError: ParseError) extends AnyVal { 56 | def render: String = parseError match { 57 | case ParseError.RangeParseFailure(message, errors, success) => 58 | s"SemVerMatcher.ParseError($message: Errors: [${errors.mkString(", ")}]" + 59 | s"${success.fold(", Success:")(succ => s", Success: ${succ.render}")})" 60 | 61 | case ParseError.SemVerComparisonParseFailure(err) => 62 | err.render 63 | } 64 | } 65 | } 66 | } 67 | -------------------------------------------------------------------------------- /modules/just-semver-decver/shared/src/main/scala-2/just/decver/matcher/DecVerMatcher.scala: -------------------------------------------------------------------------------- 1 | package just.decver.matcher 2 | 3 | import just.decver.DecVer 4 | 5 | /** @author Kevin Lee 6 | * @since 2022-04-03 7 | */ 8 | sealed trait DecVerMatcher 9 | object DecVerMatcher { 10 | 11 | final case class Range(from: DecVer, to: DecVer) extends DecVerMatcher 12 | final case class Comparison( 13 | decVerComparison: DecVerComparison 14 | ) extends DecVerMatcher 15 | 16 | def range(from: DecVer, to: DecVer): DecVerMatcher = Range(from, to) 17 | 18 | def comparison(decVerComparison: DecVerComparison): DecVerMatcher = Comparison(decVerComparison) 19 | 20 | implicit class DecVerMatcherOps(private val decVerMatcher: DecVerMatcher) extends AnyVal { 21 | 22 | def matches(decVer: DecVer): Boolean = decVerMatcher match { 23 | case DecVerMatcher.Range(from, to) => 24 | decVer >= from && decVer <= to 25 | 26 | case DecVerMatcher.Comparison(DecVerComparison(op, v)) => 27 | op.eval(decVer, v) 28 | } 29 | 30 | def render: String = decVerMatcher match { 31 | case DecVerMatcher.Range(v1, v2) => 32 | s"${v1.render} - ${v2.render}" 33 | 34 | case DecVerMatcher.Comparison(svc) => 35 | svc.render 36 | } 37 | } 38 | 39 | sealed trait ParseError 40 | object ParseError { 41 | 42 | final case class RangeParseFailure(message: String, parseErrors: List[String], success: Option[DecVer]) 43 | extends ParseError 44 | final case class DecVerComparisonParseFailure(decVerComparisonParseError: DecVerComparison.ParseError) 45 | extends ParseError 46 | 47 | def rangeParseFailure( 48 | message: String, 49 | parseErrors: List[String], 50 | success: Option[DecVer] 51 | ): ParseError = 52 | RangeParseFailure(message, parseErrors, success) 53 | 54 | def decVerComparisonParseFailure(decVerComparisonParseError: DecVerComparison.ParseError): ParseError = 55 | DecVerComparisonParseFailure(decVerComparisonParseError) 56 | 57 | implicit class ParseErrorOps(private val parseError: ParseError) extends AnyVal { 58 | def render: String = parseError match { 59 | case ParseError.RangeParseFailure(message, errors, success) => 60 | s"DecVerMatcher.ParseError($message: Errors: [${errors.mkString(", ")}]" + 61 | s"${success.fold(", Success:")(succ => s", Success: ${succ.render}")})" 62 | 63 | case ParseError.DecVerComparisonParseFailure(err) => 64 | err.render 65 | } 66 | } 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /.github/workflows/doc-site-publish.yml: -------------------------------------------------------------------------------- 1 | name: "[Doc][M] Publish" 2 | 3 | on: 4 | workflow_dispatch: 5 | 6 | jobs: 7 | build_and_publish_doc_site: 8 | if: github.ref != 'refs/heads/gh-pages' && github.ref != 'gh-pages' 9 | 10 | runs-on: ubuntu-latest 11 | 12 | strategy: 13 | matrix: 14 | scala: 15 | - { version: "2.13.16", binary-version: "2.13", java-version: "11", java-distribution: "temurin" } 16 | node: 17 | - { version: "22.17.0" } 18 | 19 | steps: 20 | - uses: actions/checkout@v6 21 | - uses: actions/setup-java@v5 22 | with: 23 | java-version: ${{ matrix.scala.java-version }} 24 | distribution: ${{ matrix.scala.java-distribution }} 25 | cache: 'sbt' 26 | - uses: sbt/setup-sbt@v1 27 | - uses: actions/setup-node@v6 28 | with: 29 | node-version: ${{ matrix.node.version }} 30 | registry-url: 'https://registry.npmjs.org' 31 | 32 | - name: Cache SBT 33 | uses: actions/cache@v5 34 | with: 35 | path: | 36 | ~/.ivy2/cache 37 | ~/.cache/coursier 38 | ~/.sbt 39 | key: ${{ runner.os }}-sbt-${{ matrix.scala.binary-version }}-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('**/build.properties') }} 40 | restore-keys: | 41 | ${{ runner.os }}-sbt-${{ matrix.scala.binary-version }}- 42 | 43 | - name: Cache npm 44 | uses: actions/cache@v5 45 | with: 46 | path: ~/.npm 47 | key: ${{ runner.os }}-node-${{ hashFiles('**/package-lock.json') }} 48 | restore-keys: | 49 | ${{ runner.os }}-node- 50 | 51 | - name: Build and publish website 52 | env: 53 | ALGOLIA_APP_ID: ${{ secrets.ALGOLIA_APP_ID }} 54 | ALGOLIA_API_KEY: ${{ secrets.ALGOLIA_API_KEY }} 55 | ALGOLIA_INDEX_NAME: ${{ secrets.ALGOLIA_INDEX_NAME }} 56 | GA_TRACKING_ID: ${{ secrets.GA_TRACKING_ID }} 57 | GA_ANONYMIZE_IP: ${{ secrets.GA_ANONYMIZE_IP }} 58 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 59 | run: | 60 | sbt \ 61 | ++${{ matrix.scala.version }}! \ 62 | clean \ 63 | docs/clean \ 64 | docs/mdoc \ 65 | docs/docusaurGenerateAlgoliaConfigFile \ 66 | docs/docusaurGenerateGoogleAnalyticsConfigFile \ 67 | docs/docusaurInstall \ 68 | docs/docusaurCleanBuild \ 69 | docs/docusaurBuild \ 70 | docs/publishToGitHubPages 71 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala/just/semver/parser/Parser.scala: -------------------------------------------------------------------------------- 1 | package just.semver.parser 2 | 3 | /** @author Kevin Lee 4 | * @since 2022-04-03 5 | */ 6 | abstract class Parser[+A] { 7 | self => 8 | 9 | def parseTo(state: Parser.State): A 10 | 11 | def parse(s: String): Either[ParserError, (String, A)] = { 12 | val state = Parser.State(s) 13 | val result = parseTo(state) 14 | val error = state.error 15 | 16 | if (error.isEmpty) 17 | Right((s.substring(state.offset), result)) 18 | else 19 | Left( 20 | ParserError( 21 | s"Error at ${state.offset.toString}", 22 | Some(s.substring(0, state.offset)).filter(_.nonEmpty), 23 | Some(s).filter(_.nonEmpty) 24 | ) 25 | ) 26 | } 27 | 28 | def map[B](f: A => B): Parser[B] = new Parser[B] { 29 | override def parseTo(state: Parser.State): B = 30 | f(self.parseTo(state)) 31 | } 32 | 33 | def flatMap[B](f: A => Parser[B]): Parser[B] = new Parser[B] { 34 | override def parseTo(state: Parser.State): B = { 35 | val result = self.parseTo(state) 36 | f(result).parseTo(state) 37 | } 38 | } 39 | 40 | def ~[B](next: Parser[B]): Parser[(A, B)] = new Parser[(A, B)] { 41 | override def parseTo(state: Parser.State): (A, B) = { 42 | val a = self.parseTo(state) 43 | val b = next.parseTo(state) 44 | (a, b) 45 | } 46 | } 47 | 48 | } 49 | 50 | object Parser { 51 | 52 | class State(val value: String) { 53 | @SuppressWarnings(Array("org.wartremover.warts.Var")) 54 | var offset: Int = 0 // scalafix:ok DisableSyntax.var 55 | 56 | @SuppressWarnings(Array("org.wartremover.warts.Var")) 57 | var error: Option[ParserError] = None // scalafix:ok DisableSyntax.var 58 | } 59 | object State { 60 | def apply(value: String): State = new State(value) 61 | } 62 | 63 | def charsWhile(f: Char => Boolean): Parser[String] = new Parser[String] { 64 | override def parseTo(state: State): String = { 65 | val parsed = state.value.zipWithIndex.drop(state.offset).takeWhile { case (c, index @ _) => f(c) } 66 | val parsedStr = parsed.map(_._1).mkString 67 | val offset = parsed.lastOption.map(_._2).getOrElse(0) 68 | if (parsedStr.isEmpty) { 69 | state.error = Some(ParserError("", None: Option[String], Some(state.value))) 70 | "" 71 | } else { 72 | state.offset = offset + 1 73 | parsedStr 74 | } 75 | } 76 | } 77 | 78 | def charsIn(in: Seq[Char]): Parser[String] = charsWhile(c => in.contains(c)) 79 | 80 | } 81 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala-2/just/semver/matcher/SemVerComparison.scala: -------------------------------------------------------------------------------- 1 | package just.semver.matcher 2 | 3 | import just.semver.expr.ComparisonOperator 4 | import just.semver.parser.Parser 5 | import just.semver.{Compat, SemVer} 6 | 7 | /** @author Kevin Lee 8 | * @since 2022-04-05 9 | */ 10 | final case class SemVerComparison(comparisonOperator: ComparisonOperator, semVer: SemVer) 11 | object SemVerComparison extends Compat { 12 | 13 | def parse(s: String): Either[ParseError, SemVerComparison] = 14 | Parser 15 | .charsIn(">!=<") 16 | .parse(s) 17 | .left 18 | .map(err => SemVerComparison.ParseError(s"Failed to parse operator from $s", err.render, None)) 19 | .flatMap { 20 | case (rest, op) => 21 | ComparisonOperator 22 | .parse(op) 23 | .left 24 | .map(err => SemVerComparison.ParseError(s"Failed to parse operator from $s", err, None)) 25 | .flatMap { operator => 26 | SemVer 27 | .parse(rest) 28 | .map(version => SemVerComparison(operator, version)) 29 | .left 30 | .map(err => 31 | SemVerComparison.ParseError( 32 | s"Parsing operator succeeded but failed to parse SemVer from $s", 33 | err.render, 34 | Some(op) 35 | ) 36 | ) 37 | } 38 | } 39 | 40 | def unsafeParse(s: String): SemVerComparison = 41 | parse(s).fold(err => sys.error(err.render), identity) 42 | 43 | def render(semVerComparison: SemVerComparison): String = semVerComparison match { 44 | case SemVerComparison(op, v) => s"${op.render}${v.render}" 45 | } 46 | 47 | implicit class SemVerComparisonOps(private val semVerComparison: SemVerComparison) extends AnyVal { 48 | def render: String = SemVerComparison.render(semVerComparison) 49 | } 50 | 51 | final case class ParseError(message: String, error: String, success: Option[String]) 52 | object ParseError { 53 | def render(semVerComparisonParseError: ParseError): String = semVerComparisonParseError match { 54 | case ParseError(message, err, success) => 55 | s"SemVerComparison.ParseError($message: ParserError($err)${success.fold(", Success:")(succ => s", Success: $succ")})" 56 | } 57 | 58 | implicit class ParseErrorOps(private val semVerComparisonParseError: SemVerComparison.ParseError) extends AnyVal { 59 | def render: String = ParseError.render(semVerComparisonParseError) 60 | } 61 | } 62 | 63 | } 64 | -------------------------------------------------------------------------------- /modules/just-semver-decver/shared/src/main/scala-2/just/decver/matcher/DecVerComparison.scala: -------------------------------------------------------------------------------- 1 | package just.decver.matcher 2 | 3 | import just.semver.expr.ComparisonOperator 4 | import just.semver.parser.Parser 5 | import just.semver.Compat 6 | import just.decver.DecVer 7 | 8 | /** @author Kevin Lee 9 | * @since 2022-04-05 10 | */ 11 | final case class DecVerComparison(comparisonOperator: ComparisonOperator, decVer: DecVer) 12 | object DecVerComparison extends Compat { 13 | 14 | def parse(s: String): Either[ParseError, DecVerComparison] = 15 | Parser 16 | .charsIn(">!=<") 17 | .parse(s) 18 | .left 19 | .map(err => DecVerComparison.ParseError(s"Failed to parse operator from $s", err.render, None)) 20 | .flatMap { 21 | case (rest, op) => 22 | ComparisonOperator 23 | .parse(op) 24 | .left 25 | .map(err => DecVerComparison.ParseError(s"Failed to parse operator from $s", err, None)) 26 | .flatMap { operator => 27 | DecVer 28 | .parse(rest) 29 | .map(version => DecVerComparison(operator, version)) 30 | .left 31 | .map(err => 32 | DecVerComparison.ParseError( 33 | s"Parsing operator succeeded but failed to parse DecVer from $s", 34 | err.render, 35 | Some(op) 36 | ) 37 | ) 38 | } 39 | } 40 | 41 | def unsafeParse(s: String): DecVerComparison = 42 | parse(s).fold(err => sys.error(err.render), identity) 43 | 44 | def render(decVerComparison: DecVerComparison): String = decVerComparison match { 45 | case DecVerComparison(op, v) => s"${op.render}${v.render}" 46 | } 47 | 48 | implicit class DecVerComparisonOps(private val decVerComparison: DecVerComparison) extends AnyVal { 49 | def render: String = DecVerComparison.render(decVerComparison) 50 | } 51 | 52 | final case class ParseError(message: String, error: String, success: Option[String]) 53 | object ParseError { 54 | def render(decVerComparisonParseError: ParseError): String = decVerComparisonParseError match { 55 | case ParseError(message, err, success) => 56 | s"DecVerComparison.ParseError($message: ParserError($err)${success.fold(", Success:")(succ => s", Success: $succ")})" 57 | } 58 | 59 | implicit class ParseErrorOps(private val decVerComparisonParseError: DecVerComparison.ParseError) extends AnyVal { 60 | def render: String = ParseError.render(decVerComparisonParseError) 61 | } 62 | } 63 | 64 | } 65 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala-3/just/semver/Dsv.scala: -------------------------------------------------------------------------------- 1 | package just.semver 2 | 3 | import just.Common.* 4 | import scala.annotation.tailrec 5 | 6 | /** Dot separated value 7 | * @author Kevin Lee 8 | * @since 2018-10-21 9 | */ 10 | final case class Dsv(values: List[Anh]) extends Ordered[Dsv] derives CanEqual { 11 | override def compare(that: Dsv): Int = 12 | this.values.compareElems(that.values) 13 | } 14 | 15 | object Dsv extends Compat { 16 | 17 | import Anh._ 18 | 19 | extension (dsv: Dsv) { 20 | def render: String = dsv.values.map(_.render).mkString 21 | } 22 | 23 | def parse(value: String): Either[DsvParseError, Dsv] = { 24 | 25 | @tailrec 26 | def accumulate(cs: List[Char], chars: Anh, acc: Vector[Anh]): Either[DsvParseError, Vector[Anh]] = 27 | cs match { 28 | case x :: xs => 29 | if (x >= '0' && x <= '9') { 30 | chars match { 31 | case Num(ns) => 32 | accumulate(xs, Num(ns :+ x), acc) 33 | 34 | case _ => 35 | accumulate(xs, Num(x.toString), acc :+ chars) 36 | } 37 | } else if (x === '-') { 38 | accumulate(xs, Hyphen, acc :+ chars) 39 | } else if ((x >= 'A' && x <= 'Z') || (x >= 'a' && x <= 'z')) { 40 | chars match { 41 | case Alphabet(as) => 42 | accumulate(xs, Alphabet(as :+ x), acc) 43 | 44 | case _ => 45 | accumulate(xs, Alphabet(x.toString), acc :+ chars) 46 | } 47 | } else { 48 | Left( 49 | DsvParseError.invalidAlphaNumHyphenError(x, xs) 50 | ) 51 | } 52 | 53 | case Nil => 54 | Right(acc :+ chars) 55 | } 56 | 57 | value.toList match { 58 | case x :: xs => 59 | val result = 60 | if (x >= '0' && x <= '9') { 61 | accumulate(xs, Num(x.toString), Vector.empty) 62 | } else if (x === '-') 63 | accumulate(xs, Hyphen, Vector.empty) 64 | else if ((x >= 'A' && x <= 'Z') || (x >= 'a' && x <= 'z')) 65 | accumulate(xs, Alphabet(x.toString), Vector.empty) 66 | else 67 | Left( 68 | DsvParseError.invalidAlphaNumHyphenError(x, xs) 69 | ) 70 | 71 | result.map(groups => Dsv(groups.toList)) 72 | 73 | case Nil => 74 | Left(DsvParseError.emptyAlphaNumHyphenError) 75 | } 76 | 77 | } 78 | 79 | enum DsvParseError { 80 | case InvalidAlphaNumHyphenError(c: Char, rest: List[Char]) 81 | case EmptyAlphaNumHyphenError 82 | } 83 | object DsvParseError { 84 | def invalidAlphaNumHyphenError(c: Char, rest: List[Char]): DsvParseError = 85 | DsvParseError.InvalidAlphaNumHyphenError(c, rest) 86 | 87 | def emptyAlphaNumHyphenError: DsvParseError = DsvParseError.EmptyAlphaNumHyphenError 88 | 89 | } 90 | } 91 | -------------------------------------------------------------------------------- /.github/workflows/coverage.yml: -------------------------------------------------------------------------------- 1 | name: "Test Coverage" 2 | 3 | on: 4 | push: 5 | branches: 6 | - 'main' 7 | 8 | pull_request: 9 | branches: 10 | - main 11 | 12 | env: 13 | GH_SBT_OPTS: "-Xss64m -Xms1024m -Xmx4G -XX:MaxInlineLevel=18 -XX:+UnlockExperimentalVMOptions" 14 | GH_JVM_OPTS: "-Xss64m -Xms1024m -Xmx4G -XX:MaxInlineLevel=18 -XX:+UnlockExperimentalVMOptions" 15 | 16 | jobs: 17 | 18 | build: 19 | runs-on: ubuntu-latest 20 | 21 | strategy: 22 | matrix: 23 | scala: 24 | - { name: "Scala 2.13", version: "2.13.16", binary-version: "2.13", java-version: "11", java-distribution: "temurin", report: "report" } 25 | 26 | steps: 27 | - uses: actions/checkout@v6 28 | - uses: actions/setup-java@v5 29 | with: 30 | java-version: ${{ matrix.scala.java-version }} 31 | distribution: ${{ matrix.scala.java-distribution }} 32 | cache: 'sbt' 33 | - uses: sbt/setup-sbt@v1 34 | 35 | - name: "[Codecov] Report ${{ matrix.scala.name }} ${{ matrix.scala.version }} - ${{ github.run_number }}" 36 | if: ${{ matrix.scala.report == 'report' && github.event_name == 'push' }} 37 | env: 38 | CURRENT_BRANCH_NAME: ${{ github.ref }} 39 | RUN_ID: ${{ github.run_id }} 40 | RUN_NUMBER: ${{ github.run_number }} 41 | JVM_OPTS: ${{ env.GH_JVM_OPTS }} 42 | SBT_OPTS: ${{ env.GH_SBT_OPTS }} 43 | run: | 44 | echo "[BEFORE]CURRENT_BRANCH_NAME=${CURRENT_BRANCH_NAME}" 45 | export CURRENT_BRANCH_NAME="${CURRENT_BRANCH_NAME#refs/heads/}" 46 | echo " [AFTER]CURRENT_BRANCH_NAME=${CURRENT_BRANCH_NAME}" 47 | echo "RUN_ID=${RUN_ID}" 48 | echo "RUN_NUMBER=${RUN_NUMBER}" 49 | echo "Push #${PUSH_NUMBER}" 50 | java -version 51 | .github/workflows/sbt-build-all-with-coverage.sh ${{ matrix.scala.version }} 52 | 53 | 54 | - name: "[Codecov] Report ${{ matrix.scala.name }} ${{ matrix.scala.version }} - ${{ github.run_number }} - PR-#${{ github.event.pull_request.number }} - ${{ github.run_number }}" 55 | if: ${{ matrix.scala.report == 'report' && github.event_name == 'pull_request' }} 56 | env: 57 | CURRENT_BRANCH_NAME: ${{ github.base_ref }} 58 | RUN_ID: ${{ github.run_id }} 59 | RUN_NUMBER: ${{ github.run_number }} 60 | PR_NUMBER: ${{ github.event.pull_request.number }} 61 | JVM_OPTS: ${{ env.GH_JVM_OPTS }} 62 | SBT_OPTS: ${{ env.GH_SBT_OPTS }} 63 | run: | 64 | echo "Rull request to the '${CURRENT_BRANCH_NAME}' branch" 65 | echo "RUN_ID=${RUN_ID}" 66 | echo "RUN_NUMBER=${RUN_NUMBER}" 67 | echo "PR #${PR_NUMBER}" 68 | java -version 69 | .github/workflows/sbt-build-all-with-coverage.sh ${{ matrix.scala.version }} 70 | 71 | - if: ${{ matrix.scala.report == 'report' }} 72 | uses: codecov/codecov-action@v5 73 | with: 74 | token: ${{ secrets.CODECOV_TOKEN }} 75 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala-2/just/semver/Dsv.scala: -------------------------------------------------------------------------------- 1 | package just.semver 2 | 3 | import just.Common._ 4 | 5 | import scala.annotation.tailrec 6 | 7 | /** Dot separated value 8 | * @author Kevin Lee 9 | * @since 2018-10-21 10 | */ 11 | final case class Dsv(values: List[Anh]) extends Ordered[Dsv] { 12 | override def compare(that: Dsv): Int = 13 | this.values.compareElems(that.values) 14 | } 15 | 16 | object Dsv extends Compat { 17 | 18 | import Anh._ 19 | 20 | implicit final class DsvOps(private val dsv: Dsv) extends AnyVal { 21 | @inline def render: String = Dsv.render(dsv) 22 | } 23 | 24 | def render(dsv: Dsv): String = 25 | dsv.values.map(Anh.render).mkString 26 | 27 | def parse(value: String): Either[DsvParseError, Dsv] = { 28 | 29 | @tailrec 30 | def accumulate(cs: List[Char], chars: Anh, acc: Vector[Anh]): Either[DsvParseError, Vector[Anh]] = 31 | cs match { 32 | case x :: xs => 33 | if (x >= '0' && x <= '9') { 34 | chars match { 35 | case Num(ns) => 36 | accumulate(xs, Num(ns :+ x), acc) 37 | 38 | case _ => 39 | accumulate(xs, Num(x.toString), acc :+ chars) 40 | } 41 | } else if (x === '-') { 42 | accumulate(xs, Hyphen, acc :+ chars) 43 | } else if ((x >= 'A' && x <= 'Z') || (x >= 'a' && x <= 'z')) { 44 | chars match { 45 | case Alphabet(as) => 46 | accumulate(xs, Alphabet(as :+ x), acc) 47 | 48 | case _ => 49 | accumulate(xs, Alphabet(x.toString), acc :+ chars) 50 | } 51 | } else { 52 | Left( 53 | DsvParseError.invalidAlphaNumHyphenError(x, xs) 54 | ) 55 | } 56 | 57 | case Nil => 58 | Right(acc :+ chars) 59 | } 60 | 61 | value.toList match { 62 | case x :: xs => 63 | val result = 64 | if (x >= '0' && x <= '9') { 65 | accumulate(xs, Num(x.toString), Vector.empty) 66 | } else if (x === '-') 67 | accumulate(xs, Hyphen, Vector.empty) 68 | else if ((x >= 'A' && x <= 'Z') || (x >= 'a' && x <= 'z')) 69 | accumulate(xs, Alphabet(x.toString), Vector.empty) 70 | else 71 | Left( 72 | DsvParseError.invalidAlphaNumHyphenError(x, xs) 73 | ) 74 | 75 | result.map(groups => Dsv(groups.toList)) 76 | 77 | case Nil => 78 | Left(DsvParseError.emptyAlphaNumHyphenError) 79 | } 80 | 81 | } 82 | 83 | sealed trait DsvParseError 84 | object DsvParseError { 85 | final case class InvalidAlphaNumHyphenError(c: Char, rest: List[Char]) extends DsvParseError 86 | case object EmptyAlphaNumHyphenError extends DsvParseError 87 | 88 | def invalidAlphaNumHyphenError(c: Char, rest: List[Char]): DsvParseError = InvalidAlphaNumHyphenError(c, rest) 89 | 90 | def emptyAlphaNumHyphenError: DsvParseError = EmptyAlphaNumHyphenError 91 | 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/test/scala/just/semver/AnhSpec.scala: -------------------------------------------------------------------------------- 1 | package just.semver 2 | 3 | import hedgehog._ 4 | import hedgehog.runner._ 5 | 6 | /** @author 7 | * Kevin Lee 8 | * @since 9 | * 2018-11-04 10 | */ 11 | object AnhSpec extends Properties { 12 | 13 | override def tests: List[Test] = List( 14 | property("Num(same).compare(Num(same)) should return 0", testNumEqual), 15 | property("Num(less).compare(Num(greater)) should return -1", testNumLess), 16 | property("Num(greater).compare(Num(less)) should return 1", testNumMore), 17 | property("AlphaHyphen(same).compare(AlphaHyphen(same)) should return 0", testAlphaHyphenEqual), 18 | property("AlphaHyphen(less).compare(AlphaHyphen(greater)) should return the Int < 0", testAlphaHyphenLess), 19 | property("AlphaHyphen(greater).compare(AlphaHyphen(less)) should return the Int > 0", testAlphaHyphenMore), 20 | property("test Anh.render(anh)", testAnhRenderAnh), 21 | property("test anh.render", testAnhRenderAnh) 22 | ) 23 | 24 | @SuppressWarnings(Array("org.wartremover.warts.Equals")) 25 | def testNumEqual: Property = for { 26 | num <- Gens.genNum.log("num") 27 | } yield { 28 | num.compare(num) ==== 0 and Result.assert(num == num) 29 | } 30 | 31 | def testNumLess: Property = for { 32 | minMax <- Gens.genMinMaxNum.log("(num1, num2)") 33 | (num1, num2) = minMax 34 | } yield { 35 | num1.compare(num2) ==== -1 36 | } 37 | 38 | def testNumMore: Property = for { 39 | minMax <- Gens.genMinMaxNum.log("(num1, num2)") 40 | (num1, num2) = minMax 41 | } yield { 42 | num2.compare(num1) ==== 1 43 | } 44 | 45 | @SuppressWarnings(Array("org.wartremover.warts.Equals")) 46 | def testAlphaHyphenEqual: Property = for { 47 | alphaHyphen <- Gens.genAlphabet(10).log("alphaHyphen") 48 | } yield { 49 | alphaHyphen.compare(alphaHyphen) ==== 0 and Result.assert(alphaHyphen == alphaHyphen) 50 | } 51 | 52 | def testAlphaHyphenLess: Property = for { 53 | alphaHyphenPair <- Gens.genMinMaxAlphabet(10).log("(alphaHyphen1, alphaHyphen2)") 54 | (alphaHyphen1, alphaHyphen2) = alphaHyphenPair 55 | } yield { 56 | Result.assert(alphaHyphen1.compare(alphaHyphen2) < 0) 57 | } 58 | 59 | def testAlphaHyphenMore: Property = for { 60 | alphaHyphenPair <- Gens.genMinMaxAlphabet(10).log("(alphaHyphen1, alphaHyphen2)") 61 | (alphaHyphen1, alphaHyphen2) = alphaHyphenPair 62 | } yield { 63 | Result.assert(alphaHyphen2.compare(alphaHyphen1) > 0) 64 | } 65 | 66 | def testAnhRenderAnh: Property = for { 67 | anh <- Gen.frequency1(3 -> Gens.genNum, 6 -> Gens.genAlphabet(10), 1 -> Gens.genHyphen).log("anh") 68 | } yield { 69 | val expected: String = 70 | anh match { 71 | case Anh.Alphabet(value) => 72 | value 73 | case Anh.Num(value) => 74 | value 75 | case Anh.Hyphen => 76 | "-" 77 | } 78 | val actual = Anh.render(anh) 79 | actual ==== expected 80 | } 81 | 82 | def testAnhRender: Property = for { 83 | anh <- Gen.frequency1(3 -> Gens.genNum, 6 -> Gens.genAlphabet(10), 1 -> Gens.genHyphen).log("anh") 84 | } yield { 85 | val expected: String = Anh.render(anh) 86 | val actual = anh.render 87 | actual ==== expected 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala-3/just/semver/AdditionalInfo.scala: -------------------------------------------------------------------------------- 1 | package just.semver 2 | 3 | import just.Common.* 4 | 5 | /** @author Kevin Lee 6 | * @since 2018-10-21 7 | */ 8 | object AdditionalInfo extends Compat { 9 | 10 | import Anh._ 11 | 12 | final case class PreRelease(identifier: List[Dsv]) derives CanEqual 13 | object PreRelease { 14 | extension (preRelease: PreRelease) { 15 | def render: String = 16 | preRelease.identifier.map(_.render).mkString(".") 17 | } 18 | } 19 | 20 | final case class BuildMetaInfo(identifier: List[Dsv]) derives CanEqual 21 | object BuildMetaInfo { 22 | extension (buildMetaInfo: BuildMetaInfo) { 23 | def render: String = 24 | buildMetaInfo.identifier.map(_.render).mkString(".") 25 | } 26 | } 27 | 28 | def parsePreRelease(value: String): Either[AdditionalInfoParseError, Option[PreRelease]] = 29 | parse( 30 | value, 31 | { 32 | case a @ Dsv(Num(n) :: Nil) => 33 | if ((n === "0") || n.takeWhile(_ === '0').length === 0) 34 | Right(a) 35 | else 36 | Left(AdditionalInfoParseError.leadingZeroNumError(n)) 37 | case a @ Dsv(_) => 38 | Right(a) 39 | } 40 | ).map(_.map(PreRelease.apply)) 41 | 42 | def parseBuildMetaInfo(value: String): Either[AdditionalInfoParseError, Option[BuildMetaInfo]] = 43 | parse(value, Right.apply).map(_.map(BuildMetaInfo.apply)) 44 | 45 | def parse( 46 | value: String, 47 | validator: Dsv => Either[AdditionalInfoParseError, Dsv] 48 | ): Either[AdditionalInfoParseError, Option[List[Dsv]]] = { 49 | val alphaNumHyphens: Either[AdditionalInfoParseError, List[Dsv]] = 50 | Option(value) 51 | .map(_.split("\\.")) 52 | .map(_.map(Dsv.parse)) match { 53 | case Some(preRelease) => 54 | preRelease.foldRight(List.empty[Dsv].asRight[AdditionalInfoParseError]) { (x, acc) => 55 | x.left 56 | .map { 57 | case Dsv.DsvParseError.InvalidAlphaNumHyphenError(c, rest) => 58 | AdditionalInfoParseError.invalidAlphaNumHyphenError(c, rest) 59 | case Dsv.DsvParseError.EmptyAlphaNumHyphenError => 60 | AdditionalInfoParseError.emptyAlphaNumHyphenError 61 | } 62 | .flatMap(validator) match { 63 | case Right(alp) => 64 | acc.map(alps => alp :: alps) 65 | case Left(error) => 66 | error.asLeft[List[Dsv]] 67 | } 68 | } 69 | case None => 70 | List.empty[Dsv].asRight[AdditionalInfoParseError] 71 | } 72 | alphaNumHyphens.map { 73 | case Nil => 74 | none[List[Dsv]] 75 | case xs => 76 | xs.some 77 | } 78 | } 79 | 80 | sealed trait AdditionalInfoParseError 81 | object AdditionalInfoParseError { 82 | final case class LeadingZeroNumError(n: String) extends AdditionalInfoParseError 83 | 84 | final case class InvalidAlphaNumHyphenError(c: Char, rest: List[Char]) extends AdditionalInfoParseError 85 | case object EmptyAlphaNumHyphenError extends AdditionalInfoParseError 86 | 87 | def leadingZeroNumError(n: String): AdditionalInfoParseError = LeadingZeroNumError(n) 88 | 89 | def invalidAlphaNumHyphenError(c: Char, rest: List[Char]): AdditionalInfoParseError = 90 | InvalidAlphaNumHyphenError(c, rest) 91 | 92 | def emptyAlphaNumHyphenError: AdditionalInfoParseError = EmptyAlphaNumHyphenError 93 | } 94 | 95 | } 96 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala-3/just/semver/ParseError.scala: -------------------------------------------------------------------------------- 1 | package just.semver 2 | 3 | import just.semver.matcher.SemVerMatchers 4 | 5 | /** @author Kevin Lee 6 | * @since 2018-10-21 7 | */ 8 | enum ParseError derives CanEqual { 9 | 10 | case InvalidAlphaNumHyphenError(c: Char, rest: List[Char]) 11 | case EmptyAlphaNumHyphenError 12 | case LeadingZeroNumError(n: String) 13 | case PreReleaseParseError(parseError: ParseError) 14 | case BuildMetadataParseError(parseError: ParseError) 15 | case CombinedParseError(preReleaseError: ParseError, buildMetadataError: ParseError) 16 | case InvalidVersionStringError(value: String) 17 | case SemVerMatchersParseErrors(error: matcher.SemVerMatchers.ParseErrors) 18 | } 19 | 20 | object ParseError { 21 | def invalidAlphaNumHyphenError(c: Char, rest: List[Char]): ParseError = 22 | InvalidAlphaNumHyphenError(c, rest) 23 | 24 | def emptyAlphaNumHyphenError: ParseError = 25 | EmptyAlphaNumHyphenError 26 | 27 | def leadingZeroNumError(n: String): ParseError = 28 | LeadingZeroNumError(n) 29 | 30 | def preReleaseParseError(parseError: ParseError): ParseError = 31 | PreReleaseParseError(parseError) 32 | 33 | def buildMetadataParseError(parseError: ParseError): ParseError = 34 | BuildMetadataParseError(parseError) 35 | 36 | def combine(preReleaseError: ParseError, buildMetadataError: ParseError): ParseError = 37 | CombinedParseError( 38 | preReleaseParseError(preReleaseError), 39 | buildMetadataParseError(buildMetadataError) 40 | ) 41 | 42 | def invalidVersionStringError(value: String): ParseError = 43 | InvalidVersionStringError(value) 44 | 45 | def semVerMatchersParseErrors(error: SemVerMatchers.ParseErrors): ParseError = 46 | SemVerMatchersParseErrors(error) 47 | 48 | def fromAdditionalInfoParserError(additionalInfoParseError: AdditionalInfo.AdditionalInfoParseError): ParseError = 49 | additionalInfoParseError match { 50 | case AdditionalInfo.AdditionalInfoParseError.LeadingZeroNumError(n) => 51 | ParseError.leadingZeroNumError(n) 52 | case AdditionalInfo.AdditionalInfoParseError.InvalidAlphaNumHyphenError(c, rest) => 53 | ParseError.invalidAlphaNumHyphenError(c, rest) 54 | case AdditionalInfo.AdditionalInfoParseError.EmptyAlphaNumHyphenError => 55 | ParseError.emptyAlphaNumHyphenError 56 | } 57 | 58 | extension (parseError: ParseError) { 59 | @SuppressWarnings(Array("org.wartremover.warts.Recursion", "org.wartremover.warts.ToString")) 60 | def render: String = parseError match { 61 | case InvalidAlphaNumHyphenError(c, rest) => 62 | s"Invalid char for AlphaNumHyphen found. value: ${c.toString} / rest: ${rest.toString}" 63 | 64 | case EmptyAlphaNumHyphenError => 65 | "AlphaNumHyphen cannot be empty but the given value is an empty String." 66 | 67 | case LeadingZeroNumError(n) => 68 | s"Invalid Num value. It should not have any leading zeros. value: $n" 69 | 70 | case PreReleaseParseError(error) => 71 | s"Error in parsing pre-release: ${error.render}" 72 | 73 | case BuildMetadataParseError(error) => 74 | s"Error in parsing build meta data: ${error.render}" 75 | 76 | case CombinedParseError(preReleaseError, buildMetadataError) => 77 | s"""Errors: 78 | |[1] ${preReleaseError.render} 79 | |[2] ${buildMetadataError.render} 80 | |""".stripMargin 81 | 82 | case InvalidVersionStringError(value) => 83 | s"Invalid SemVer String. value: $value" 84 | 85 | case SemVerMatchersParseErrors(error) => 86 | s"Error when parsing SemVerMatchers: ${error.render}" 87 | } 88 | } 89 | 90 | } 91 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala-2/just/semver/AdditionalInfo.scala: -------------------------------------------------------------------------------- 1 | package just.semver 2 | 3 | import just.Common._ 4 | 5 | /** @author Kevin Lee 6 | * @since 2018-10-21 7 | */ 8 | object AdditionalInfo extends Compat { 9 | 10 | import Anh._ 11 | 12 | final case class PreRelease(identifier: List[Dsv]) 13 | object PreRelease { 14 | implicit final class PreReleaseOps(private val preRelease: PreRelease) extends AnyVal { 15 | @inline def render: String = PreRelease.render(preRelease) 16 | } 17 | def render(preRelease: PreRelease): String = 18 | preRelease.identifier.map(Dsv.render).mkString(".") 19 | } 20 | 21 | final case class BuildMetaInfo(identifier: List[Dsv]) 22 | object BuildMetaInfo { 23 | implicit final class BuildMetaInfoOps(private val buildMetaInfo: BuildMetaInfo) extends AnyVal { 24 | @inline def render: String = BuildMetaInfo.render(buildMetaInfo) 25 | } 26 | def render(buildMetaInfo: BuildMetaInfo): String = 27 | buildMetaInfo.identifier.map(Dsv.render).mkString(".") 28 | } 29 | 30 | def parsePreRelease(value: String): Either[AdditionalInfoParseError, Option[PreRelease]] = 31 | parse( 32 | value, 33 | { 34 | case a @ Dsv(Num(n) :: Nil) => 35 | if ((n === "0") || n.takeWhile(_ === '0').length === 0) 36 | Right(a) 37 | else 38 | Left(AdditionalInfoParseError.leadingZeroNumError(n)) 39 | case a @ Dsv(_) => 40 | Right(a) 41 | } 42 | ).map(_.map(PreRelease.apply)) 43 | 44 | def parseBuildMetaInfo(value: String): Either[AdditionalInfoParseError, Option[BuildMetaInfo]] = 45 | parse(value, Right.apply).map(_.map(BuildMetaInfo.apply)) 46 | 47 | def parse( 48 | value: String, 49 | validator: Dsv => Either[AdditionalInfoParseError, Dsv] 50 | ): Either[AdditionalInfoParseError, Option[List[Dsv]]] = { 51 | val alphaNumHyphens: Either[AdditionalInfoParseError, List[Dsv]] = 52 | Option(value) 53 | .map(_.split("\\.")) 54 | .map(_.map(Dsv.parse)) match { 55 | case Some(preRelease) => 56 | preRelease.foldRight(List.empty[Dsv].asRight[AdditionalInfoParseError]) { (x, acc) => 57 | x.left 58 | .map { 59 | case Dsv.DsvParseError.InvalidAlphaNumHyphenError(c, rest) => 60 | AdditionalInfoParseError.invalidAlphaNumHyphenError(c, rest) 61 | case Dsv.DsvParseError.EmptyAlphaNumHyphenError => 62 | AdditionalInfoParseError.emptyAlphaNumHyphenError 63 | } 64 | .flatMap(validator) match { 65 | case Right(alp) => 66 | acc.map(alps => alp :: alps) 67 | case Left(error) => 68 | error.asLeft[List[Dsv]] 69 | } 70 | } 71 | case None => 72 | List.empty[Dsv].asRight[AdditionalInfoParseError] 73 | } 74 | alphaNumHyphens.map { 75 | case Nil => 76 | none[List[Dsv]] 77 | case xs => 78 | xs.some 79 | } 80 | } 81 | 82 | sealed trait AdditionalInfoParseError 83 | object AdditionalInfoParseError { 84 | final case class LeadingZeroNumError(n: String) extends AdditionalInfoParseError 85 | 86 | final case class InvalidAlphaNumHyphenError(c: Char, rest: List[Char]) extends AdditionalInfoParseError 87 | case object EmptyAlphaNumHyphenError extends AdditionalInfoParseError 88 | 89 | def leadingZeroNumError(n: String): AdditionalInfoParseError = LeadingZeroNumError(n) 90 | 91 | def invalidAlphaNumHyphenError(c: Char, rest: List[Char]): AdditionalInfoParseError = 92 | InvalidAlphaNumHyphenError(c, rest) 93 | 94 | def emptyAlphaNumHyphenError: AdditionalInfoParseError = EmptyAlphaNumHyphenError 95 | } 96 | 97 | } 98 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/test/scala/just/semver/SemVerMajorSpec.scala: -------------------------------------------------------------------------------- 1 | package just.semver 2 | 3 | import hedgehog._ 4 | import hedgehog.runner._ 5 | 6 | /** @author 7 | * Kevin Lee 8 | * @since 9 | * 2018-11-04 10 | */ 11 | object SemVerMajorSpec extends Properties { 12 | 13 | override def tests: List[Test] = List( 14 | property("Two SemVers with the same Major and the rest are equal then it should be equal", testSameMajors), 15 | property( 16 | "Two SemVers with the different Majors and the rest are equal then it should be not equal", 17 | testDifferentMajors 18 | ), 19 | property("Test SemVer(Major(less)) < SemVer(Major(greater)) is true", testMajorLessCase), 20 | property("Test SemVer(Major(greater)) > SemVer(Major(less)) is true", testMajorMoreCase), 21 | property("Test SemVer(same Major) <= SemVer(same Major) is true", testLeeThanEqualWithSameMajors), 22 | property("Test SemVer(Major(less)) <= SemVer(Major(greater)) is true", testLeeThanEqualWithLess), 23 | property("Test SemVer(same Major) >= SemVer(same Major) is true", testMoreThanEqualWithSameMajors), 24 | property("Test SemVer(Major(greater)) >= SemVer(Major(less)) is true", testMoreThanEqualWithGreater) 25 | ) 26 | 27 | @SuppressWarnings(Array("org.wartremover.warts.Equals")) 28 | def testSameMajors: Property = for { 29 | major <- Gens.genMajor.log("major") 30 | } yield { 31 | val v1 = SemVer.withMajor(major) 32 | val v2 = SemVer.withMajor(major) 33 | Result.assert(v1 == v2).log("major == major") 34 | } 35 | 36 | @SuppressWarnings(Array("org.wartremover.warts.Equals")) 37 | def testDifferentMajors: Property = for { 38 | major1AndMajor2 <- Gens.genMinMaxMajors.log("(major1, major2)") 39 | (major1, major2) = major1AndMajor2 40 | } yield { 41 | val v1 = SemVer.withMajor(major1) 42 | val v2 = SemVer.withMajor(major2) 43 | Result.assert(v1 != v2).log("major1 != major2") 44 | } 45 | 46 | def testMajorLessCase: Property = for { 47 | major1AndMajor2 <- Gens.genMinMaxMajors.log("(major1, major2)") 48 | (major1, major2) = major1AndMajor2 49 | } yield { 50 | val v1 = SemVer.withMajor(major1) 51 | val v2 = SemVer.withMajor(major2) 52 | Result.assert(v1 < v2).log("major1 < major2") 53 | } 54 | 55 | def testMajorMoreCase: Property = for { 56 | major1AndMajor2 <- Gens.genMinMaxMajors.log("(major1, major2)") 57 | (major1, major2) = major1AndMajor2 58 | } yield { 59 | val v1 = SemVer.withMajor(major1) 60 | val v2 = SemVer.withMajor(major2) 61 | Result.assert(v2 > v1).log("major2 > major1") 62 | } 63 | 64 | def testLeeThanEqualWithSameMajors: Property = for { 65 | major <- Gens.genMajor.log("major") 66 | } yield { 67 | val v1 = SemVer.withMajor(major) 68 | val v2 = SemVer.withMajor(major) 69 | Result.assert(v1 <= v2).log("major1 <= major2") 70 | } 71 | 72 | def testLeeThanEqualWithLess: Property = for { 73 | major1AndMajor2 <- Gens.genMinMaxMajors.log("(major1, major2)") 74 | (major1, major2) = major1AndMajor2 75 | } yield { 76 | val v1 = SemVer.withMajor(major1) 77 | val v2 = SemVer.withMajor(major2) 78 | Result.assert(v1 <= v2).log("major1 <= major2") 79 | } 80 | 81 | def testMoreThanEqualWithSameMajors: Property = for { 82 | major <- Gens.genMajor.log("major") 83 | } yield { 84 | val v1 = SemVer.withMajor(major) 85 | val v2 = SemVer.withMajor(major) 86 | Result.assert(v1 >= v2) 87 | } 88 | 89 | def testMoreThanEqualWithGreater: Property = for { 90 | major1AndMajor2 <- Gens.genMinMaxMajors.log("(major1, major2)") 91 | (major1, major2) = major1AndMajor2 92 | } yield { 93 | val v1 = SemVer.withMajor(major1) 94 | val v2 = SemVer.withMajor(major2) 95 | Result.assert(v2 >= v1).log("major2 >= major1") 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/test/scala/just/semver/SemVerMinorSpec.scala: -------------------------------------------------------------------------------- 1 | package just.semver 2 | 3 | import hedgehog._ 4 | import hedgehog.runner._ 5 | 6 | /** @author 7 | * Kevin Lee 8 | * @since 9 | * 2018-11-04 10 | */ 11 | object SemVerMinorSpec extends Properties { 12 | 13 | override def tests: List[Test] = List( 14 | property("Two SemVers with the same Minor and the rest are equal then it should be equal", testSameMinors), 15 | property( 16 | "Two SemVers with the different Minors and the rest are equal then it should be not equal", 17 | testDifferentMinors 18 | ), 19 | property("Test SemVer(Minor(less)) < SemVer(Minor(greater)) is true", testMinorLessCase), 20 | property("Test SemVer(Minor(greater)) > SemVer(Minor(less)) is true", testMinorMoreCase), 21 | property("Test SemVer(same Minor) <= SemVer(same Minor) is true", testLeeThanEqualWithSameMinors), 22 | property("Test SemVer(Minor(less)) <= SemVer(Minor(greater)) is true", testLeeThanEqualWithLess), 23 | property("Test SemVer(same Minor) >= SemVer(same Minor) is true", testMoreThanEqualWithSameMinors), 24 | property("Test SemVer(Minor(greater)) >= SemVer(Minor(less)) is true", testMoreThanEqualWithGreater) 25 | ) 26 | 27 | @SuppressWarnings(Array("org.wartremover.warts.Equals")) 28 | def testSameMinors: Property = for { 29 | minor <- Gens.genMinor.log("minor") 30 | } yield { 31 | val v1 = SemVer.withMinor(minor) 32 | val v2 = SemVer.withMinor(minor) 33 | Result.assert(v1 == v2).log("minor == minor") 34 | } 35 | 36 | @SuppressWarnings(Array("org.wartremover.warts.Equals")) 37 | def testDifferentMinors: Property = for { 38 | minor1AndMinor2 <- Gens.genMinMaxMinors.log("(minor1, minor2)") 39 | (minor1, minor2) = minor1AndMinor2 40 | } yield { 41 | val v1 = SemVer.withMinor(minor1) 42 | val v2 = SemVer.withMinor(minor2) 43 | Result.assert(v1 != v2).log("minor1 != minor2") 44 | } 45 | 46 | def testMinorLessCase: Property = for { 47 | minor1AndMinor2 <- Gens.genMinMaxMinors.log("(minor1, minor2)") 48 | (minor1, minor2) = minor1AndMinor2 49 | } yield { 50 | val v1 = SemVer.withMinor(minor1) 51 | val v2 = SemVer.withMinor(minor2) 52 | Result.assert(v1 < v2).log("minor1 < minor2") 53 | } 54 | 55 | def testMinorMoreCase: Property = for { 56 | minor1AndMinor2 <- Gens.genMinMaxMinors.log("(minor1, minor2)") 57 | (minor1, minor2) = minor1AndMinor2 58 | } yield { 59 | val v1 = SemVer.withMinor(minor1) 60 | val v2 = SemVer.withMinor(minor2) 61 | Result.assert(v2 > v1).log("minor2 > minor1") 62 | } 63 | 64 | def testLeeThanEqualWithSameMinors: Property = for { 65 | minor <- Gens.genMinor.log("minor") 66 | } yield { 67 | val v1 = SemVer.withMinor(minor) 68 | val v2 = SemVer.withMinor(minor) 69 | Result.assert(v1 <= v2).log("minor1 <= minor2") 70 | } 71 | 72 | def testLeeThanEqualWithLess: Property = for { 73 | minor1AndMinor2 <- Gens.genMinMaxMinors.log("(minor1, minor2)") 74 | (minor1, minor2) = minor1AndMinor2 75 | } yield { 76 | val v1 = SemVer.withMinor(minor1) 77 | val v2 = SemVer.withMinor(minor2) 78 | Result.assert(v1 <= v2).log("minor1 <= minor2") 79 | } 80 | 81 | def testMoreThanEqualWithSameMinors: Property = for { 82 | minor <- Gens.genMinor.log("minor") 83 | } yield { 84 | val v1 = SemVer.withMinor(minor) 85 | val v2 = SemVer.withMinor(minor) 86 | Result.assert(v1 >= v2) 87 | } 88 | 89 | def testMoreThanEqualWithGreater: Property = for { 90 | minor1AndMinor2 <- Gens.genMinMaxMinors.log("(minor1, minor2)") 91 | (minor1, minor2) = minor1AndMinor2 92 | } yield { 93 | val v1 = SemVer.withMinor(minor1) 94 | val v2 = SemVer.withMinor(minor2) 95 | Result.assert(v2 >= v1).log("minor2 >= minor1") 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/test/scala/just/semver/SemVerPatchSpec.scala: -------------------------------------------------------------------------------- 1 | package just.semver 2 | 3 | import hedgehog._ 4 | import hedgehog.runner._ 5 | 6 | /** @author 7 | * Kevin Lee 8 | * @since 9 | * 2018-11-04 10 | */ 11 | object SemVerPatchSpec extends Properties { 12 | 13 | override def tests: List[Test] = List( 14 | property("Two SemVers with the same Patch and the rest are equal then it should be equal", testSamePatchs), 15 | property( 16 | "Two SemVers with the different Patchs and the rest are equal then it should be not equal", 17 | testDifferentPatchs 18 | ), 19 | property("Test SemVer(Patch(less)) < SemVer(Patch(greater)) is true", testPatchLessCase), 20 | property("Test SemVer(Patch(greater)) > SemVer(Patch(less)) is true", testPatchMoreCase), 21 | property("Test SemVer(same Patch) <= SemVer(same Patch) is true", testLeeThanEqualWithSamePatchs), 22 | property("Test SemVer(Patch(less)) <= SemVer(Patch(greater)) is true", testLeeThanEqualWithLess), 23 | property("Test SemVer(same Patch) >= SemVer(same Patch) is true", testMoreThanEqualWithSamePatchs), 24 | property("Test SemVer(Patch(greater)) >= SemVer(Patch(less)) is true", testMoreThanEqualWithGreater) 25 | ) 26 | 27 | @SuppressWarnings(Array("org.wartremover.warts.Equals")) 28 | def testSamePatchs: Property = for { 29 | patch <- Gens.genPatch.log("patch") 30 | } yield { 31 | val v1 = SemVer.withPatch(patch) 32 | val v2 = SemVer.withPatch(patch) 33 | Result.assert(v1 == v2).log("patch == patch") 34 | } 35 | 36 | @SuppressWarnings(Array("org.wartremover.warts.Equals")) 37 | def testDifferentPatchs: Property = for { 38 | patch1AndPatch2 <- Gens.genMinMaxPatches.log("(patch1, patch2)") 39 | (patch1, patch2) = patch1AndPatch2 40 | } yield { 41 | val v1 = SemVer.withPatch(patch1) 42 | val v2 = SemVer.withPatch(patch2) 43 | Result.assert(v1 != v2).log("patch1 != patch2") 44 | } 45 | 46 | def testPatchLessCase: Property = for { 47 | patch1AndPatch2 <- Gens.genMinMaxPatches.log("(patch1, patch2)") 48 | (patch1, patch2) = patch1AndPatch2 49 | } yield { 50 | val v1 = SemVer.withPatch(patch1) 51 | val v2 = SemVer.withPatch(patch2) 52 | Result.assert(v1 < v2).log("patch1 < patch2") 53 | } 54 | 55 | def testPatchMoreCase: Property = for { 56 | patch1AndPatch2 <- Gens.genMinMaxPatches.log("(patch1, patch2)") 57 | (patch1, patch2) = patch1AndPatch2 58 | } yield { 59 | val v1 = SemVer.withPatch(patch1) 60 | val v2 = SemVer.withPatch(patch2) 61 | Result.assert(v2 > v1).log("patch2 > patch1") 62 | } 63 | 64 | def testLeeThanEqualWithSamePatchs: Property = for { 65 | patch <- Gens.genPatch.log("patch") 66 | } yield { 67 | val v1 = SemVer.withPatch(patch) 68 | val v2 = SemVer.withPatch(patch) 69 | Result.assert(v1 <= v2).log("patch1 <= patch2") 70 | } 71 | 72 | def testLeeThanEqualWithLess: Property = for { 73 | patch1AndPatch2 <- Gens.genMinMaxPatches.log("(patch1, patch2)") 74 | (patch1, patch2) = patch1AndPatch2 75 | } yield { 76 | val v1 = SemVer.withPatch(patch1) 77 | val v2 = SemVer.withPatch(patch2) 78 | Result.assert(v1 <= v2).log("patch1 <= patch2") 79 | } 80 | 81 | def testMoreThanEqualWithSamePatchs: Property = for { 82 | patch <- Gens.genPatch.log("patch") 83 | } yield { 84 | val v1 = SemVer.withPatch(patch) 85 | val v2 = SemVer.withPatch(patch) 86 | Result.assert(v1 >= v2) 87 | } 88 | 89 | def testMoreThanEqualWithGreater: Property = for { 90 | patch1AndPatch2 <- Gens.genMinMaxPatches.log("(patch1, patch2)") 91 | (patch1, patch2) = patch1AndPatch2 92 | } yield { 93 | val v1 = SemVer.withPatch(patch1) 94 | val v2 = SemVer.withPatch(patch2) 95 | Result.assert(v2 >= v1).log("patch2 >= patch1") 96 | } 97 | 98 | } 99 | -------------------------------------------------------------------------------- /docs/decver/decver.md: -------------------------------------------------------------------------------- 1 | 2 | # DecVer (Decimal Version) 3 | 4 | It requires the `just-semver-decver` module. 5 | 6 | ```scala 7 | "io.kevinlee" %% "just-semver-decver" % "@VERSION@" 8 | ``` 9 | 10 | ```scala 11 | "io.kevinlee" %%% "just-semver-decver" % "@VERSION@" 12 | ``` 13 | 14 | 15 | ## `DecVer.parse` 16 | 17 | ```scala mdoc:reset-object 18 | import just.decver.DecVer 19 | 20 | val v = DecVer.parse("1.0") 21 | 22 | // To render it to `String`, 23 | v.map(_.render) 24 | 25 | // Invalid version 26 | DecVer.parse("a1.0") 27 | 28 | // Invalid version 29 | DecVer.parse("a1.0.0") 30 | 31 | ``` 32 | 33 | ## `DecVer.unsafeParse` 34 | 35 | ```scala mdoc:reset-object 36 | import just.decver.DecVer 37 | 38 | // parse unsafe - NOT RECOMMENDED!!! 39 | val v = DecVer.unsafeParse("1.0") 40 | 41 | // to String 42 | v.render 43 | ``` 44 | 45 | ```scala mdoc:crash 46 | 47 | // Invalid version 48 | DecVer.unsafeParse("a1.0") 49 | ``` 50 | 51 | ## DecVer with `pre-release` info 52 | ```scala mdoc:reset-object 53 | import just.decver.DecVer 54 | 55 | DecVer.parse("1.0-beta1") 56 | 57 | val v = DecVer.parse("1.0-3.123.9a") 58 | 59 | v.map(_.render) 60 | ``` 61 | 62 | ## DecVer with build `meta-info` 63 | ```scala mdoc:reset-object 64 | import just.decver.DecVer 65 | 66 | val v = DecVer.parse("1.0+100.0.12abc") 67 | 68 | v.map(_.render) 69 | ``` 70 | 71 | ## DecVer with `pre-release` info and build `meta-info` 72 | ```scala mdoc:reset-object 73 | import just.decver.DecVer 74 | 75 | DecVer.parse("1.0-beta1") 76 | 77 | val v = DecVer.parse("1.0-3.123.9a+100.0.12abc") 78 | 79 | v.map(_.render) 80 | ``` 81 | 82 | ## Compare `DecVer` 83 | ```scala mdoc:reset-object 84 | import just.decver.DecVer 85 | 86 | for { 87 | a <- DecVer.parse("1.0") 88 | b <- DecVer.parse("1.1") 89 | } yield a < b 90 | 91 | for { 92 | a <- DecVer.parse("1.1") 93 | b <- DecVer.parse("1.0") 94 | } yield a < b 95 | 96 | for { 97 | a <- DecVer.parse("1.0") 98 | b <- DecVer.parse("1.1") 99 | } yield a <= b 100 | 101 | for { 102 | a <- DecVer.parse("1.0") 103 | b <- DecVer.parse("1.0") 104 | } yield a <= b 105 | 106 | for { 107 | a <- DecVer.parse("1.0") 108 | b <- DecVer.parse("1.0") 109 | } yield a == b 110 | 111 | for { 112 | a <- DecVer.parse("1.1") 113 | b <- DecVer.parse("1.0") 114 | } yield a > b 115 | 116 | for { 117 | a <- DecVer.parse("1.0") 118 | b <- DecVer.parse("1.1") 119 | } yield a > b 120 | 121 | for { 122 | a <- DecVer.parse("1.0") 123 | b <- DecVer.parse("1.1") 124 | } yield a >= b 125 | 126 | for { 127 | a <- DecVer.parse("1.0") 128 | b <- DecVer.parse("1.0") 129 | } yield a >= b 130 | 131 | for { 132 | a <- DecVer.parse("1.1") 133 | b <- DecVer.parse("1.0") 134 | } yield a >= b 135 | ``` 136 | 137 | ## Matchers 138 | ```scala mdoc 139 | DecVer.unsafeParse("1.0").unsafeMatches("1.0 - 2.0") 140 | DecVer.unsafeParse("1.5").unsafeMatches("1.0 - 2.0") 141 | DecVer.unsafeParse("2.0").unsafeMatches("1.0 - 2.0") 142 | DecVer.unsafeParse("0.9").unsafeMatches("1.0 - 2.0") 143 | DecVer.unsafeParse("2.1").unsafeMatches("1.0 - 2.0") 144 | 145 | DecVer.unsafeParse("1.0").unsafeMatches(">1.0 <2.0") 146 | DecVer.unsafeParse("1.0").unsafeMatches(">=1.0 <=2.0") 147 | DecVer.unsafeParse("1.5").unsafeMatches(">1.0 <2.0") 148 | DecVer.unsafeParse("2.0").unsafeMatches(">1.0 <2.0") 149 | DecVer.unsafeParse("2.0").unsafeMatches(">=1.0 <=2.0") 150 | DecVer.unsafeParse("0.9").unsafeMatches(">=1.0 <=2.0") 151 | DecVer.unsafeParse("2.1").unsafeMatches(">=1.0 <=2.0") 152 | 153 | DecVer.unsafeParse("1.0").unsafeMatches("1.0 - 2.0 || >3.0 <4.0") 154 | DecVer.unsafeParse("2.0").unsafeMatches("1.0 - 2.0 || >3.0 <4.0") 155 | DecVer.unsafeParse("3.0").unsafeMatches("1.0 - 2.0 || >3.0 <=4.0") 156 | DecVer.unsafeParse("3.1").unsafeMatches("1.0 - 2.0 || >3.0 <=4.0") 157 | DecVer.unsafeParse("4.0").unsafeMatches("1.0 - 2.0 || >3.0 <=4.0") 158 | ``` 159 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala-2/just/semver/ParseError.scala: -------------------------------------------------------------------------------- 1 | package just.semver 2 | 3 | import just.semver.matcher.SemVerMatchers 4 | 5 | /** @author Kevin Lee 6 | * @since 2018-10-21 7 | */ 8 | sealed trait ParseError 9 | 10 | object ParseError { 11 | 12 | final case class InvalidAlphaNumHyphenError(c: Char, rest: List[Char]) extends ParseError 13 | case object EmptyAlphaNumHyphenError extends ParseError 14 | 15 | final case class LeadingZeroNumError(n: String) extends ParseError 16 | 17 | final case class PreReleaseParseError(parseError: ParseError) extends ParseError 18 | final case class BuildMetadataParseError(parseError: ParseError) extends ParseError 19 | 20 | final case class CombinedParseError(preReleaseError: ParseError, buildMetadataError: ParseError) extends ParseError 21 | 22 | final case class InvalidVersionStringError(value: String) extends ParseError 23 | 24 | final case class SemVerMatchersParseErrors(error: matcher.SemVerMatchers.ParseErrors) extends ParseError 25 | 26 | def invalidAlphaNumHyphenError(c: Char, rest: List[Char]): ParseError = 27 | InvalidAlphaNumHyphenError(c, rest) 28 | 29 | def emptyAlphaNumHyphenError: ParseError = 30 | EmptyAlphaNumHyphenError 31 | 32 | def leadingZeroNumError(n: String): ParseError = 33 | LeadingZeroNumError(n) 34 | 35 | def preReleaseParseError(parseError: ParseError): ParseError = 36 | PreReleaseParseError(parseError) 37 | 38 | def buildMetadataParseError(parseError: ParseError): ParseError = 39 | BuildMetadataParseError(parseError) 40 | 41 | def combine(preReleaseError: ParseError, buildMetadataError: ParseError): ParseError = 42 | CombinedParseError( 43 | preReleaseParseError(preReleaseError), 44 | buildMetadataParseError(buildMetadataError) 45 | ) 46 | 47 | def invalidVersionStringError(value: String): ParseError = 48 | InvalidVersionStringError(value) 49 | 50 | def semVerMatchersParseErrors(error: SemVerMatchers.ParseErrors): ParseError = SemVerMatchersParseErrors(error) 51 | 52 | def fromAdditionalInfoParserError(additionalInfoParseError: AdditionalInfo.AdditionalInfoParseError): ParseError = 53 | additionalInfoParseError match { 54 | case AdditionalInfo.AdditionalInfoParseError.LeadingZeroNumError(n) => 55 | ParseError.leadingZeroNumError(n) 56 | case AdditionalInfo.AdditionalInfoParseError.InvalidAlphaNumHyphenError(c, rest) => 57 | ParseError.invalidAlphaNumHyphenError(c, rest) 58 | case AdditionalInfo.AdditionalInfoParseError.EmptyAlphaNumHyphenError => 59 | ParseError.emptyAlphaNumHyphenError 60 | } 61 | 62 | @SuppressWarnings(Array("org.wartremover.warts.Recursion")) 63 | def render(parseError: ParseError): String = parseError match { 64 | case InvalidAlphaNumHyphenError(c, rest) => 65 | s"Invalid char for AlphaNumHyphen found. value: ${c.toString} / rest: ${rest.toString}" 66 | 67 | case EmptyAlphaNumHyphenError => 68 | "AlphaNumHyphen cannot be empty but the given value is an empty String." 69 | 70 | case LeadingZeroNumError(n) => 71 | s"Invalid Num value. It should not have any leading zeros. value: $n" 72 | 73 | case PreReleaseParseError(error) => 74 | s"Error in parsing pre-release: ${render(error)}" 75 | 76 | case BuildMetadataParseError(error) => 77 | s"Error in parsing build meta data: ${render(error)}" 78 | 79 | case CombinedParseError(preReleaseError, buildMetadataError) => 80 | s"""Errors: 81 | |[1] ${render(preReleaseError)} 82 | |[2] ${render(buildMetadataError)} 83 | |""".stripMargin 84 | 85 | case InvalidVersionStringError(value) => 86 | s"Invalid SemVer String. value: $value" 87 | 88 | case SemVerMatchersParseErrors(error) => 89 | s"Error when parsing SemVerMatchers: ${error.render}" 90 | } 91 | 92 | implicit final class ParseErrorOps(private val parseError: ParseError) extends AnyVal { 93 | def render: String = just.semver.ParseError.render(parseError) 94 | } 95 | 96 | } 97 | -------------------------------------------------------------------------------- /modules/just-semver-decver/shared/src/test/scala/just/decver/matcher/Gens.scala: -------------------------------------------------------------------------------- 1 | package just.decver.matcher 2 | 3 | import hedgehog._ 4 | import just.decver.{DecVer, DecVerGens} 5 | import just.semver.expr.ComparisonOperator 6 | 7 | /** @author Kevin Lee 8 | * @since 2022-04-03 9 | */ 10 | object Gens { 11 | 12 | def genComparisonOperator: Gen[ComparisonOperator] = 13 | Gen.element1( 14 | ComparisonOperator.lt, 15 | ComparisonOperator.le, 16 | ComparisonOperator.eql, 17 | ComparisonOperator.ne, 18 | ComparisonOperator.gt, 19 | ComparisonOperator.ge 20 | ) 21 | 22 | def genDecVerMatcherRange( 23 | majorRange: Range[Int], 24 | minorRange: Range[Int], 25 | ): Gen[DecVerMatcher] = genDecVerMatcherRangeAndDecVerInRange( 26 | majorRange, 27 | minorRange, 28 | ).map { case (matcher, _) => matcher } 29 | 30 | def genDecVerMatcherRangeAndDecVerInRange( 31 | majorRange: Range[Int], 32 | minorRange: Range[Int], 33 | ): Gen[(DecVerMatcher, DecVer)] = for { 34 | v1 <- DecVerGens.genDecVerWithRange(majorRange, minorRange) 35 | one <- Gen.element1(1, 2, 4) 36 | (m, n, p) = ((4 & one) >> 2, (2 & one) >> 1, 1 & one) 37 | v2 <- DecVerGens.genDecVerWithRange( 38 | Range.linear(v1.major.value + m, v1.major.value + 100), 39 | Range.linear(v1.minor.value + n, v1.minor.value + 100), 40 | ) 41 | semVer <- DecVerGens.genDecVerWithRange( 42 | Range.linear(v1.major.value, v2.major.value), 43 | Range.linear(v1.minor.value, v2.minor.value), 44 | ) 45 | } yield (DecVerMatcher.range(v1, v2), v1.copy(major = semVer.major, minor = semVer.minor)) 46 | 47 | def genDecVerMatcherComparison( 48 | majorRange: Range[Int], 49 | minorRange: Range[Int], 50 | ): Gen[DecVerMatcher] = for { 51 | op <- genComparisonOperator 52 | semVer <- DecVerGens.genDecVerWithRange( 53 | majorRange = majorRange, 54 | minorRange = minorRange, 55 | ) 56 | } yield DecVerMatcher.comparison(DecVerComparison(op, semVer)) 57 | 58 | def genDecVerMatcher: Gen[DecVerMatcher] = 59 | Gen.frequency1( 60 | 20 -> genDecVerMatcherRange( 61 | Range.linear(0, 100), 62 | Range.linear(0, 100), 63 | ), 64 | 80 -> genDecVerMatcherComparison( 65 | majorRange = Range.linear(0, 100), 66 | minorRange = Range.linear(0, 500), 67 | ) 68 | ) 69 | 70 | def genRangedDecVerComparison( 71 | majorRange: Range[Int], 72 | minorRange: Range[Int], 73 | ): Gen[(DecVerComparison, DecVerComparison, DecVer)] = for { 74 | v1DecVer <- DecVerGens.genDecVerWithRange(majorRange, minorRange) 75 | m <- Gen.int(Range.linear(1, 10)) 76 | n <- Gen.int(Range.linear(1, 10)) 77 | inclusive <- Gen.boolean 78 | } yield { 79 | inclusive match { 80 | case false => 81 | val semVer = v1DecVer.copy( 82 | major = DecVer.Major(v1DecVer.major.value + m), 83 | minor = DecVer.Minor(v1DecVer.minor.value + n), 84 | ) 85 | val v1 = DecVerComparison( 86 | ComparisonOperator.gt, 87 | v1DecVer 88 | ) 89 | val v2 = DecVerComparison( 90 | ComparisonOperator.lt, 91 | v1.decVer 92 | .copy( 93 | major = DecVer.Major(semVer.major.value + m), 94 | minor = DecVer.Minor(semVer.minor.value + n), 95 | ) 96 | ) 97 | (v1, v2, semVer) 98 | case true => 99 | val semVer = v1DecVer.copy( 100 | major = DecVer.Major(v1DecVer.major.value + m), 101 | minor = DecVer.Minor(v1DecVer.minor.value + n), 102 | ) 103 | val v1 = DecVerComparison( 104 | ComparisonOperator.ge, 105 | v1DecVer 106 | ) 107 | val v2 = DecVerComparison( 108 | ComparisonOperator.le, 109 | v1.decVer 110 | .copy( 111 | major = DecVer.Major(semVer.major.value + m), 112 | minor = DecVer.Minor(semVer.minor.value + n), 113 | ) 114 | ) 115 | (v1, v2, semVer) 116 | } 117 | 118 | } 119 | } 120 | -------------------------------------------------------------------------------- /docs/semver/semver.md: -------------------------------------------------------------------------------- 1 | 2 | # SemVer (Semantic Version) 3 | 4 | :::caution NOTE 5 | For now, please do not use any types and methods from the package other than `just.semver`. 6 | * `just.semver`: Fine 7 | * `just.semver.matcher` or any other `just.semver.xxx` packages: You can use it but not recommended as it's currently experimental. 8 | ::: 9 | 10 | *** 11 | 12 | It requires the `just-semver-core` module. 13 | 14 | ```scala 15 | "io.kevinlee" %% "just-semver-core" % "@VERSION@" 16 | ``` 17 | 18 | ```scala 19 | "io.kevinlee" %%% "just-semver-core" % "@VERSION@" 20 | ``` 21 | 22 | 23 | ## `SemVer.parse` 24 | 25 | ```scala mdoc:reset-object 26 | import just.semver.SemVer 27 | 28 | val v = SemVer.parse("1.0.0") 29 | 30 | // To render it to `String`, 31 | v.map(_.render) 32 | 33 | // Invalid version 34 | SemVer.parse("a1.0.0") 35 | 36 | ``` 37 | 38 | ## `SemVer.unsafeParse` 39 | 40 | ```scala mdoc:reset-object 41 | import just.semver.SemVer 42 | 43 | // parse unsafe - NOT RECOMMENDED!!! 44 | val v = SemVer.unsafeParse("1.0.0") 45 | 46 | // to String 47 | v.render 48 | ``` 49 | 50 | ```scala mdoc:crash 51 | 52 | // Invalid version 53 | SemVer.unsafeParse("a1.0.0") 54 | ``` 55 | 56 | ## SemVer with `pre-release` info 57 | ```scala mdoc:reset-object 58 | import just.semver.SemVer 59 | 60 | SemVer.parse("1.0.0-beta1") 61 | 62 | val v = SemVer.parse("1.0.0-3.123.9a") 63 | 64 | v.map(_.render) 65 | ``` 66 | 67 | ## SemVer with build `meta-info` 68 | ```scala mdoc:reset-object 69 | import just.semver.SemVer 70 | 71 | val v = SemVer.parse("1.0.0+100.0.12abc") 72 | 73 | v.map(_.render) 74 | ``` 75 | 76 | ## SemVer with `pre-release` info and build `meta-info` 77 | ```scala mdoc:reset-object 78 | import just.semver.SemVer 79 | 80 | SemVer.parse("1.0.0-beta1") 81 | 82 | val v = SemVer.parse("1.0.0-3.123.9a+100.0.12abc") 83 | 84 | v.map(_.render) 85 | ``` 86 | 87 | ## Compare `SemVer` 88 | ```scala mdoc:reset-object 89 | import just.semver.SemVer 90 | 91 | for { 92 | a <- SemVer.parse("1.0.0") 93 | b <- SemVer.parse("1.0.1") 94 | } yield a < b 95 | 96 | for { 97 | a <- SemVer.parse("1.0.1") 98 | b <- SemVer.parse("1.0.0") 99 | } yield a < b 100 | 101 | for { 102 | a <- SemVer.parse("1.0.0") 103 | b <- SemVer.parse("1.0.1") 104 | } yield a <= b 105 | 106 | for { 107 | a <- SemVer.parse("1.0.0") 108 | b <- SemVer.parse("1.0.0") 109 | } yield a <= b 110 | 111 | for { 112 | a <- SemVer.parse("1.0.0") 113 | b <- SemVer.parse("1.0.0") 114 | } yield a == b 115 | 116 | for { 117 | a <- SemVer.parse("1.0.1") 118 | b <- SemVer.parse("1.0.0") 119 | } yield a > b 120 | 121 | for { 122 | a <- SemVer.parse("1.0.0") 123 | b <- SemVer.parse("1.0.1") 124 | } yield a > b 125 | 126 | for { 127 | a <- SemVer.parse("1.0.0") 128 | b <- SemVer.parse("1.0.1") 129 | } yield a >= b 130 | 131 | for { 132 | a <- SemVer.parse("1.0.0") 133 | b <- SemVer.parse("1.0.0") 134 | } yield a >= b 135 | 136 | for { 137 | a <- SemVer.parse("1.0.1") 138 | b <- SemVer.parse("1.0.0") 139 | } yield a >= b 140 | ``` 141 | 142 | ## Matchers 143 | ```scala mdoc 144 | SemVer.unsafeParse("1.0.0").unsafeMatches("1.0.0 - 2.0.0") 145 | SemVer.unsafeParse("1.5.0").unsafeMatches("1.0.0 - 2.0.0") 146 | SemVer.unsafeParse("2.0.0").unsafeMatches("1.0.0 - 2.0.0") 147 | SemVer.unsafeParse("0.9.9").unsafeMatches("1.0.0 - 2.0.0") 148 | SemVer.unsafeParse("2.0.1").unsafeMatches("1.0.0 - 2.0.0") 149 | 150 | SemVer.unsafeParse("1.0.0").unsafeMatches(">1.0.0 <2.0.0") 151 | SemVer.unsafeParse("1.0.0").unsafeMatches(">=1.0.0 <=2.0.0") 152 | SemVer.unsafeParse("1.5.0").unsafeMatches(">1.0.0 <2.0.0") 153 | SemVer.unsafeParse("2.0.0").unsafeMatches(">1.0.0 <2.0.0") 154 | SemVer.unsafeParse("2.0.0").unsafeMatches(">=1.0.0 <=2.0.0") 155 | SemVer.unsafeParse("0.9.9").unsafeMatches(">=1.0.0 <=2.0.0") 156 | SemVer.unsafeParse("2.0.1").unsafeMatches(">=1.0.0 <=2.0.0") 157 | 158 | SemVer.unsafeParse("1.0.0").unsafeMatches("1.0.0 - 2.0.0 || >3.0.0 <4.0.0") 159 | SemVer.unsafeParse("2.0.0").unsafeMatches("1.0.0 - 2.0.0 || >3.0.0 <4.0.0") 160 | SemVer.unsafeParse("3.0.0").unsafeMatches("1.0.0 - 2.0.0 || >3.0.0 <=4.0.0") 161 | SemVer.unsafeParse("3.0.1").unsafeMatches("1.0.0 - 2.0.0 || >3.0.0 <=4.0.0") 162 | SemVer.unsafeParse("4.0.0").unsafeMatches("1.0.0 - 2.0.0 || >3.0.0 <=4.0.0") 163 | ``` 164 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/test/scala/just/semver/matcher/Gens.scala: -------------------------------------------------------------------------------- 1 | package just.semver.matcher 2 | 3 | import hedgehog._ 4 | import just.semver.expr.ComparisonOperator 5 | import just.semver.{SemVer, Gens => SemVerGens} 6 | 7 | /** @author Kevin Lee 8 | * @since 2022-04-03 9 | */ 10 | object Gens { 11 | 12 | def genComparisonOperator: Gen[ComparisonOperator] = 13 | Gen.element1( 14 | ComparisonOperator.lt, 15 | ComparisonOperator.le, 16 | ComparisonOperator.eql, 17 | ComparisonOperator.ne, 18 | ComparisonOperator.gt, 19 | ComparisonOperator.ge 20 | ) 21 | 22 | def genSemVerMatcherRange( 23 | majorRange: Range[Int], 24 | minorRange: Range[Int], 25 | patchRange: Range[Int] 26 | ): Gen[SemVerMatcher] = genSemVerMatcherRangeAndSemVerInRange( 27 | majorRange, 28 | minorRange, 29 | patchRange 30 | ).map { case (matcher, _) => matcher } 31 | 32 | def genSemVerMatcherRangeAndSemVerInRange( 33 | majorRange: Range[Int], 34 | minorRange: Range[Int], 35 | patchRange: Range[Int] 36 | ): Gen[(SemVerMatcher, SemVer)] = for { 37 | v1 <- SemVerGens.genSemVerWithRange(majorRange, minorRange, patchRange) 38 | one <- Gen.element1(1, 2, 4) 39 | (m, n, p) = ((4 & one) >> 2, (2 & one) >> 1, 1 & one) 40 | v2 <- SemVerGens.genSemVerWithRange( 41 | Range.linear(v1.major.value + m, v1.major.value + 100), 42 | Range.linear(v1.minor.value + n, v1.minor.value + 100), 43 | Range.linear(v1.patch.value + p, v1.patch.value + 100) 44 | ) 45 | semVer <- SemVerGens.genSemVerWithRange( 46 | Range.linear(v1.major.value, v2.major.value), 47 | Range.linear(v1.minor.value, v2.minor.value), 48 | Range.linear(v1.patch.value, v2.patch.value) 49 | ) 50 | } yield (SemVerMatcher.range(v1, v2), v1.copy(major = semVer.major, minor = semVer.minor, patch = semVer.patch)) 51 | 52 | def genSemVerMatcherComparison( 53 | majorRange: Range[Int], 54 | minorRange: Range[Int], 55 | patchRange: Range[Int] 56 | ): Gen[SemVerMatcher] = for { 57 | op <- genComparisonOperator 58 | semVer <- SemVerGens.genSemVerWithRange( 59 | majorRange = majorRange, 60 | minorRange = minorRange, 61 | patchRange = patchRange 62 | ) 63 | } yield SemVerMatcher.comparison(SemVerComparison(op, semVer)) 64 | 65 | def genSemVerMatcher: Gen[SemVerMatcher] = 66 | Gen.frequency1( 67 | 20 -> genSemVerMatcherRange( 68 | Range.linear(0, 100), 69 | Range.linear(0, 100), 70 | Range.linear(0, 1000) 71 | ), 72 | 80 -> genSemVerMatcherComparison( 73 | majorRange = Range.linear(0, 100), 74 | minorRange = Range.linear(0, 500), 75 | patchRange = Range.linear(0, 1000) 76 | ) 77 | ) 78 | 79 | def genRangedSemVerComparison( 80 | majorRange: Range[Int], 81 | minorRange: Range[Int], 82 | patchRange: Range[Int] 83 | ): Gen[(SemVerComparison, SemVerComparison, SemVer)] = for { 84 | v1SemVer <- SemVerGens.genSemVerWithRange(majorRange, minorRange, patchRange) 85 | m <- Gen.int(Range.linear(1, 10)) 86 | n <- Gen.int(Range.linear(1, 10)) 87 | p <- Gen.int(Range.linear(1, 10)) 88 | inclusive <- Gen.boolean 89 | } yield { 90 | inclusive match { 91 | case false => 92 | val semVer = v1SemVer.copy( 93 | major = SemVer.Major(v1SemVer.major.value + m), 94 | minor = SemVer.Minor(v1SemVer.minor.value + n), 95 | patch = SemVer.Patch(v1SemVer.patch.value + p) 96 | ) 97 | val v1 = SemVerComparison( 98 | ComparisonOperator.gt, 99 | v1SemVer 100 | ) 101 | val v2 = SemVerComparison( 102 | ComparisonOperator.lt, 103 | v1.semVer 104 | .copy( 105 | major = SemVer.Major(semVer.major.value + m), 106 | minor = SemVer.Minor(semVer.minor.value + n), 107 | patch = SemVer.Patch(semVer.patch.value + p) 108 | ) 109 | ) 110 | (v1, v2, semVer) 111 | case true => 112 | val semVer = v1SemVer.copy( 113 | major = SemVer.Major(v1SemVer.major.value + m), 114 | minor = SemVer.Minor(v1SemVer.minor.value + n), 115 | patch = SemVer.Patch(v1SemVer.patch.value + p) 116 | ) 117 | val v1 = SemVerComparison( 118 | ComparisonOperator.ge, 119 | v1SemVer 120 | ) 121 | val v2 = SemVerComparison( 122 | ComparisonOperator.le, 123 | v1.semVer 124 | .copy( 125 | major = SemVer.Major(semVer.major.value + m), 126 | minor = SemVer.Minor(semVer.minor.value + n), 127 | patch = SemVer.Patch(semVer.patch.value + p) 128 | ) 129 | ) 130 | (v1, v2, semVer) 131 | } 132 | 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /website/docusaurus.config.ts: -------------------------------------------------------------------------------- 1 | // @ts-check 2 | // Note: type annotations allow type checking and IDEs autocompletion 3 | import {themes as prismThemes} from 'prism-react-renderer'; 4 | import type {Config} from '@docusaurus/types'; 5 | import type * as Preset from '@docusaurus/preset-classic'; 6 | 7 | const algoliaConfig = require('./algolia.config.json'); 8 | const googleAnalyticsConfig = require('./google-analytics.config.json'); 9 | 10 | // const lightCodeTheme = require('prism-react-renderer/themes/github'); 11 | // const darkCodeTheme = require('prism-react-renderer/themes/dracula'); 12 | const lightCodeTheme = prismThemes.nightOwlLight; 13 | const darkCodeTheme = prismThemes.nightOwl; 14 | 15 | const isEmptyObject = (obj: object) => Object.keys(obj).length === 0; 16 | 17 | const isSearchable = !isEmptyObject(algoliaConfig); 18 | const hasGoogleAnalytics = !isEmptyObject(googleAnalyticsConfig); 19 | 20 | const classicConfig = { 21 | docs: { 22 | path: '../generated-docs/docs/', 23 | sidebarPath: require.resolve('./sidebars.js'), 24 | lastVersion: 'current', 25 | "versions": { 26 | "current": { 27 | "label": "1.1.1" 28 | }, 29 | } 30 | // Please change this to your repo. 31 | // Remove this to remove the "edit this page" links. 32 | }, 33 | theme: { 34 | customCss: require.resolve('./src/css/custom.css'), 35 | }, 36 | } 37 | 38 | 39 | if (hasGoogleAnalytics) { 40 | classicConfig['gtag'] = googleAnalyticsConfig; 41 | } 42 | 43 | /** @type {import('@docusaurus/types').Config} */ 44 | const config = { 45 | title: 'Just SemVer', 46 | tagline: 'Just a Semantic Version Library', 47 | url: 'https://just-semver.kevinly.dev', 48 | baseUrl: '/', 49 | onBrokenLinks: 'throw', 50 | onBrokenMarkdownLinks: 'warn', 51 | favicon: 'img/favicon.png', 52 | 53 | // GitHub pages deployment config. 54 | // If you aren't using GitHub pages, you don't need these. 55 | organizationName: 'Kevin-Lee', // Usually your GitHub org/user name. 56 | projectName: 'just-semver', // Usually your repo name. 57 | 58 | // Even if you don't use internalization, you can use this field to set useful 59 | // metadata like html lang. For example, if your site is Chinese, you may want 60 | // to replace "en" with "zh-Hans". 61 | i18n: { 62 | defaultLocale: 'en', 63 | locales: ['en'], 64 | }, 65 | 66 | presets: [ 67 | [ 68 | 'classic', 69 | /** @type {import('@docusaurus/preset-classic').Options} */ 70 | (classicConfig), 71 | ], 72 | ], 73 | 74 | themeConfig: 75 | /** @type {import('@docusaurus/preset-classic').ThemeConfig} */ 76 | ({ 77 | image: 'img/poster.png', 78 | navbar: { 79 | title: 'Just SemVer', 80 | logo: { 81 | alt: 'Just SemVer', 82 | src: 'img/just-semver-logo-32x32.png', 83 | }, 84 | items: [ 85 | { 86 | type: 'doc', 87 | docId: 'intro', 88 | position: 'left', 89 | label: 'Docs', 90 | }, 91 | { 92 | type: 'docsVersionDropdown', 93 | position: 'right', 94 | dropdownActiveClassDisabled: true, 95 | dropdownItemsAfter: [ 96 | { 97 | to: '/versions', 98 | label: 'All versions', 99 | }, 100 | ], 101 | }, 102 | { 103 | href: 'https://github.com/Kevin-Lee/just-semver', 104 | label: 'GitHub', 105 | position: 'right', 106 | }, 107 | ], 108 | }, 109 | footer: { 110 | style: 'dark', 111 | links: [ 112 | { 113 | title: 'Docs', 114 | items: [ 115 | { 116 | label: 'Getting Started', 117 | to: '/docs', 118 | }, 119 | ], 120 | }, 121 | { 122 | title: 'More', 123 | items: [ 124 | { 125 | label: 'GitHub', 126 | href: 'https://github.com/Kevin-Lee/just-semver', 127 | }, 128 | ], 129 | }, 130 | ], 131 | copyright: `Copyright © ${new Date().getFullYear()} Just SemVer written by Kevin Lee, The website built with Docusaurus. 132 |
133 | History icons created by juicy_fish - Flaticon, 134 | Null icons created by Freepik - Flaticon and 135 | Scala icons created by Freepik - Flaticon 136 |
137 | `, 138 | }, 139 | prism: { 140 | theme: lightCodeTheme, 141 | darkTheme: darkCodeTheme, 142 | additionalLanguages: [ 143 | 'java', 144 | 'scala', 145 | ], 146 | } 147 | }), 148 | }; 149 | 150 | if (isSearchable) { 151 | config['themeConfig']['algolia'] = algoliaConfig; 152 | } 153 | 154 | module.exports = config; 155 | -------------------------------------------------------------------------------- /website/src/pages/versions.tsx: -------------------------------------------------------------------------------- 1 | /** 2 | * Copyright (c) Facebook, Inc. and its affiliates. 3 | * 4 | * This source code is licensed under the MIT license found in the 5 | * LICENSE file in the root directory of this source tree. 6 | */ 7 | import React from 'react'; 8 | import useDocusaurusContext from '@docusaurus/useDocusaurusContext'; 9 | import Link from '@docusaurus/Link'; 10 | import Layout from '@theme/Layout'; 11 | 12 | import { 13 | useVersions, 14 | useLatestVersion, 15 | } from '@docusaurus/plugin-content-docs/client'; 16 | 17 | import VersionsArchived from './versionsArchived.json'; 18 | 19 | const docsPluginId = undefined; 20 | 21 | function Version() { 22 | const {siteConfig} = useDocusaurusContext(); 23 | const versions = useVersions(docsPluginId); 24 | const latestVersion = useLatestVersion(docsPluginId); 25 | const currentVersion = versions.find((version) => version.name === 'current'); 26 | const pastVersions = versions.filter( 27 | (version) => version !== latestVersion && version.name !== 'current', 28 | ).concat( 29 | VersionsArchived 30 | ).sort((a, b) => { 31 | if (!a.name.includes(".") || !b.name.includes(".")) { 32 | if (a.name.includes("v")) { 33 | const aVersion = parseInt(a.name.substring(1).split(".")[0]); 34 | if (b.name.includes("v")) { 35 | const bVersion = parseInt(b.name.substring(1).split(".")[0]); 36 | return bVersion - aVersion; 37 | } else { 38 | const bVersion = parseInt(b.name.split(".")[0]); 39 | return bVersion - aVersion; 40 | } 41 | } else { 42 | const aVersion = parseInt(a.name.split(".")[0]); 43 | if (b.name.includes("v")) { 44 | const bVersion = parseInt(b.name.substring(1).split(".")[0]); 45 | return bVersion - aVersion; 46 | } else { 47 | const bVersion = parseInt(b.name.split(".")[0]); 48 | return bVersion - aVersion; 49 | } 50 | } 51 | } 52 | const [aMajor, aMinor, aPatchAndMore] = a.name.split("."); 53 | const [aPatch] = aPatchAndMore.split("-"); 54 | const [bMajor, bMinor, bPatchAndMore] = b.name.split("."); 55 | const [bPatch] = bPatchAndMore.split("-"); 56 | 57 | const a1 = parseInt(aMajor); 58 | const a2 = parseInt(aMinor); 59 | const a3 = parseInt(aPatch); 60 | 61 | const b1 = parseInt(bMajor); 62 | const b2 = parseInt(bMinor); 63 | const b3 = parseInt(bPatch); 64 | 65 | if (a1 > b1) { 66 | return -1; 67 | } else if (a1 === b1) { 68 | if (a2 > b2) { 69 | return -1; 70 | } else if (a2 === b2) { 71 | if (a3 > b3) { 72 | return -1; 73 | } else if (a3 === b3) { 74 | return 0; 75 | } else { 76 | return 1; 77 | } 78 | } else { 79 | return 1; 80 | } 81 | } else { 82 | return 1; 83 | } 84 | }); 85 | console.log(JSON.stringify(pastVersions)); 86 | const stableVersion = currentVersion; 87 | const repoUrl = `https://github.com/${siteConfig.organizationName}/${siteConfig.projectName}`; 88 | 89 | const docLink = path => path ? Documentation :  ; 90 | 91 | const releaseLink = version => version.label !== "v1" ? 92 | Release Notes :  ; 93 | 94 | const spaces = howMany => ; 95 | 96 | return ( 97 | 100 |
101 |

Effectie documentation versions

102 | 103 | {stableVersion && ( 104 |
105 |

Current version (Stable)

106 |

107 | Here you can find the documentation for current released version. 108 |

109 | 110 | 111 | 112 | 113 | 116 | 121 | 122 | 123 |
{stableVersion.label} 114 | Documentation 115 | 117 | 118 | Release Notes 119 | 120 |
124 |
125 | )} 126 | 127 | {pastVersions.length > 0 && ( 128 |
129 |

Past versions (Not maintained anymore)

130 |

131 | Here you can find documentation for previous versions of 132 | Effectie. 133 |

134 | 135 | 136 | {pastVersions.map((version) => ( 137 | 138 | 139 | 142 | 145 | 146 | ))} 147 | 148 |
{version.label} 140 | {docLink(version.path)} 141 | 143 | {releaseLink(version)} 144 |
149 |
150 | )} 151 | 152 |
153 |
154 | ); 155 | } 156 | 157 | export default Version; -------------------------------------------------------------------------------- /modules/just-semver-decver/shared/src/test/scala/just/decver/matcher/DecVerComparisonSpec.scala: -------------------------------------------------------------------------------- 1 | package just.decver.matcher 2 | 3 | import hedgehog._ 4 | import hedgehog.runner._ 5 | import just.decver.DecVerGens 6 | 7 | import scala.util.{Failure, Success, Try} 8 | 9 | /** @author Kevin Lee 10 | * @since 2022-04-07 11 | */ 12 | object DecVerComparisonSpec extends Properties { 13 | override def tests: List[Test] = List( 14 | property("test DecVerComparison.parse(Valid)", testDecVerComparisonParseValid), 15 | property( 16 | "test DecVerComparison.parse(Invalid comparison operator)", 17 | testDecVerComparisonParseInvalidComparisonOperator 18 | ), 19 | property( 20 | "test DecVerComparison.parse(Invalid DecVer)", 21 | testDecVerComparisonParseInvalidDecVer 22 | ), 23 | property("test DecVerComparison.unsafeParse(Valid)", testDecVerComparisonUnsafeParseValid), 24 | property( 25 | "test DecVerComparison.unsafeParse(Invalid comparison operator)", 26 | testDecVerComparisonUnsafeParseInvalidComparisonOperator 27 | ), 28 | property( 29 | "test DecVerComparison.unsafeParse(Invalid DecVer)", 30 | testDecVerComparisonUnsafeParseInvalidDecVer 31 | ) 32 | ) 33 | 34 | def testDecVerComparisonParseValid: Property = for { 35 | op <- Gens.genComparisonOperator.log("op") 36 | decVer <- DecVerGens.genDecVer.log("decVer") 37 | } yield { 38 | val input = s"${op.render}${decVer.render}" 39 | DecVerComparison.parse(input) match { 40 | case Right(actual) => 41 | actual.comparisonOperator ==== op and actual.decVer ==== decVer 42 | case Left(err) => 43 | Result.failure.log(s"Parsed DecVerComparison expected but got error instead: ${err.render}") 44 | } 45 | } 46 | 47 | def testDecVerComparisonParseInvalidComparisonOperator: Property = for { 48 | op <- Gens.genComparisonOperator.log("op") 49 | decVer <- DecVerGens.genDecVer.log("decVer") 50 | } yield { 51 | val input = s"${op.render}${op.render}${op.render}${decVer.render}" 52 | DecVerComparison.parse(input) match { 53 | case Right(actual) => 54 | Result 55 | .failure 56 | .log(s"DecVerComparisonParseError expected but got parsed DecVerComparison: ${actual.render}") 57 | case Left(actual) => 58 | val expectedMessage = s"Failed to parse operator from $input" 59 | actual matchPattern { 60 | case DecVerComparison.ParseError(`expectedMessage`, _: String, None) => 61 | } 62 | } 63 | } 64 | 65 | def testDecVerComparisonParseInvalidDecVer: Property = for { 66 | op <- Gens.genComparisonOperator.log("op") 67 | decVer <- DecVerGens.genDecVer.log("decVer") 68 | } yield { 69 | val input = s"${op.render}${decVer.renderMajorMinor}.${decVer.render}" 70 | DecVerComparison.parse(input) match { 71 | case Right(actual) => 72 | Result 73 | .failure 74 | .log(s"DecVerComparisonParseError expected but got parsed DecVerComparison: ${actual.render}") 75 | case Left(actual) => 76 | val opRendered = op.render 77 | val expectedMessage = s"Parsing operator succeeded but failed to parse DecVer from $input" 78 | (actual matchPattern { 79 | case DecVerComparison.ParseError( 80 | `expectedMessage`, 81 | _: String, 82 | Some(`opRendered`) 83 | ) => 84 | }).log(s"${actual.render} doesn't match the given pattern") 85 | } 86 | } 87 | 88 | def testDecVerComparisonUnsafeParseValid: Property = for { 89 | op <- Gens.genComparisonOperator.log("op") 90 | decVer <- DecVerGens.genDecVer.log("decVer") 91 | } yield { 92 | val input = s"${op.render}${decVer.render}" 93 | val actual = DecVerComparison.unsafeParse(input) 94 | actual.comparisonOperator ==== op and actual.decVer ==== decVer 95 | } 96 | 97 | def testDecVerComparisonUnsafeParseInvalidComparisonOperator: Property = for { 98 | op <- Gens.genComparisonOperator.log("op") 99 | decVer <- DecVerGens.genDecVer.log("decVer") 100 | } yield { 101 | val input = s"${op.render}${op.render}${op.render}${decVer.render}" 102 | Try(DecVerComparison.unsafeParse(input)) match { 103 | case Success(actual) => 104 | Result 105 | .failure 106 | .log(s"DecVerComparisonParseError expected but got parsed DecVerComparison: ${actual.render}") 107 | case Failure(actual) => 108 | val errorMessage = actual.getMessage 109 | Result.all( 110 | List( 111 | Result.diff(errorMessage, s"Failed to parse operator from $input")(_.contains(_)), 112 | Result.diff(errorMessage, s"Success:)")(_.endsWith(_)) 113 | ) 114 | ) 115 | } 116 | } 117 | 118 | def testDecVerComparisonUnsafeParseInvalidDecVer: Property = for { 119 | op <- Gens.genComparisonOperator.log("op") 120 | decVer <- DecVerGens.genDecVer.log("decVer") 121 | } yield { 122 | val input = s"${op.render}${decVer.renderMajorMinor}.${decVer.render}" 123 | Try(DecVerComparison.unsafeParse(input)) match { 124 | case Success(actual) => 125 | Result 126 | .failure 127 | .log(s"DecVerComparisonParseError expected but got parsed DecVerComparison: ${actual.render}") 128 | case Failure(actual) => 129 | val opRendered = op.render 130 | val errorMessage = actual.getMessage 131 | Result.all( 132 | List( 133 | Result.diff(errorMessage, s"Parsing operator succeeded but failed to parse DecVer from $input")( 134 | _.contains(_) 135 | ), 136 | Result.diff(errorMessage, s"Success: $opRendered)")(_.endsWith(_)) 137 | ) 138 | ) 139 | } 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/test/scala/just/semver/matcher/SemVerComparisonSpec.scala: -------------------------------------------------------------------------------- 1 | package just.semver.matcher 2 | 3 | import hedgehog._ 4 | import hedgehog.runner._ 5 | import just.semver.{Gens => SemVerGens} 6 | 7 | import scala.util.{Failure, Success, Try} 8 | 9 | /** @author Kevin Lee 10 | * @since 2022-04-07 11 | */ 12 | object SemVerComparisonSpec extends Properties { 13 | override def tests: List[Test] = List( 14 | property("test SemVerComparison.parse(Valid)", testSemVerComparisonParseValid), 15 | property( 16 | "test SemVerComparison.parse(Invalid comparison operator)", 17 | testSemVerComparisonParseInvalidComparisonOperator 18 | ), 19 | property( 20 | "test SemVerComparison.parse(Invalid SemVer)", 21 | testSemVerComparisonParseInvalidSemVer 22 | ), 23 | property("test SemVerComparison.unsafeParse(Valid)", testSemVerComparisonUnsafeParseValid), 24 | property( 25 | "test SemVerComparison.unsafeParse(Invalid comparison operator)", 26 | testSemVerComparisonUnsafeParseInvalidComparisonOperator 27 | ), 28 | property( 29 | "test SemVerComparison.unsafeParse(Invalid SemVer)", 30 | testSemVerComparisonUnsafeParseInvalidSemVer 31 | ) 32 | ) 33 | 34 | def testSemVerComparisonParseValid: Property = for { 35 | op <- Gens.genComparisonOperator.log("op") 36 | semVer <- SemVerGens.genSemVer.log("semVer") 37 | } yield { 38 | val input = s"${op.render}${semVer.render}" 39 | SemVerComparison.parse(input) match { 40 | case Right(actual) => 41 | actual.comparisonOperator ==== op and actual.semVer ==== semVer 42 | case Left(err) => 43 | Result.failure.log(s"Parsed SemVerComparison expected but got error instead: ${err.render}") 44 | } 45 | } 46 | 47 | def testSemVerComparisonParseInvalidComparisonOperator: Property = for { 48 | op <- Gens.genComparisonOperator.log("op") 49 | semVer <- SemVerGens.genSemVer.log("semVer") 50 | } yield { 51 | val input = s"${op.render}${op.render}${op.render}${semVer.render}" 52 | SemVerComparison.parse(input) match { 53 | case Right(actual) => 54 | Result 55 | .failure 56 | .log(s"SemVerComparisonParseError expected but got parsed SemVerComparison: ${actual.render}") 57 | case Left(actual) => 58 | val expectedMessage = s"Failed to parse operator from $input" 59 | actual matchPattern { 60 | case SemVerComparison.ParseError(`expectedMessage`, _: String, None) => 61 | } 62 | } 63 | } 64 | 65 | def testSemVerComparisonParseInvalidSemVer: Property = for { 66 | op <- Gens.genComparisonOperator.log("op") 67 | semVer <- SemVerGens.genSemVer.log("semVer") 68 | } yield { 69 | val input = s"${op.render}${semVer.renderMajorMinorPatch}.${semVer.render}" 70 | SemVerComparison.parse(input) match { 71 | case Right(actual) => 72 | Result 73 | .failure 74 | .log(s"SemVerComparisonParseError expected but got parsed SemVerComparison: ${actual.render}") 75 | case Left(actual) => 76 | val opRendered = op.render 77 | val expectedMessage = s"Parsing operator succeeded but failed to parse SemVer from $input" 78 | (actual matchPattern { 79 | case SemVerComparison.ParseError( 80 | `expectedMessage`, 81 | _: String, 82 | Some(`opRendered`) 83 | ) => 84 | }).log(s"${actual.render} doesn't match the given pattern") 85 | } 86 | } 87 | 88 | def testSemVerComparisonUnsafeParseValid: Property = for { 89 | op <- Gens.genComparisonOperator.log("op") 90 | semVer <- SemVerGens.genSemVer.log("semVer") 91 | } yield { 92 | val input = s"${op.render}${semVer.render}" 93 | val actual = SemVerComparison.unsafeParse(input) 94 | actual.comparisonOperator ==== op and actual.semVer ==== semVer 95 | } 96 | 97 | def testSemVerComparisonUnsafeParseInvalidComparisonOperator: Property = for { 98 | op <- Gens.genComparisonOperator.log("op") 99 | semVer <- SemVerGens.genSemVer.log("semVer") 100 | } yield { 101 | val input = s"${op.render}${op.render}${op.render}${semVer.render}" 102 | Try(SemVerComparison.unsafeParse(input)) match { 103 | case Success(actual) => 104 | Result 105 | .failure 106 | .log(s"SemVerComparisonParseError expected but got parsed SemVerComparison: ${actual.render}") 107 | case Failure(actual) => 108 | val errorMessage = actual.getMessage 109 | Result.all( 110 | List( 111 | Result.diff(errorMessage, s"Failed to parse operator from $input")(_.contains(_)), 112 | Result.diff(errorMessage, s"Success:)")(_.endsWith(_)) 113 | ) 114 | ) 115 | } 116 | } 117 | 118 | def testSemVerComparisonUnsafeParseInvalidSemVer: Property = for { 119 | op <- Gens.genComparisonOperator.log("op") 120 | semVer <- SemVerGens.genSemVer.log("semVer") 121 | } yield { 122 | val input = s"${op.render}${semVer.renderMajorMinorPatch}.${semVer.render}" 123 | Try(SemVerComparison.unsafeParse(input)) match { 124 | case Success(actual) => 125 | Result 126 | .failure 127 | .log(s"SemVerComparisonParseError expected but got parsed SemVerComparison: ${actual.render}") 128 | case Failure(actual) => 129 | val opRendered = op.render 130 | val errorMessage = actual.getMessage 131 | Result.all( 132 | List( 133 | Result.diff(errorMessage, s"Parsing operator succeeded but failed to parse SemVer from $input")( 134 | _.contains(_) 135 | ), 136 | Result.diff(errorMessage, s"Success: $opRendered)")(_.endsWith(_)) 137 | ) 138 | ) 139 | } 140 | } 141 | 142 | } 143 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build All 2 | 3 | on: 4 | push: 5 | 6 | pull_request: 7 | branches: 8 | - main 9 | 10 | jobs: 11 | 12 | build: 13 | runs-on: ubuntu-latest 14 | 15 | strategy: 16 | matrix: 17 | scala: 18 | - { name: "Scala 2", version: "2.12.18", binary-version: "2.12", java-version: "11", java-distribution: "temurin", params: "" } 19 | - { name: "Scala 2", version: "2.13.16", binary-version: "2.13", java-version: "11", java-distribution: "temurin", params: "" } 20 | - { name: "Scala 3.1", version: "3.3.5", binary-version: "3", java-version: "11", java-distribution: "temurin", params: '' } 21 | 22 | steps: 23 | - uses: actions/checkout@v6 24 | - uses: actions/setup-java@v5 25 | with: 26 | java-version: ${{ matrix.scala.java-version }} 27 | distribution: ${{ matrix.scala.java-distribution }} 28 | cache: 'sbt' 29 | - uses: sbt/setup-sbt@v1 30 | 31 | - name: "[Push] Build All for Scala ${{ matrix.scala.version }}" 32 | if: github.event_name == 'push' 33 | env: 34 | CURRENT_BRANCH_NAME: ${{ github.ref }} 35 | run: | 36 | echo "[BEFORE]CURRENT_BRANCH_NAME=${CURRENT_BRANCH_NAME}" 37 | export CURRENT_BRANCH_NAME="${CURRENT_BRANCH_NAME#refs/heads/}" 38 | echo " [AFTER]CURRENT_BRANCH_NAME=${CURRENT_BRANCH_NAME}" 39 | java -version 40 | .github/workflows/sbt-build-all.sh ${{ matrix.scala.version }} "${{ matrix.scala.params }}" 41 | 42 | - name: "[PR] Build All for Scala ${{ matrix.scala.version }}" 43 | if: github.event_name == 'pull_request' 44 | env: 45 | CURRENT_BRANCH_NAME: ${{ github.base_ref }} 46 | run: | 47 | echo "Rull request to the '${CURRENT_BRANCH_NAME}' branch" 48 | java -version 49 | .github/workflows/sbt-build-all.sh ${{ matrix.scala.version }} "${{ matrix.scala.params }}" 50 | 51 | scalafix: 52 | runs-on: ubuntu-latest 53 | 54 | env: 55 | GH_JAVA_VERSION: "11" 56 | GH_JAVA_DISTRIBUTION: "temurin" 57 | 58 | steps: 59 | - uses: actions/checkout@v6 60 | - uses: actions/setup-java@v5 61 | with: 62 | java-version: ${{ env.GH_JAVA_VERSION }} 63 | distribution: ${{ env.GH_JAVA_DISTRIBUTION }} 64 | cache: 'sbt' 65 | - uses: sbt/setup-sbt@v1 66 | 67 | - name: Cache SBT 68 | uses: actions/cache@v5 69 | with: 70 | path: | 71 | ~/.ivy2/cache 72 | ~/.cache/coursier 73 | ~/.sbt 74 | key: ${{ runner.os }}-sbt-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('**/build.properties') }} 75 | restore-keys: | 76 | ${{ runner.os }}-sbt 77 | 78 | - name: "sbt +scalafix" 79 | run: | 80 | sbt \ 81 | -J-XX:MaxMetaspaceSize=1024m \ 82 | -J-Xmx2048m \ 83 | clean \ 84 | +scalafixAll 85 | 86 | 87 | build_with_test_coverage: 88 | runs-on: ubuntu-latest 89 | 90 | env: 91 | GH_JAVA_VERSION: "11" 92 | GH_JAVA_DISTRIBUTION: "temurin" 93 | 94 | steps: 95 | - uses: actions/checkout@v6 96 | - uses: actions/setup-java@v5 97 | with: 98 | java-version: ${{ env.GH_JAVA_VERSION }} 99 | distribution: ${{ env.GH_JAVA_DISTRIBUTION }} 100 | cache: 'sbt' 101 | - uses: sbt/setup-sbt@v1 102 | 103 | - name: Cache Coursier 104 | uses: actions/cache@v5 105 | with: 106 | path: ~/.cache/coursier 107 | key: ${{ runner.os }}-coursier-scala-2_13-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('**/build.properties') }} 108 | restore-keys: | 109 | ${{ runner.os }}-coursier-scala-2_13- 110 | 111 | - name: Cache Ivy 112 | uses: actions/cache@v5 113 | with: 114 | path: ~/.ivy2/cache 115 | key: ${{ runner.os }}-ivy-scala-2_13-${{ hashFiles('**/*.sbt') }}-${{ hashFiles('**/build.properties') }} 116 | restore-keys: | 117 | ${{ runner.os }}-ivy-scala-2_13- 118 | 119 | - name: "[Push] Build with Test Coverage - ${{ github.run_number }}" 120 | if: github.event_name == 'push' 121 | env: 122 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} 123 | CURRENT_BRANCH_NAME: ${{ github.ref }} 124 | RUN_ID: ${{ github.run_id }} 125 | RUN_NUMBER: ${{ github.run_number }} 126 | run: | 127 | echo "[BEFORE]CURRENT_BRANCH_NAME=${CURRENT_BRANCH_NAME}" 128 | export CURRENT_BRANCH_NAME="${CURRENT_BRANCH_NAME#refs/heads/}" 129 | echo " [AFTER]CURRENT_BRANCH_NAME=${CURRENT_BRANCH_NAME}" 130 | echo "RUN_ID=${RUN_ID}" 131 | echo "RUN_NUMBER=${RUN_NUMBER}" 132 | export CI_BRANCH=$CURRENT_BRANCH_NAME 133 | .github/workflows/sbt-build-all-with-coverage.sh 2.13.16 134 | 135 | - name: "[PR] Build with Test Coverage - PR-#${{ github.event.pull_request.number }} - ${{ github.run_number }}" 136 | if: github.event_name == 'pull_request' 137 | env: 138 | COVERALLS_REPO_TOKEN: ${{ secrets.COVERALLS_REPO_TOKEN }} 139 | CURRENT_BRANCH_NAME: ${{ github.base_ref }} 140 | RUN_ID: ${{ github.run_id }} 141 | RUN_NUMBER: ${{ github.run_number }} 142 | PR_NUMBER: ${{ github.event.pull_request.number }} 143 | run: | 144 | export CI_BRANCH="PR-$CURRENT_BRANCH_NAME" 145 | echo "RUN_ID=${RUN_ID}" 146 | echo "RUN_NUMBER=${RUN_NUMBER}" 147 | echo "PR #${PR_NUMBER}" 148 | echo "Rull request to the '${CURRENT_BRANCH_NAME}' branch" 149 | .github/workflows/sbt-build-all-with-coverage.sh 2.13.16 150 | 151 | - if: github.event_name == 'pull_request' 152 | uses: codecov/codecov-action@v5 153 | -------------------------------------------------------------------------------- /modules/just-semver-decver/shared/src/test/scala/just/decver/DecVerGens.scala: -------------------------------------------------------------------------------- 1 | package just.decver 2 | 3 | import hedgehog._ 4 | import just.semver.AdditionalInfo.{BuildMetaInfo, PreRelease} 5 | import just.semver.{CommonGens, Gens => SemVerGens} 6 | 7 | /** @author Kevin Lee 8 | * @since 2022-06-10 9 | */ 10 | object DecVerGens { 11 | def genMajor: Gen[DecVer.Major] = 12 | SemVerGens.genNonNegativeInt.map(DecVer.Major(_)) 13 | 14 | def genMinor: Gen[DecVer.Minor] = 15 | SemVerGens.genNonNegativeInt.map(DecVer.Minor(_)) 16 | 17 | def genMajorWithMax(max: Int): Gen[DecVer.Major] = 18 | SemVerGens.genNonNegativeIntWithMax(max).map(DecVer.Major(_)) 19 | 20 | def genMinorWithMax(max: Int): Gen[DecVer.Minor] = 21 | SemVerGens.genNonNegativeIntWithMax(max).map(DecVer.Minor(_)) 22 | 23 | def genMajorWithRange(range: Range[Int]): Gen[DecVer.Major] = 24 | CommonGens.genVersionNumberWithRange(range).map(DecVer.Major(_)) 25 | 26 | def genMinorWithRange(range: Range[Int]): Gen[DecVer.Minor] = 27 | CommonGens.genVersionNumberWithRange(range).map(DecVer.Minor(_)) 28 | 29 | def genDecVer: Gen[DecVer] = 30 | for { 31 | major <- genMajor 32 | minor <- genMinor 33 | pre <- Gen.frequency1[Option[PreRelease]]( 34 | 5 -> Gen.constant(None), 35 | 7 -> SemVerGens.genPreRelease.map(Some(_)) 36 | ) 37 | meta <- Gen.frequency1[Option[BuildMetaInfo]]( 38 | 5 -> Gen.constant(None), 39 | 7 -> SemVerGens.genBuildMetaInfo.map(Some(_)) 40 | ) 41 | } yield DecVer(major, minor, pre, meta) 42 | 43 | def genMinMaxDecVers: Gen[(DecVer, DecVer)] = 44 | for { 45 | (major1, major2) <- SemVerGens.genMinMaxNonNegInts.map(SemVerGens.pairFromIntsTo(DecVer.Major.apply)) 46 | (minor1, minor2) <- SemVerGens.genMinMaxNonNegInts.map(SemVerGens.pairFromIntsTo(DecVer.Minor.apply)) 47 | pre1 <- Gen.frequency1[Option[PreRelease]]( 48 | 5 -> Gen.constant(None), 49 | 7 -> SemVerGens.genPreRelease.map(Some(_)) 50 | ) 51 | meta1 <- Gen.frequency1[Option[BuildMetaInfo]]( 52 | 5 -> Gen.constant(None), 53 | 7 -> SemVerGens.genBuildMetaInfo.map(Some(_)) 54 | ) 55 | pre2 <- Gen.frequency1[Option[PreRelease]]( 56 | 5 -> Gen.constant(None), 57 | 7 -> SemVerGens.genPreRelease.map(Some(_)) 58 | ) 59 | meta2 <- Gen.frequency1[Option[BuildMetaInfo]]( 60 | 5 -> Gen.constant(None), 61 | 7 -> SemVerGens.genBuildMetaInfo.map(Some(_)) 62 | ) 63 | } yield (DecVer(major1, minor1, pre1, meta1), DecVer(major2, minor2, pre2, meta2)) 64 | 65 | def genLessAndGreaterDecVerPair: Gen[(DecVer, DecVer)] = 66 | for { 67 | major1 <- DecVerGens.genMajorWithMax(Int.MaxValue >> 1) 68 | minor1 <- DecVerGens.genMinorWithMax(Int.MaxValue >> 1) 69 | diff <- Gen.int(Range.linear(1, Int.MaxValue >> 1)) 70 | choice <- Gen.element1(1, 2) 71 | (m, n) = ((choice & 2) >> 1, choice & 1) 72 | pre1 <- Gen.frequency1[Option[PreRelease]]( 73 | 5 -> Gen.constant(None), 74 | 7 -> SemVerGens.genPreRelease.map(Some(_)) 75 | ) 76 | meta1 <- Gen.frequency1[Option[BuildMetaInfo]]( 77 | 5 -> Gen.constant(None), 78 | 7 -> SemVerGens.genBuildMetaInfo.map(Some(_)) 79 | ) 80 | pre2 <- Gen.frequency1[Option[PreRelease]]( 81 | 5 -> Gen.constant(None), 82 | 7 -> SemVerGens.genPreRelease.map(Some(_)) 83 | ) 84 | meta2 <- Gen.frequency1[Option[BuildMetaInfo]]( 85 | 5 -> Gen.constant(None), 86 | 7 -> SemVerGens.genBuildMetaInfo.map(Some(_)) 87 | ) 88 | } yield ( 89 | DecVer(major1, minor1, pre1, meta1), 90 | DecVer(DecVer.Major(major1.value + diff * m), DecVer.Minor(minor1.value + diff * n), pre2, meta2) 91 | ) 92 | 93 | def genDecVerWithOnlyMajorMinorPatch( 94 | majorRange: Range[Int], 95 | minorRange: Range[Int], 96 | ): Gen[DecVer] = for { 97 | major <- genMajorWithRange(majorRange) 98 | minor <- genMinorWithRange(minorRange) 99 | } yield DecVer(major, minor, None, None) 100 | 101 | def genDecVerWithRange(majorRange: Range[Int], minorRange: Range[Int]): Gen[DecVer] = 102 | for { 103 | semVer <- genDecVerWithOnlyMajorMinorPatch(majorRange, minorRange) 104 | maybePreAndMeta <- Gen 105 | .frequency1( 106 | 5 -> SemVerGens 107 | .genPreReleaseAndBuildMetaInfo 108 | .map[(Option[PreRelease], Option[BuildMetaInfo])] { 109 | case (pre, meta) => 110 | ((Some(pre), Some(meta))) 111 | }, 112 | 5 -> Gen 113 | .frequency1[Option[PreRelease]]( 114 | 5 -> Gen.constant(None), 115 | 7 -> SemVerGens.genPreRelease.map(Some(_)) 116 | ) 117 | .flatMap { preRelease => 118 | Gen 119 | .frequency1[Option[BuildMetaInfo]]( 120 | 5 -> Gen.constant(None), 121 | 7 -> SemVerGens.genBuildMetaInfo.map(Some(_)) 122 | ) 123 | .map(meta => (preRelease, meta)) 124 | 125 | } 126 | ) 127 | .option 128 | 129 | } yield maybePreAndMeta match { 130 | case Some((pre, meta)) => 131 | semVer.copy(pre = pre, buildMetadata = meta) 132 | case None => 133 | semVer 134 | } 135 | 136 | } 137 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala-2/just/semver/SemVer.scala: -------------------------------------------------------------------------------- 1 | package just.semver 2 | 3 | import just.Common._ 4 | //import just.decver.DecVer 5 | import just.semver.AdditionalInfo.{BuildMetaInfo, PreRelease} 6 | import just.semver.SemVer.{Major, Minor, Patch} 7 | import just.semver.matcher.SemVerMatchers 8 | 9 | import scala.util.matching.Regex 10 | 11 | /** @author Kevin Lee 12 | * @since 2018-10-21 13 | */ 14 | final case class SemVer( 15 | major: Major, 16 | minor: Minor, 17 | patch: Patch, 18 | pre: Option[PreRelease], 19 | buildMetadata: Option[BuildMetaInfo] 20 | ) extends Ordered[SemVer] { 21 | 22 | override def compare(that: SemVer): Int = { 23 | ( 24 | this.major.value.compareTo(that.major.value), 25 | this.minor.value.compareTo(that.minor.value), 26 | this.patch.value.compareTo(that.patch.value) 27 | ) match { 28 | case (0, 0, 0) => 29 | (this.pre, that.pre) match { 30 | case (Some(thisPre), Some(thatPre)) => 31 | thisPre.identifier.compareElems(thatPre.identifier) 32 | 33 | case (Some(_), None) => 34 | -1 35 | case (None, Some(_)) => 36 | 1 37 | case (None, None) => 38 | 0 39 | } 40 | case (0, 0, pt) => 41 | pt 42 | case (0, mn, _) => 43 | mn 44 | case (mj, _, _) => 45 | mj 46 | } 47 | } 48 | 49 | } 50 | 51 | object SemVer { 52 | 53 | final case class Major(value: Int) extends AnyVal 54 | object Major { 55 | implicit class MajorOps(private val major0: Major) { 56 | def major: Int = major0.value 57 | } 58 | } 59 | 60 | final case class Minor(value: Int) extends AnyVal 61 | object Minor { 62 | implicit class MinorOps(private val minor0: Minor) { 63 | def minor: Int = minor0.value 64 | } 65 | } 66 | 67 | final case class Patch(value: Int) extends AnyVal 68 | object Patch { 69 | implicit class PatchOps(private val patch0: Patch) { 70 | def patch: Int = patch0.value 71 | } 72 | } 73 | 74 | val major0: Major = Major(0) 75 | val minor0: Minor = Minor(0) 76 | val patch0: Patch = Patch(0) 77 | 78 | val semVerRegex: Regex = """(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z\d-\.]+)?)?(?:\+([a-zA-Z\d-\.]+)?)?""".r 79 | 80 | implicit final class SemVerOps(private val semVer: SemVer) extends AnyVal { 81 | @inline def majorMinorPatch: (SemVer.Major, SemVer.Minor, SemVer.Patch) = 82 | SemVer.majorMinorPatch(semVer) 83 | 84 | @inline def renderMajorMinorPatch: String = 85 | SemVer.renderMajorMinorPatch(semVer) 86 | 87 | @inline def render: String = SemVer.render(semVer) 88 | 89 | def matches(semVerMatchers: SemVerMatchers): Boolean = semVerMatchers.matches(semVer) 90 | 91 | def unsafeMatches(semVerMatchers: String): Boolean = 92 | SemVerMatchers 93 | .unsafeParse(semVerMatchers) 94 | .matches(semVer) 95 | 96 | // def toDecVer: DecVer = DecVer.fromSemVer(semVer) 97 | } 98 | 99 | def majorMinorPatch(semVer: SemVer): (SemVer.Major, SemVer.Minor, SemVer.Patch) = 100 | (semVer.major, semVer.minor, semVer.patch) 101 | 102 | def renderMajorMinorPatch(semVer: SemVer): String = 103 | s"${semVer.major.value.toString}.${semVer.minor.value.toString}.${semVer.patch.value.toString}" 104 | 105 | def render(semVer: SemVer): String = semVer match { 106 | case SemVer(major, minor, patch, pre, buildMetadata) => 107 | val versionString = s"${major.value.toString}.${minor.value.toString}.${patch.value.toString}" 108 | val additionalInfoString = 109 | (pre, buildMetadata) match { 110 | case (Some(p), Some(m)) => 111 | s"-${PreRelease.render(p)}+${BuildMetaInfo.render(m)}" 112 | case (Some(p), None) => 113 | s"-${PreRelease.render(p)}" 114 | case (None, Some(m)) => 115 | s"+${BuildMetaInfo.render(m)}" 116 | case (None, None) => 117 | "" 118 | } 119 | versionString + additionalInfoString 120 | } 121 | 122 | def unsafeParse(version: String): SemVer = 123 | parse(version) match { 124 | case Right(semVer) => 125 | semVer 126 | case Left(error) => 127 | sys.error(ParseError.render(error)) 128 | } 129 | 130 | def parseUnsafe(version: String): SemVer = unsafeParse(version) 131 | 132 | def parse(version: String): Either[ParseError, SemVer] = version match { 133 | case semVerRegex(major, minor, patch, pre, meta) => 134 | val preRelease = AdditionalInfo.parsePreRelease(pre) 135 | val metaInfo = AdditionalInfo.parseBuildMetaInfo(meta) 136 | (preRelease, metaInfo) match { 137 | case (Left(preError), Left(metaError)) => 138 | Left(ParseError.combine(ParseError.fromAdditionalInfoParserError(preError), ParseError.fromAdditionalInfoParserError(metaError))) 139 | case (Left(preError), _) => 140 | Left(ParseError.preReleaseParseError(ParseError.fromAdditionalInfoParserError(preError))) 141 | case (_, Left(metaError)) => 142 | Left(ParseError.buildMetadataParseError(ParseError.fromAdditionalInfoParserError(metaError))) 143 | case (Right(preR), Right(metaI)) => 144 | Right( 145 | SemVer( 146 | Major(major.toInt), 147 | Minor(minor.toInt), 148 | Patch(patch.toInt), 149 | preR, 150 | metaI 151 | ) 152 | ) 153 | } 154 | 155 | case _ => 156 | Left(ParseError.invalidVersionStringError(version)) 157 | } 158 | 159 | def semVer(major: Major, minor: Minor, patch: Patch): SemVer = 160 | SemVer(major, minor, patch, None, None) 161 | 162 | def withMajor(major: Major): SemVer = 163 | SemVer(major, minor0, patch0, None, None) 164 | 165 | def withMinor(minor: Minor): SemVer = 166 | SemVer(major0, minor, patch0, None, None) 167 | 168 | def withPatch(patch: Patch): SemVer = 169 | SemVer(major0, minor0, patch, None, None) 170 | 171 | def increaseMajor(semVer: SemVer): SemVer = 172 | semVer.copy(major = Major(semVer.major.value + 1)) 173 | 174 | def increaseMinor(semVer: SemVer): SemVer = 175 | semVer.copy(minor = Minor(semVer.minor.value + 1)) 176 | 177 | def increasePatch(semVer: SemVer): SemVer = 178 | semVer.copy(patch = Patch(semVer.patch.value + 1)) 179 | 180 | // def fromDecVer(decVer: DecVer): SemVer = 181 | // SemVer.semVer(SemVer.Major(decVer.major.value), SemVer.Minor(decVer.minor.value), patch0) 182 | 183 | } 184 | -------------------------------------------------------------------------------- /docs/intro.md: -------------------------------------------------------------------------------- 1 | --- 2 | sidebar_position: 1 3 | id: 'intro' 4 | title: 'Just SemVer' 5 | slug: '/' 6 | --- 7 | # just-semver 8 | 9 | [![Build Status](https://github.com/Kevin-Lee/just-semver/workflows/Build%20All/badge.svg)](https://github.com/Kevin-Lee/just-semver/actions?workflow=Build+All) 10 | [![Release Status](https://github.com/Kevin-Lee/just-semver/workflows/Release/badge.svg)](https://github.com/Kevin-Lee/just-semver/actions?workflow=Release) 11 | [![codecov](https://codecov.io/gh/kevin-lee/just-semver/graph/badge.svg?token=SO5LB2BWOL)](https://codecov.io/gh/kevin-lee/just-semver) 12 | 13 | [![Maven Central](https://maven-badges.herokuapp.com/maven-central/io.kevinlee/just-semver_2.13/badge.svg)](https://search.maven.org/artifact/io.kevinlee/just-semver_2.13) 14 | [![Latest version](https://index.scala-lang.org/kevin-lee/just-semver/just-semver/latest.svg)](https://index.scala-lang.org/kevin-lee/just-semver/just-semver) 15 | 16 | Semantic Versioning (`SemVer`) for Scala 17 | 18 | :::info 19 | Supported Scala Versions: @SUPPORTED_SCALA_VERSIONS@.
20 | It also supports Scala.js and Scala Native. 21 | 22 | Show [**all `just-semver` versions**](https://index.scala-lang.org/kevin-lee/just-semver/artifacts) 23 | ::: 24 | 25 | 26 | ## Get just-semver 27 | 28 | ### `@VERSION@` 29 | 30 | [![Scala.js](https://www.scala-js.org/assets/badges/scalajs-1.11.0.svg)](https://www.scala-js.org) 31 | 32 | Since `0.6.0` `just-semver` supports Scala.js. 33 | 34 | #### Core 35 | ```scala 36 | "io.kevinlee" %% "just-semver-core" % "@VERSION@" 37 | ``` 38 | 39 | ```scala 40 | "io.kevinlee" %%% "just-semver-core" % "@VERSION@" 41 | ``` 42 | 43 | 44 | e.g.) 45 | ```scala 46 | libraryDependencies += "io.kevinlee" %% "just-semver-core" % "@VERSION@" 47 | ``` 48 | ```scala 49 | libraryDependencies += "io.kevinlee" %%% "just-semver-core" % "@VERSION@" 50 | ``` 51 | 52 | 53 | #### DecVer: Decimal version module 54 | ```scala 55 | "io.kevinlee" %% "just-semver-decver" % "@VERSION@" 56 | ``` 57 | 58 | ```scala 59 | "io.kevinlee" %%% "just-semver-decver" % "@VERSION@" 60 | ``` 61 | 62 | 63 | e.g.) 64 | ```scala 65 | libraryDependencies += "io.kevinlee" %% "just-semver-decver" % "@VERSION@" 66 | ``` 67 | ```scala 68 | libraryDependencies += "io.kevinlee" %%% "just-semver-decver" % "@VERSION@" 69 | ``` 70 | 71 | #### All modules 72 | 73 | ```scala 74 | "io.kevinlee" %% "just-semver-core" % "@VERSION@", 75 | "io.kevinlee" %% "just-semver-decver" % "@VERSION@", 76 | ``` 77 | 78 | ```scala 79 | "io.kevinlee" %%% "just-semver-core" % "@VERSION@", 80 | "io.kevinlee" %%% "just-semver-decver" % "@VERSION@", 81 | ``` 82 | 83 | *** 84 | 85 | ```scala 86 | libraryDependencies ++= Seq( 87 | "io.kevinlee" %% "just-semver-core" % "@VERSION@", 88 | "io.kevinlee" %% "just-semver-decver" % "@VERSION@", 89 | ) 90 | ``` 91 | ```scala 92 | libraryDependencies ++= Seq( 93 | "io.kevinlee" %%% "just-semver-core" % "@VERSION@", 94 | "io.kevinlee" %%% "just-semver-decver" % "@VERSION@", 95 | ) 96 | ``` 97 | 98 | 99 | 100 | ## Older Versions 101 | 102 | ### `1.1.0` 103 | Please use a higher version. 104 | 105 | ### `1.0.0` 106 | Please use a higher version. 107 | 108 | ### `0.13.0` 109 | Please use a higher version. 110 | 111 | ### `0.12.0` 112 | Please use a higher version. 113 | 114 | ### `0.11.0` 115 | 116 | :::info 117 | Supported Scala Versions: `2.12`, `2.13` and `3.3+`. 118 | 119 | Show [**all `just-semver` versions**](https://index.scala-lang.org/kevin-lee/just-semver/artifacts) 120 | ::: 121 | 122 | 123 | #### Get just-semver 124 | 125 | ```scala 126 | "io.kevinlee" %% "just-semver-core" % "0.11.0" 127 | ``` 128 | 129 | [![Scala.js](https://www.scala-js.org/assets/badges/scalajs-1.18.0.svg)](https://www.scala-js.org) 130 | 131 | Since `0.6.0` `just-semver` supports Scala.js. 132 | 133 | ```scala 134 | "io.kevinlee" %%% "just-semver-core" % "0.11.0" 135 | ``` 136 | 137 | 138 | e.g.) 139 | ```scala 140 | libraryDependencies += "io.kevinlee" %% "just-semver-core" % "0.11.0" 141 | ``` 142 | ```scala 143 | libraryDependencies += "io.kevinlee" %%% "just-semver-core" % "0.11.0" 144 | ``` 145 | 146 | 147 | ### `0.10.0` 148 | 149 | :::info 150 | Supported Scala Versions: `2.12`, `2.13` and `3.2+`. 151 | 152 | Show [**all `just-semver` versions**](https://index.scala-lang.org/kevin-lee/just-semver/artifacts) 153 | ::: 154 | 155 | 156 | #### Get just-semver 157 | 158 | ```scala 159 | "io.kevinlee" %% "just-semver-core" % "0.10.0" 160 | ``` 161 | 162 | [![Scala.js](https://www.scala-js.org/assets/badges/scalajs-1.11.0.svg)](https://www.scala-js.org) 163 | 164 | Since `0.6.0` `just-semver` supports Scala.js. 165 | 166 | ```scala 167 | "io.kevinlee" %%% "just-semver-core" % "0.10.0" 168 | ``` 169 | 170 | 171 | e.g.) 172 | ```scala 173 | libraryDependencies += "io.kevinlee" %% "just-semver-core" % "0.10.0" 174 | ``` 175 | ```scala 176 | libraryDependencies += "io.kevinlee" %%% "just-semver-core" % "0.10.0" 177 | ``` 178 | 179 | 180 | ### `0.9.0` 181 | 182 | :::info 183 | Supported Scala Versions: `2.12`, `2.13` and `3.1+`. 184 | 185 | Show [**all `just-semver` versions**](https://index.scala-lang.org/kevin-lee/just-semver/artifacts) 186 | ::: 187 | 188 | 189 | #### Get just-semver 190 | 191 | ```scala 192 | "io.kevinlee" %% "just-semver-core" % "0.9.0" 193 | ``` 194 | 195 | [![Scala.js](https://www.scala-js.org/assets/badges/scalajs-1.11.0.svg)](https://www.scala-js.org) 196 | 197 | Since `0.6.0` `just-semver` supports Scala.js. 198 | 199 | ```scala 200 | "io.kevinlee" %%% "just-semver-core" % "0.9.0" 201 | ``` 202 | 203 | 204 | e.g.) 205 | ```scala 206 | libraryDependencies += "io.kevinlee" %% "just-semver-core" % "0.9.0" 207 | ``` 208 | ```scala 209 | libraryDependencies += "io.kevinlee" %%% "just-semver-core" % "0.9.0" 210 | ``` 211 | 212 | 213 | ### `0.6.0` 214 | 215 | :::info 216 | Supported Scala Versions: `2.11`, `2.12`, `2.13` and `3`. 217 | 218 | Show [**all `just-semver` versions**](https://index.scala-lang.org/kevin-lee/just-semver/artifacts) 219 | ::: 220 | 221 | 222 | #### Get just-semver 223 | 224 | ```scala 225 | "io.kevinlee" %% "just-semver-core" % "0.6.0" 226 | ``` 227 | 228 | [![Scala.js](https://www.scala-js.org/assets/badges/scalajs-1.11.0.svg)](https://www.scala-js.org) 229 | 230 | Since `0.6.0` `just-semver` supports Scala.js. 231 | 232 | ```scala 233 | "io.kevinlee" %%% "just-semver-core" % "0.6.0" 234 | ``` 235 | 236 | 237 | e.g.) 238 | ```scala 239 | libraryDependencies += "io.kevinlee" %% "just-semver-core" % "0.6.0" 240 | ``` 241 | ```scala 242 | libraryDependencies += "io.kevinlee" %%% "just-semver-core" % "0.6.0" 243 | ``` 244 | -------------------------------------------------------------------------------- /project/ProjectInfo.scala: -------------------------------------------------------------------------------- 1 | import just.semver.SemVer 2 | import sbt._ 3 | //import wartremover.WartRemover.autoImport.{Wart, Warts} 4 | 5 | /** @author Kevin Lee 6 | * @since 2018-05-21 7 | */ 8 | object ProjectInfo { 9 | 10 | object Wart { 11 | val Any = "org.wartremover.warts.Any" 12 | val AnyVal = "org.wartremover.warts.AnyVal" 13 | val ArrayEquals = "org.wartremover.warts.ArrayEquals" 14 | val AsInstanceOf = "org.wartremover.warts.AsInstanceOf" 15 | val DefaultArguments = "org.wartremover.warts.DefaultArguments" 16 | val EitherProjectionPartial = "org.wartremover.warts.EitherProjectionPartial" 17 | val Enumeration = "org.wartremover.warts.Enumeration" 18 | val Equals = "org.wartremover.warts.Equals" 19 | val ExplicitImplicitTypes = "org.wartremover.warts.ExplicitImplicitTypes" 20 | val FinalCaseClass = "org.wartremover.warts.FinalCaseClass" 21 | val FinalVal = "org.wartremover.warts.FinalVal" 22 | val GlobalExecutionContext = "org.wartremover.warts.GlobalExecutionContext" 23 | val ImplicitConversion = "org.wartremover.warts.ImplicitConversion" 24 | val ImplicitParameter = "org.wartremover.warts.ImplicitParameter" 25 | val IsInstanceOf = "org.wartremover.warts.IsInstanceOf" 26 | val IterableOps = "org.wartremover.warts.IterableOps" 27 | val JavaConversions = "org.wartremover.warts.JavaConversions" 28 | val JavaSerializable = "org.wartremover.warts.JavaSerializable" 29 | val LeakingSealed = "org.wartremover.warts.LeakingSealed" 30 | val ListAppend = "org.wartremover.warts.ListAppend" 31 | val ListUnapply = "org.wartremover.warts.ListUnapply" 32 | val MutableDataStructures = "org.wartremover.warts.MutableDataStructures" 33 | val NoNeedImport = "org.wartremover.warts.NoNeedImport" 34 | val NonUnitStatements = "org.wartremover.warts.NonUnitStatements" 35 | val Nothing = "org.wartremover.warts.Nothing" 36 | val Null = "org.wartremover.warts.Null" 37 | val Option2Iterable = "org.wartremover.warts.Option2Iterable" 38 | val OptionPartial = "org.wartremover.warts.OptionPartial" 39 | val Overloading = "org.wartremover.warts.Overloading" 40 | val PlatformDefault = "org.wartremover.warts.PlatformDefault" 41 | val Product = "org.wartremover.warts.Product" 42 | val PublicInference = "org.wartremover.warts.PublicInference" 43 | val Recursion = "org.wartremover.warts.Recursion" 44 | val RedundantConversions = "org.wartremover.warts.RedundantConversions" 45 | val Return = "org.wartremover.warts.Return" 46 | val ScalaApp = "org.wartremover.warts.ScalaApp" 47 | val Serializable = "org.wartremover.warts.Serializable" 48 | val SizeIs = "org.wartremover.warts.SizeIs" 49 | val StringPlusAny = "org.wartremover.warts.StringPlusAny" 50 | val ThreadSleep = "org.wartremover.warts.ThreadSleep" 51 | val Throw = "org.wartremover.warts.Throw" 52 | val ToString = "org.wartremover.warts.ToString" 53 | val TryPartial = "org.wartremover.warts.TryPartial" 54 | val Var = "org.wartremover.warts.Var" 55 | val While = "org.wartremover.warts.While" 56 | } 57 | 58 | val all: List[String] = List( 59 | Wart.Any, 60 | Wart.AnyVal, 61 | Wart.ArrayEquals, 62 | Wart.AsInstanceOf, 63 | Wart.DefaultArguments, 64 | Wart.EitherProjectionPartial, 65 | Wart.Enumeration, 66 | Wart.Equals, 67 | Wart.ExplicitImplicitTypes, 68 | Wart.FinalCaseClass, 69 | Wart.FinalVal, 70 | Wart.GlobalExecutionContext, 71 | Wart.ImplicitConversion, 72 | Wart.ImplicitParameter, 73 | Wart.IsInstanceOf, 74 | Wart.IterableOps, 75 | Wart.JavaConversions, 76 | Wart.JavaSerializable, 77 | Wart.LeakingSealed, 78 | Wart.ListAppend, 79 | Wart.ListUnapply, 80 | Wart.MutableDataStructures, 81 | Wart.NoNeedImport, 82 | Wart.NonUnitStatements, 83 | Wart.Nothing, 84 | Wart.Null, 85 | Wart.Option2Iterable, 86 | Wart.OptionPartial, 87 | Wart.Overloading, 88 | Wart.PlatformDefault, 89 | Wart.Product, 90 | Wart.PublicInference, 91 | Wart.Recursion, 92 | Wart.RedundantConversions, 93 | Wart.Return, 94 | Wart.ScalaApp, 95 | Wart.Serializable, 96 | Wart.SizeIs, 97 | Wart.StringPlusAny, 98 | Wart.ThreadSleep, 99 | Wart.Throw, 100 | Wart.ToString, 101 | Wart.TryPartial, 102 | Wart.Var, 103 | Wart.While 104 | ) 105 | 106 | def allBut(wart: String, warts: String*): List[String] = 107 | all.diff(wart +: warts) 108 | 109 | def commonWarts(scalaBinaryVersion: String): List[String] = 110 | SemVer.majorMinorPatch(SemVer.parseUnsafe(scalaBinaryVersion)) match { 111 | case (SemVer.Major(2), SemVer.Minor(10), _) | (SemVer.Major(3), SemVer.Minor(0), _) => 112 | List.empty[String] 113 | 114 | case (SemVer.Major(2), SemVer.Minor(11), _) => 115 | allBut( 116 | Wart.DefaultArguments, 117 | Wart.Overloading, 118 | Wart.Any, 119 | Wart.Nothing, 120 | Wart.NonUnitStatements, 121 | Wart.IterableOps, 122 | Wart.NoNeedImport, 123 | Wart.Product 124 | ).map("-P:wartremover:traverser:" + _) 125 | 126 | case (SemVer.Major(2), SemVer.Minor(12) | SemVer.Minor(13), _) | (SemVer.Major(3), _, _) => 127 | allBut(Wart.DefaultArguments, Wart.Overloading, Wart.Any, Wart.Nothing, Wart.NonUnitStatements).map( 128 | "-P:wartremover:traverser:" + _ 129 | ) 130 | 131 | case _ => 132 | throw new MessageOnlyException(s"Unsupported Scala version: $scalaBinaryVersion") 133 | } 134 | 135 | // def commonWarts(scalaBinaryVersion: String): Seq[wartremover.Wart] = scalaBinaryVersion match { 136 | // case "2.10" => 137 | // Seq.empty 138 | // case "2.11" => 139 | // Seq.empty 140 | // case "2.12" | "2.13" | "3" => 141 | // Warts.allBut(Wart.DefaultArguments, Wart.Overloading, Wart.Any, Wart.Nothing, Wart.NonUnitStatements) 142 | //// case "3" => 143 | //// Seq.empty[wartremover.Wart] 144 | // case _ => 145 | // Seq.empty[wartremover.Wart] 146 | // } 147 | 148 | } 149 | -------------------------------------------------------------------------------- /.github/workflows/release.yml: -------------------------------------------------------------------------------- 1 | name: Release 2 | 3 | on: 4 | push: 5 | branches: 6 | - main 7 | tags: 8 | - '*' 9 | 10 | env: 11 | GH_JAVA_VERSION: "11" 12 | GH_JAVA_DISTRIBUTION: "temurin" 13 | GH_JVM_OPTS: "-Xss64m -Xms1024m -XX:MaxMetaspaceSize=1G -Xmx2G -XX:MaxInlineLevel=18 -XX:+UnlockExperimentalVMOptions" 14 | 15 | jobs: 16 | 17 | build: 18 | runs-on: ubuntu-latest 19 | 20 | strategy: 21 | matrix: 22 | scala: 23 | - { name: "Scala 2", version: "2.12.18", binary-version: "2.12", java-version: "11", java-distribution: "temurin", params: "" } 24 | - { name: "Scala 2", version: "2.13.16", binary-version: "2.13", java-version: "11", java-distribution: "temurin", params: "" } 25 | - { name: "Scala 3.1", version: "3.3.5", binary-version: "3", java-version: "11", java-distribution: "temurin", params: '' } 26 | 27 | steps: 28 | - uses: actions/checkout@v6 29 | - uses: actions/setup-java@v5 30 | with: 31 | java-version: ${{ matrix.scala.java-version }} 32 | distribution: ${{ matrix.scala.java-distribution }} 33 | cache: 'sbt' 34 | - uses: sbt/setup-sbt@v1 35 | 36 | - name: "Build All for Scala ${{ matrix.scala.version }}" 37 | env: 38 | JVM_OPTS: ${{ env.GH_JVM_OPTS }} 39 | run: | 40 | java -version 41 | .github/workflows/sbt-build-test.sh ${{ matrix.scala.version }} "${{ matrix.scala.params }}" 42 | 43 | test_report: 44 | runs-on: ubuntu-latest 45 | 46 | strategy: 47 | matrix: 48 | scala: 49 | - { name: "Scala 2", version: "2.13.16", binary-version: "2.13", java-version: "11", java-distribution: "temurin" } 50 | 51 | steps: 52 | - uses: actions/checkout@v6 53 | - uses: actions/setup-java@v5 54 | with: 55 | java-version: ${{ matrix.scala.java-version }} 56 | distribution: ${{ matrix.scala.java-distribution }} 57 | cache: 'sbt' 58 | - uses: sbt/setup-sbt@v1 59 | 60 | - name: "[Codecov] Report ${{ matrix.scala.name }} ${{ matrix.scala.version }} - ${{ github.run_number }}" 61 | env: 62 | CURRENT_BRANCH_NAME: ${{ github.ref }} 63 | RUN_ID: ${{ github.run_id }} 64 | RUN_NUMBER: ${{ github.run_number }} 65 | JVM_OPTS: ${{ env.GH_JVM_OPTS }} 66 | run: | 67 | echo "[BEFORE]CURRENT_BRANCH_NAME=${CURRENT_BRANCH_NAME}" 68 | export CURRENT_BRANCH_NAME="${CURRENT_BRANCH_NAME#refs/heads/}" 69 | echo " [AFTER]CURRENT_BRANCH_NAME=${CURRENT_BRANCH_NAME}" 70 | echo "RUN_ID=${RUN_ID}" 71 | echo "RUN_NUMBER=${RUN_NUMBER}" 72 | .github/workflows/sbt-build-all-with-coverage.sh ${{ matrix.scala.version }} 73 | 74 | - uses: codecov/codecov-action@v5 75 | 76 | github-release: 77 | needs: build 78 | if: startsWith(github.ref, 'refs/tags/v') 79 | 80 | runs-on: ubuntu-latest 81 | steps: 82 | - uses: actions/checkout@v6 83 | with: 84 | fetch-depth: 0 85 | - uses: actions/setup-java@v5 86 | with: 87 | java-version: ${{ env.GH_JAVA_VERSION }} 88 | distribution: ${{ env.GH_JAVA_DISTRIBUTION }} 89 | cache: 'sbt' 90 | - uses: sbt/setup-sbt@v1 91 | 92 | - name: sbt GitHub Release 93 | env: 94 | GITHUB_TOKEN: ${{ secrets.RELEASE_AUTH_TOKEN_GITHUB }} 95 | JVM_OPTS: ${{ env.GH_JVM_OPTS }} 96 | run: | 97 | echo "Run] sbt GitHub release" 98 | export SOURCE_DATE_EPOCH=$(date +%s) 99 | echo "SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH" 100 | echo "=====================" 101 | echo 'sbt -J-Xmx2048m devOopsGitHubRelease' 102 | sbt \ 103 | -J-XX:MaxMetaspaceSize=1024m \ 104 | -J-Xmx2048m \ 105 | devOopsGitHubRelease 106 | 107 | publish: 108 | needs: github-release 109 | if: startsWith(github.ref, 'refs/tags/v') 110 | 111 | runs-on: ubuntu-latest 112 | 113 | steps: 114 | - uses: actions/checkout@v6 115 | with: 116 | fetch-depth: 0 117 | - uses: actions/setup-java@v5 118 | with: 119 | java-version: ${{ env.GH_JAVA_VERSION }} 120 | distribution: ${{ env.GH_JAVA_DISTRIBUTION }} 121 | cache: 'sbt' 122 | - uses: sbt/setup-sbt@v1 123 | - uses: olafurpg/setup-gpg@v3 124 | 125 | - name: "sbt ci-release - ${{ github.run_number }}" 126 | env: 127 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 128 | PGP_SECRET: ${{ secrets.PGP_SECRET }} 129 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 130 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 131 | GITHUB_TOKEN: ${{ secrets.RELEASE_AUTH_TOKEN_GITHUB }} 132 | JVM_OPTS: ${{ env.GH_JVM_OPTS }} 133 | GHA_IS_PUBLISHING: "true" 134 | run: | 135 | echo "Run] sbt ci-release" 136 | export SOURCE_DATE_EPOCH=$(date +%s) 137 | echo "SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH" 138 | echo 'sbt -J-Xmx2048m -v clean +test +packagedArtifacts ci-release' 139 | sbt \ 140 | -J-XX:MaxMetaspaceSize=1024m \ 141 | -J-Xmx2048m \ 142 | -v \ 143 | clean \ 144 | +packagedArtifacts \ 145 | ci-release 146 | 147 | publish-snapshot: 148 | needs: build 149 | if: startsWith(github.ref, 'refs/heads/') 150 | 151 | runs-on: ubuntu-latest 152 | 153 | steps: 154 | - uses: actions/checkout@v6 155 | with: 156 | fetch-depth: 0 157 | - uses: actions/setup-java@v5 158 | with: 159 | java-version: ${{ env.GH_JAVA_VERSION }} 160 | distribution: ${{ env.GH_JAVA_DISTRIBUTION }} 161 | cache: 'sbt' 162 | - uses: sbt/setup-sbt@v1 163 | 164 | - uses: olafurpg/setup-gpg@v3 165 | 166 | - name: "sbt ci-release (no tag) - ${{ github.run_number }}" 167 | if: startsWith(github.ref, 'refs/heads/') 168 | env: 169 | PGP_PASSPHRASE: ${{ secrets.PGP_PASSPHRASE }} 170 | PGP_SECRET: ${{ secrets.PGP_SECRET }} 171 | SONATYPE_PASSWORD: ${{ secrets.SONATYPE_PASSWORD }} 172 | SONATYPE_USERNAME: ${{ secrets.SONATYPE_USERNAME }} 173 | JVM_OPTS: ${{ env.GH_JVM_OPTS }} 174 | GHA_IS_PUBLISHING: "true" 175 | run: | 176 | echo "Run] sbt ci-release" 177 | export SOURCE_DATE_EPOCH=$(date +%s) 178 | echo "SOURCE_DATE_EPOCH=$SOURCE_DATE_EPOCH" 179 | echo "JVM_OPTS=${JVM_OPTS}" 180 | echo "SBT_OPTS=${SBT_OPTS}" 181 | echo 'sbt -v clean +packagedArtifacts ci-release' 182 | sbt \ 183 | -v \ 184 | clean \ 185 | +packagedArtifacts \ 186 | ci-release 187 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/main/scala-3/just/semver/SemVer.scala: -------------------------------------------------------------------------------- 1 | package just.semver 2 | 3 | import just.Common.* 4 | //import just.decver.DecVer 5 | import just.semver.AdditionalInfo.{BuildMetaInfo, PreRelease} 6 | import just.semver.SemVer.{Major, Minor, Patch} 7 | import just.semver.matcher.SemVerMatchers 8 | 9 | import scala.util.matching.Regex 10 | 11 | /** @author Kevin Lee 12 | * @since 2018-10-21 13 | */ 14 | final case class SemVer( 15 | major: Major, 16 | minor: Minor, 17 | patch: Patch, 18 | pre: Option[PreRelease], 19 | buildMetadata: Option[BuildMetaInfo] 20 | ) extends Ordered[SemVer] 21 | derives CanEqual { 22 | 23 | override def compare(that: SemVer): Int = { 24 | ( 25 | this.major.value.compareTo(that.major.value), 26 | this.minor.value.compareTo(that.minor.value), 27 | this.patch.value.compareTo(that.patch.value) 28 | ) match { 29 | case (0, 0, 0) => 30 | (this.pre, that.pre) match { 31 | case (Some(thisPre), Some(thatPre)) => 32 | thisPre.identifier.compareElems(thatPre.identifier) 33 | 34 | case (Some(_), None) => 35 | -1 36 | case (None, Some(_)) => 37 | 1 38 | case (None, None) => 39 | 0 40 | } 41 | case (0, 0, pt) => 42 | pt 43 | case (0, mn, _) => 44 | mn 45 | case (mj, _, _) => 46 | mj 47 | } 48 | } 49 | 50 | } 51 | 52 | object SemVer { 53 | 54 | type Major = Major.Major 55 | object Major { 56 | opaque type Major = Int 57 | def apply(major: Int): Major = major 58 | def unapply(major: Major): Some[Int] = Some(major) 59 | 60 | given majorCanEqual: CanEqual[Major, Major] = CanEqual.derived 61 | 62 | extension (major0: Major) { 63 | def value: Int = major0 64 | def major: Int = major0 65 | } 66 | } 67 | 68 | type Minor = Minor.Minor 69 | object Minor { 70 | opaque type Minor = Int 71 | def apply(minor: Int): Minor = minor 72 | def unapply(minor: Minor): Some[Int] = Some(minor) 73 | 74 | given minorCanEqual: CanEqual[Minor, Minor] = CanEqual.derived 75 | 76 | extension (minor0: Minor) { 77 | def value: Int = minor0 78 | def minor: Int = minor0 79 | } 80 | } 81 | 82 | type Patch = Patch.Patch 83 | object Patch { 84 | opaque type Patch = Int 85 | def apply(patch: Int): Patch = patch 86 | def unapply(patch: Patch): Some[Int] = Some(patch) 87 | 88 | given patchCanEqual: CanEqual[Patch, Patch] = CanEqual.derived 89 | 90 | extension (patch0: Patch) { 91 | def value: Int = patch0 92 | def patch: Int = patch0 93 | } 94 | } 95 | 96 | val major0: Major = Major(0) 97 | val minor0: Minor = Minor(0) 98 | val patch0: Patch = Patch(0) 99 | 100 | val semVerRegex: Regex = """(\d+)\.(\d+)\.(\d+)(?:-([a-zA-Z\d-\.]+)?)?(?:\+([a-zA-Z\d-\.]+)?)?""".r 101 | 102 | extension (semVer: SemVer) { 103 | inline def majorMinorPatch: (SemVer.Major, SemVer.Minor, SemVer.Patch) = 104 | (semVer.major, semVer.minor, semVer.patch) 105 | 106 | def renderMajorMinorPatch: String = 107 | s"${semVer.major.value.toString}.${semVer.minor.value.toString}.${semVer.patch.value.toString}" 108 | 109 | def render: String = semVer match { 110 | case SemVer(major, minor, patch, pre, buildMetadata) => 111 | val versionString = s"${major.value.toString}.${minor.value.toString}.${patch.value.toString}" 112 | val additionalInfoString = 113 | (pre, buildMetadata) match { 114 | case (Some(p), Some(m)) => 115 | s"-${PreRelease.render(p)}+${BuildMetaInfo.render(m)}" 116 | case (Some(p), None) => 117 | s"-${PreRelease.render(p)}" 118 | case (None, Some(m)) => 119 | s"+${BuildMetaInfo.render(m)}" 120 | case (None, None) => 121 | "" 122 | } 123 | versionString + additionalInfoString 124 | } 125 | 126 | def matches(semVerMatchers: SemVerMatchers): Boolean = semVerMatchers.matches(semVer) 127 | 128 | def unsafeMatches(semVerMatchers: String): Boolean = 129 | SemVerMatchers 130 | .unsafeParse(semVerMatchers) 131 | .matches(semVer) 132 | 133 | // def toDecVer: DecVer = DecVer.fromSemVer(semVer) 134 | 135 | } 136 | 137 | def unsafeParse(version: String): SemVer = 138 | parse(version) match { 139 | case Right(semVer) => 140 | semVer 141 | case Left(error) => 142 | sys.error(ParseError.render(error)) 143 | } 144 | 145 | def parseUnsafe(version: String): SemVer = unsafeParse(version) 146 | 147 | def parse(version: String): Either[ParseError, SemVer] = version match { 148 | case semVerRegex(major, minor, patch, pre, meta) => 149 | val preRelease = AdditionalInfo.parsePreRelease(pre) 150 | val metaInfo = AdditionalInfo.parseBuildMetaInfo(meta) 151 | (preRelease, metaInfo) match { 152 | case (Left(preError), Left(metaError)) => 153 | Left( 154 | ParseError.combine( 155 | ParseError.fromAdditionalInfoParserError(preError), 156 | ParseError.fromAdditionalInfoParserError(metaError) 157 | ) 158 | ) 159 | case (Left(preError), _) => 160 | Left(ParseError.preReleaseParseError(ParseError.fromAdditionalInfoParserError(preError))) 161 | case (_, Left(metaError)) => 162 | Left(ParseError.buildMetadataParseError(ParseError.fromAdditionalInfoParserError(metaError))) 163 | case (Right(preR), Right(metaI)) => 164 | Right( 165 | SemVer( 166 | Major(major.toInt), 167 | Minor(minor.toInt), 168 | Patch(patch.toInt), 169 | preR, 170 | metaI 171 | ) 172 | ) 173 | } 174 | 175 | case _ => 176 | Left(ParseError.invalidVersionStringError(version)) 177 | } 178 | 179 | def semVer(major: Major, minor: Minor, patch: Patch): SemVer = 180 | SemVer(major, minor, patch, None, None) 181 | 182 | def withMajor(major: Major): SemVer = 183 | SemVer(major, minor0, patch0, None, None) 184 | 185 | def withMinor(minor: Minor): SemVer = 186 | SemVer(major0, minor, patch0, None, None) 187 | 188 | def withPatch(patch: Patch): SemVer = 189 | SemVer(major0, minor0, patch, None, None) 190 | 191 | def increaseMajor(semVer: SemVer): SemVer = 192 | semVer.copy(major = Major(semVer.major.value + 1)) 193 | 194 | def increaseMinor(semVer: SemVer): SemVer = 195 | semVer.copy(minor = Minor(semVer.minor.value + 1)) 196 | 197 | def increasePatch(semVer: SemVer): SemVer = 198 | semVer.copy(patch = Patch(semVer.patch.value + 1)) 199 | 200 | // def fromDecVer(decVer: DecVer): SemVer = 201 | // SemVer.semVer(SemVer.Major(decVer.major.value), SemVer.Minor(decVer.minor.value), patch0) 202 | 203 | } 204 | -------------------------------------------------------------------------------- /modules/just-semver-core/shared/src/test/scala/just/semver/DsvSpec.scala: -------------------------------------------------------------------------------- 1 | package just.semver 2 | 3 | import hedgehog._ 4 | import hedgehog.runner._ 5 | 6 | import scala.collection.compat.immutable._ 7 | 8 | /** @author Kevin Lee 9 | * @since 2024-04-22 10 | */ 11 | object DsvSpec extends Properties { 12 | override def tests: List[Test] = List( 13 | property("test Dsv.parse(valid String)", testParse), 14 | property("test Dsv.parse(invalid String)", testParseInvalid).withTests(20000).noShrinking, 15 | example("test Dsv.parse(an empty String)", testParseInvalid2), 16 | ) 17 | 18 | def testParse: Property = for { 19 | anh1 <- Gen 20 | .frequency1( 21 | 45 -> Gen.string(Gen.alpha, Range.linear(1, 3)).map(Anh.alphabet), 22 | 45 -> Gen.int(Range.linear(0, 999)).map(Anh.num), 23 | ) 24 | .log("anh1") 25 | anh2 <- (anh1 match { 26 | case Anh.Alphabet(_) => 27 | Gen 28 | .frequency1( 29 | 90 -> Gen.int(Range.linear(0, 999)).map(Anh.num), 30 | 10 -> Gen.constant(Anh.hyphen) 31 | ) 32 | case Anh.Num(_) => 33 | Gen 34 | .frequency1( 35 | 90 -> Gen.string(Gen.alpha, Range.linear(1, 3)).map(Anh.alphabet), 36 | 10 -> Gen.constant(Anh.hyphen) 37 | ) 38 | case Anh.Hyphen => 39 | Gen 40 | .frequency1( 41 | 50 -> Gen.string(Gen.alpha, Range.linear(1, 3)).map(Anh.alphabet), 42 | 50 -> Gen.int(Range.linear(0, 999)).map(Anh.num), 43 | ) 44 | }).log("anh2") 45 | anh3 <- (anh2 match { 46 | case Anh.Alphabet(_) => 47 | Gen 48 | .frequency1( 49 | 90 -> Gen.int(Range.linear(0, 999)).map(Anh.num), 50 | 10 -> Gen.constant(Anh.hyphen) 51 | ) 52 | case Anh.Num(_) => 53 | Gen 54 | .frequency1( 55 | 90 -> Gen.string(Gen.alpha, Range.linear(1, 3)).map(Anh.alphabet), 56 | 10 -> Gen.constant(Anh.hyphen) 57 | ) 58 | case Anh.Hyphen => 59 | Gen 60 | .frequency1( 61 | 50 -> Gen.string(Gen.alpha, Range.linear(1, 3)).map(Anh.alphabet), 62 | 50 -> Gen.int(Range.linear(0, 999)).map(Anh.num), 63 | ) 64 | }).log("anh3") 65 | anh4 <- (anh3 match { 66 | case Anh.Alphabet(_) => 67 | Gen 68 | .frequency1( 69 | 90 -> Gen.int(Range.linear(0, 999)).map(Anh.num), 70 | 10 -> Gen.constant(Anh.hyphen) 71 | ) 72 | case Anh.Num(_) => 73 | Gen 74 | .frequency1( 75 | 90 -> Gen.string(Gen.alpha, Range.linear(1, 3)).map(Anh.alphabet), 76 | 10 -> Gen.constant(Anh.hyphen) 77 | ) 78 | case Anh.Hyphen => 79 | Gen 80 | .frequency1( 81 | 50 -> Gen.string(Gen.alpha, Range.linear(1, 3)).map(Anh.alphabet), 82 | 50 -> Gen.int(Range.linear(0, 999)).map(Anh.num), 83 | ) 84 | }).log("anh4") 85 | anh5 <- (anh4 match { 86 | case Anh.Alphabet(_) => 87 | Gen 88 | .frequency1( 89 | 90 -> Gen.int(Range.linear(0, 999)).map(Anh.num), 90 | 10 -> Gen.constant(Anh.hyphen) 91 | ) 92 | case Anh.Num(_) => 93 | Gen 94 | .frequency1( 95 | 90 -> Gen.string(Gen.alpha, Range.linear(1, 3)).map(Anh.alphabet), 96 | 10 -> Gen.constant(Anh.hyphen) 97 | ) 98 | case Anh.Hyphen => 99 | Gen 100 | .frequency1( 101 | 50 -> Gen.string(Gen.alpha, Range.linear(1, 3)).map(Anh.alphabet), 102 | 50 -> Gen.int(Range.linear(0, 999)).map(Anh.num), 103 | ) 104 | }).log("anh5") 105 | input <- Gen.constant(List(anh1, anh2, anh3, anh4, anh5).map(_.render).mkString).log("input") 106 | } yield { 107 | val expected = Dsv(List(anh1, anh2, anh3, anh4, anh5)) 108 | val actual = Dsv.parse(input) 109 | actual ==== Right(expected) 110 | } 111 | 112 | @SuppressWarnings( 113 | Array("org.wartremover.warts.Equals", "org.wartremover.warts.ToString", "org.wartremover.warts.IterableOps") 114 | ) 115 | private val someNonAnhCharInts = LazyList 116 | // .range(1, 100_000) 117 | .range(1, 100000) 118 | .filterNot { n => 119 | val c = n.toChar 120 | c.isUpper || c.isLower || c.isDigit || c == '-' 121 | } 122 | .foldLeft(Vector.empty[(Int, Int)]) { 123 | case (Vector(), n) => Vector(n -> n) 124 | case ((head +: Vector()), n) => if (head._2 + 1 == n) Vector(head._1 -> n) else Vector(head._1 -> head._2, n -> n) 125 | case (xs, n) => if (xs.last._2 + 1 == n) xs.init :+ (xs.last._1 -> n) else xs :+ (n -> n) 126 | } 127 | .toList 128 | 129 | // private val someNonAnhChars = someNonAnhCharInts 130 | // .map { case (start, end) => start.toChar -> end.toChar } 131 | 132 | @SuppressWarnings(Array("org.wartremover.warts.IterableOps", "org.wartremover.warts.ToString")) 133 | def testParseInvalid: Property = for { 134 | charInt <- Gen 135 | .choice( 136 | Gen.int((Range.linear[Int] _).tupled(someNonAnhCharInts.head)), 137 | someNonAnhCharInts.tail.map { case (start, end) => Gen.int(Range.linear[Int](start, end)) } 138 | ) 139 | .list(Range.linear(1, 3)) 140 | .log("charInt") 141 | // _ = println(s"charInt=${charInt.mkString("[", ", ", "]")}") 142 | s <- Gen.constant(charInt.map(_.toChar).mkString).log("s") 143 | // s <- Gen 144 | // .string( 145 | // Gen.choice( 146 | // (Gen.char _).tupled(someNonAnhChars.head), 147 | // someNonAnhChars.tail.map { case (start, end) => Gen.char(start, end) } 148 | // ), 149 | // Range.linear(1, 3) 150 | // ) 151 | // .log("s") 152 | } yield { 153 | val actual = Dsv.parse(s) 154 | (actual ==== Left(Dsv.DsvParseError.invalidAlphaNumHyphenError(s.head, s.tail.toList))) 155 | .log( 156 | s">>> Invalid char detection failed: " + 157 | s"s=$s / Int=${s.map(_.toInt).mkString("[", ", ", "]")} / unicode=${s.map(c => "\\u%04x".format(c.toInt)).mkString}" 158 | ) 159 | } 160 | 161 | def testParseInvalid2: Result = { 162 | val actual = Dsv.parse("") 163 | actual ==== Left(Dsv.DsvParseError.emptyAlphaNumHyphenError) 164 | } 165 | 166 | } 167 | --------------------------------------------------------------------------------