diff --git a/.github/workflows/publish-snapshots.yml b/.github/workflows/publish-snapshots.yml index 607b7b5..42003ef 100644 --- a/.github/workflows/publish-snapshots.yml +++ b/.github/workflows/publish-snapshots.yml @@ -9,7 +9,7 @@ on: jobs: publish: - runs-on: ubuntu-latest + runs-on: macos-latest steps: - name: Checkout uses: actions/checkout@v3 @@ -24,4 +24,4 @@ jobs: - name: Publish snapshot if: success() - run: ./gradlew setSnapshotVersion publishToSonatype closeAndReleaseSonatypeStagingRepository \ No newline at end of file + run: ./gradlew -Psnapshot publishToSonatype closeAndReleaseSonatypeStagingRepository \ No newline at end of file diff --git a/.github/workflows/pull-requests.yml b/.github/workflows/pull-requests.yml index b5adfd2..5f502ad 100644 --- a/.github/workflows/pull-requests.yml +++ b/.github/workflows/pull-requests.yml @@ -10,7 +10,16 @@ on: jobs: unit-tests-pass: - runs-on: ubuntu-latest + strategy: + matrix: + include: + - test: jvmTest jsTest linuxX64Test + os: ubuntu-latest + - test: macosX64Test macosArm64Test iosSimulatorArm64Test iosX64Test + os: macos-latest + - test: mingwX64Test + os: windows-latest + runs-on: ${{ matrix.os }} steps: - name: Checkout uses: actions/checkout@v3 @@ -19,4 +28,4 @@ jobs: uses: gradle/wrapper-validation-action@v1.0.4 - name: Build and test - run: ./gradlew check + run: ./gradlew ${{ matrix.test }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 80f3786..8cffe8e 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -9,7 +9,7 @@ on: jobs: release: - runs-on: ubuntu-latest + runs-on: macos-latest steps: - name: Checkout uses: actions/checkout@v3 diff --git a/README.md b/README.md index a948d10..0af8165 100644 --- a/README.md +++ b/README.md @@ -14,6 +14,7 @@ val interval: IntInterval = interval( 0, 10, isEndIncluded = false ) val areIncluded = 0 in interval && 5 in interval // true val areExcluded = 10 !in interval && 15 !in interval // true val size: UInt = interval.size // 10 +val shifted = interval shr 10u // Shifted right by 10: [10, 20) ``` This protects against overflows (e.g. if `size > Int.MAX_VALUE`) but also offers better semantics. @@ -24,6 +25,7 @@ val now = Clock.System.now() val interval: InstantInterval = interval( now, now + 100.seconds ) val areIncluded = now + 50.seconds in interval // true val size: Duration = interval.size // 100 seconds +val shifted = interval shr 24.hours // 100 seconds 24 hours from now ``` ## Interval Unions @@ -36,6 +38,7 @@ Since operations are generally defined on the base interface, you can easily cha val start = interval( 0, 100 ) // Interval: [0, 100] val areIncluded = 50 in start && 100 in start // true val splitInTwo = start - interval( 25, 85 ) // Union: [[0, 25), (85, 100]] +val shiftBackAndForth = splitInTwo shr 100u shl 100u // == splitInTwo val areExcluded = 50 !in splitInTwo && 85 !in splitInTwo // true val unite = splitInTwo + interval( 10, 90 ) // Interval: [0, 100] val backToStart = start == unite // true @@ -63,17 +66,18 @@ Instead, new instances are returned if the operation results in a different set The following operations are available for any `IntervalUnion`: -| Operation | Description | -|:-------------------:|:--------------------------------------------------------------------------------------------:| -| `isEmpty()` | Determines whether this is an empty set. | -| `getBounds()` | Gets the upper and lower bound of the set. | -| `contains()` (`in`) | Determines whether a value lies in the set. | -| `minus()` (`-`) | Subtract an interval from the set. | -| `plus()` (`+`) | Add an interval from the set. | -| `intersects()` | Determines whether another interval intersects with this set. | -| `setEquals()` | Determines whether a set represents the same values. | -| `iterator()` | Iterate over all intervals in the union, in order. | -| `toString()` | Output as a string using common interval notation, e.g., `[0, 10]` or `[[0, 10), (10, 20]]`. | +| Operation | Description | +|:-----------------------:|:--------------------------------------------------------------------------------------------:| +| `isEmpty()` | Determines whether this is an empty set. | +| `getBounds()` | Gets the upper and lower bound of the set. | +| `contains()` (`in`) | Determines whether a value lies in the set. | +| `minus()` (`-`) | Subtract an interval from the set. | +| `plus()` (`+`) | Add an interval to the set. | +| `shift()` (`shl`/`shr`) | Move the interval by a specified offset. | +| `intersects()` | Determines whether another interval intersects with this set. | +| `setEquals()` | Determines whether a set represents the same values. | +| `iterator()` | Iterate over all intervals in the union, in order. | +| `toString()` | Output as a string using common interval notation, e.g., `[0, 10]` or `[[0, 10), (10, 20]]`. | The following operations are specific to `Interval`: @@ -87,6 +91,7 @@ The following operations are specific to `Interval`: | `lowerBound`, `upperBound` | Corresponds to `start` and `end`, but swapped if `isReversed`. | | `isLowerBoundIncluded`, `isUpperBoundIncluded` | Corresponds to `isStartIncluded` and `isEndIncluded`, but swapped if `isReversed`. | | `size` | The absolute difference between `start` and `end`. | +| `getValueAt()` | Get the value at a given percentage inside (0.0–1.0) or outside (< 0.0, > 1.0) the interval. | | `nonReversed()` | `reverse()` the interval in case it `isReversed`. | | `reverse()` | Return an interval which swaps `start` with `end`, as well as boundary inclusions. | | `canonicalize()` | Return the interval in canonical form. E.g., The canonical form of `[5, 1)` is `[2, 5]` for integer types. | diff --git a/build-logic/build.gradle.kts b/build-logic/build.gradle.kts index fe595b8..92ed708 100644 --- a/build-logic/build.gradle.kts +++ b/build-logic/build.gradle.kts @@ -8,6 +8,6 @@ repositories { } dependencies { - implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.0") - implementation("org.jetbrains.dokka:dokka-gradle-plugin:1.9.20") + implementation("org.jetbrains.kotlin:kotlin-gradle-plugin:2.1.20") + implementation("org.jetbrains.dokka:dokka-gradle-plugin:2.0.0") } diff --git a/build-logic/src/main/kotlin/interval.library-conventions.gradle.kts b/build-logic/src/main/kotlin/interval.library-conventions.gradle.kts index 8ef9548..657b21a 100644 --- a/build-logic/src/main/kotlin/interval.library-conventions.gradle.kts +++ b/build-logic/src/main/kotlin/interval.library-conventions.gradle.kts @@ -1,8 +1,8 @@ import java.io.FileInputStream import java.util.Properties -import org.jetbrains.dokka.Platform -import org.jetbrains.dokka.gradle.DokkaTask -import org.jetbrains.kotlin.gradle.ExperimentalKotlinGradlePluginApi +import org.jetbrains.dokka.gradle.engine.parameters.KotlinPlatform +import org.jetbrains.dokka.gradle.internal.InternalDokkaGradlePluginApi +import org.jetbrains.dokka.gradle.tasks.DokkaGenerateTask import org.jetbrains.kotlin.gradle.dsl.JvmTarget @@ -20,7 +20,6 @@ repositories { kotlin { jvm { - @OptIn(ExperimentalKotlinGradlePluginApi::class) compilerOptions { jvmTarget = JvmTarget.JVM_1_8 } @@ -34,14 +33,15 @@ kotlin { } binaries.executable() } - val hostOs = System.getProperty("os.name") - val isMingwX64 = hostOs.startsWith("Windows") - val nativeTarget = when { - hostOs == "Mac OS X" -> macosX64("native") - hostOs == "Linux" -> linuxX64("native") - isMingwX64 -> mingwX64("native") - else -> throw GradleException("Host OS is not supported in Kotlin/Native.") - } + linuxX64() + + macosX64() + macosArm64() + iosSimulatorArm64() + iosX64() + iosArm64() + + mingwX64() sourceSets { @@ -51,30 +51,29 @@ kotlin { implementation(kotlin("test")) } } - val jvmMain by getting - val jvmTest by getting - val jsMain by getting - val jsTest by getting - val nativeMain by getting - val nativeTest by getting } } // Documentation. -val dokkaJvmJavadoc by tasks.creating(DokkaTask::class) { +dokka { dokkaSourceSets { register("jvm") { - platform.set(Platform.jvm) + analysisPlatform.set(KotlinPlatform.JVM) sourceRoots.from(kotlin.sourceSets.getByName("jvmMain").kotlin.srcDirs) } } } +tasks.withType().configureEach { + // HACK: Dokka 2.0.0 exposes this debug file by default (https://github.com/Kotlin/dokka/issues/3958) + @OptIn( InternalDokkaGradlePluginApi::class ) + dokkaConfigurationJsonFile.convention( null as RegularFile? ) +} val javadocJar by tasks.creating(Jar::class) { group = JavaBasePlugin.DOCUMENTATION_GROUP description = "Create javadoc jar using Dokka" archiveClassifier.set("javadoc") - from(dokkaJvmJavadoc) + from(tasks.dokkaGeneratePublicationHtml) } @@ -88,7 +87,7 @@ publishing { repositories { maven { name = "local" - url = uri("${layout.buildDirectory}/repo") + url = uri("${layout.projectDirectory}/build/repository") } } publications.filterIsInstance().forEach { diff --git a/build.gradle.kts b/build.gradle.kts index 0dbee4f..bfaaf9d 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -21,7 +21,14 @@ if (publishPropertiesFile.exists()) { publishProperties.load(java.io.FileInputStream(publishPropertiesFile)) } group = "io.github.whathecode.kotlinx.interval" -version = "1.0.1" +version = "2.0.0" +if (properties.containsKey("snapshot")) +{ + val versionSplit = version.toString().split("-") + val snapshotVersion = "${versionSplit[0]}-SNAPSHOT" + version = snapshotVersion + rootProject.subprojects.forEach { it.version = snapshotVersion } +} nexusPublishing { repositories { sonatype { @@ -32,12 +39,3 @@ nexusPublishing { } } } -val setSnapshotVersion: Task by tasks.creating { - doFirst { - val versionSplit = version.toString().split("-") - val snapshotVersion = "${versionSplit[0]}-SNAPSHOT" - version = snapshotVersion - - rootProject.subprojects.forEach { it.version = snapshotVersion } - } -} diff --git a/gradle.properties b/gradle.properties index 590f3dd..237f4b8 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1,4 +1,6 @@ kotlin.mpp.applyDefaultHierarchyTemplate=false +org.jetbrains.dokka.experimental.gradle.pluginMode=V2Enabled +org.jetbrains.dokka.experimental.gradle.pluginMode.noWarn=true # Prevent out of memory errors. org.gradle.jvmargs=-XX:MaxMetaspaceSize=1024m diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 19cfad9..81aa1c0 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,5 +1,5 @@ distributionBase=GRADLE_USER_HOME distributionPath=wrapper/dists -distributionUrl=https\://services.gradle.org/distributions/gradle-8.9-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.11.1-bin.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/kotlin-js-store/yarn.lock b/kotlin-js-store/yarn.lock index 5b19fe7..c3cf40b 100644 --- a/kotlin-js-store/yarn.lock +++ b/kotlin-js-store/yarn.lock @@ -12,18 +12,6 @@ resolved "https://registry.yarnpkg.com/@discoveryjs/json-ext/-/json-ext-0.5.7.tgz#1d572bfbbe14b7704e0ba0f39b74815b84870d70" integrity sha512-dBVuXR082gk3jsFp7Rd/JI4kytwGHecnCoTtXFb7DB6CNHp4rg5k1bhg0nWdLGLnOV71lmDzGQaLMy8iPLY0pw== -"@isaacs/cliui@^8.0.2": - version "8.0.2" - resolved "https://registry.yarnpkg.com/@isaacs/cliui/-/cliui-8.0.2.tgz#b37667b7bc181c168782259bab42474fbf52b550" - integrity sha512-O8jcjabXaleOG9DQ0+ARXWZBTfnP4WNAqzuiJK7ll44AmxGKv/J2M4TPjxjY3znBCfvBXFzucm1twdyFybFqEA== - dependencies: - string-width "^5.1.2" - string-width-cjs "npm:string-width@^4.2.0" - strip-ansi "^7.0.1" - strip-ansi-cjs "npm:strip-ansi@^6.0.1" - wrap-ansi "^8.1.0" - wrap-ansi-cjs "npm:wrap-ansi@^7.0.0" - "@jridgewell/gen-mapping@^0.3.5": version "0.3.5" resolved "https://registry.yarnpkg.com/@jridgewell/gen-mapping/-/gen-mapping-0.3.5.tgz#dcce6aff74bdf6dad1a95802b69b04a2fcb1fb36" @@ -69,36 +57,11 @@ resolved "https://registry.yarnpkg.com/@js-joda/core/-/core-3.2.0.tgz#3e61e21b7b2b8a6be746df1335cf91d70db2a273" integrity sha512-PMqgJ0sw5B7FKb2d5bWYIoxjri+QlW/Pys7+Rw82jSH0QN3rB05jZ/VrrsUdh1w4+i2kw9JOejXGq/KhDOX7Kg== -"@jsonjoy.com/base64@^1.1.1": - version "1.1.2" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/base64/-/base64-1.1.2.tgz#cf8ea9dcb849b81c95f14fc0aaa151c6b54d2578" - integrity sha512-q6XAnWQDIMA3+FTiOYajoYqySkO+JSat0ytXGSuRdq9uXE7o92gzuQwQM14xaCRlBLGq3v5miDGC4vkVTn54xA== - -"@jsonjoy.com/json-pack@^1.0.3": - version "1.0.4" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/json-pack/-/json-pack-1.0.4.tgz#ab59c642a2e5368e8bcfd815d817143d4f3035d0" - integrity sha512-aOcSN4MeAtFROysrbqG137b7gaDDSmVrl5mpo6sT/w+kcXpWnzhMjmY/Fh/sDx26NBxyIE7MB1seqLeCAzy9Sg== - dependencies: - "@jsonjoy.com/base64" "^1.1.1" - "@jsonjoy.com/util" "^1.1.2" - hyperdyperid "^1.2.0" - thingies "^1.20.0" - -"@jsonjoy.com/util@^1.1.2": - version "1.2.0" - resolved "https://registry.yarnpkg.com/@jsonjoy.com/util/-/util-1.2.0.tgz#0fe9a92de72308c566ebcebe8b5a3f01d3149df2" - integrity sha512-4B8B+3vFsY4eo33DMKyJPlQ3sBMpPFUZK2dr3O3rXrOGKKbYG44J0XSFkDo1VOQiri5HFEhIeVvItjR2xcazmg== - "@leichtgewicht/ip-codec@^2.0.1": version "2.0.4" resolved "https://registry.yarnpkg.com/@leichtgewicht/ip-codec/-/ip-codec-2.0.4.tgz#b2ac626d6cb9c8718ab459166d4bb405b8ffa78b" integrity sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A== -"@pkgjs/parseargs@^0.11.0": - version "0.11.0" - resolved "https://registry.yarnpkg.com/@pkgjs/parseargs/-/parseargs-0.11.0.tgz#a77ea742fab25775145434eb1d2328cf5013ac33" - integrity sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg== - "@socket.io/component-emitter@~3.1.0": version "3.1.0" resolved "https://registry.yarnpkg.com/@socket.io/component-emitter/-/component-emitter-3.1.0.tgz#96116f2a912e0c02817345b3c10751069920d553" @@ -112,14 +75,14 @@ "@types/connect" "*" "@types/node" "*" -"@types/bonjour@^3.5.13": +"@types/bonjour@^3.5.9": version "3.5.13" resolved "https://registry.yarnpkg.com/@types/bonjour/-/bonjour-3.5.13.tgz#adf90ce1a105e81dd1f9c61fdc5afda1bfb92956" integrity sha512-z9fJ5Im06zvUL548KvYNecEVlA7cVDkGUi6kZusb04mpyEFKCIZJvloCcmpmLaIahDpOQGHaHmG6imtPMmPXGQ== dependencies: "@types/node" "*" -"@types/connect-history-api-fallback@^1.5.4": +"@types/connect-history-api-fallback@^1.3.5": version "1.5.4" resolved "https://registry.yarnpkg.com/@types/connect-history-api-fallback/-/connect-history-api-fallback-1.5.4.tgz#7de71645a103056b48ac3ce07b3520b819c1d5b3" integrity sha512-n6Cr2xS1h4uAulPRdlw6Jl6s1oG8KrVilPN2yUITEs+K48EzMJJ3W1xy8K5eWuFvjp3R74AOIGSmp2UfBJ8HFw== @@ -146,27 +109,6 @@ dependencies: "@types/node" "*" -"@types/eslint-scope@^3.7.3": - version "3.7.7" - resolved "https://registry.yarnpkg.com/@types/eslint-scope/-/eslint-scope-3.7.7.tgz#3108bd5f18b0cdb277c867b3dd449c9ed7079ac5" - integrity sha512-MzMFlSLBqNF2gcHWO0G1vP/YQyfvrxZ0bF+u7mzUdZ1/xK4A4sru+nraZz5i3iEIk1l1uyicaDVTB4QbbEkAYg== - dependencies: - "@types/eslint" "*" - "@types/estree" "*" - -"@types/eslint@*": - version "8.4.2" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-8.4.2.tgz#48f2ac58ab9c631cb68845c3d956b28f79fad575" - integrity sha512-Z1nseZON+GEnFjJc04sv4NSALGjhFwy6K0HXt7qsn5ArfAKtb63dXNJHf+1YW6IpOIYRBGUbu3GwJdj8DGnCjA== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*": - version "0.0.51" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.51.tgz#cfd70924a25a3fd32b218e5e420e6897e1ac4f40" - integrity sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ== - "@types/estree@^1.0.5": version "1.0.5" resolved "https://registry.yarnpkg.com/@types/estree/-/estree-1.0.5.tgz#a6ce3e556e00fd9895dd872dd172ad0d4bd687f4" @@ -182,7 +124,7 @@ "@types/range-parser" "*" "@types/send" "*" -"@types/express@*", "@types/express@^4.17.21": +"@types/express@*", "@types/express@^4.17.13": version "4.17.21" resolved "https://registry.yarnpkg.com/@types/express/-/express-4.17.21.tgz#c26d4a151e60efe0084b23dc3369ebc631ed192d" integrity sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ== @@ -204,7 +146,7 @@ dependencies: "@types/node" "*" -"@types/json-schema@*", "@types/json-schema@^7.0.8": +"@types/json-schema@^7.0.8": version "7.0.11" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.11.tgz#d421b6c527a3037f7c84433fd2c4229e016863d3" integrity sha512-wOuvG1SN4Us4rez+tylwwwCV1psiNVOkJeM3AUWUNWg/jDQY2+HE/444y5gc+jBmRqASOm2Oeh5c1axHobwRKQ== @@ -246,10 +188,10 @@ resolved "https://registry.yarnpkg.com/@types/range-parser/-/range-parser-1.2.7.tgz#50ae4353eaaddc04044279812f52c8c65857dbcb" integrity sha512-hKormJbkJqzQGhziax5PItDUTMAM9uE2XXQmM37dyd4hVM+5aVl7oVxMVUiVQn2oCQFN/LKCZdvSM0pFRqbSmQ== -"@types/retry@0.12.2": - version "0.12.2" - resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.2.tgz#ed279a64fa438bb69f2480eda44937912bb7480a" - integrity sha512-XISRgDJ2Tc5q4TRqvgJtzsRkFYNJzZrhTdtMoGVBttwzzQJkPnS3WWTFc7kuDRoPtPakl+T+OfdEUjYJj7Jbow== +"@types/retry@0.12.0": + version "0.12.0" + resolved "https://registry.yarnpkg.com/@types/retry/-/retry-0.12.0.tgz#2b35eccfcee7d38cd72ad99232fbd58bffb3c84d" + integrity sha512-wWKOClTTiizcZhXnPY4wikVAwmdYHp8q6DmC+EJUzAMsycb7HB32Kh9RN4+0gExjmPmZSAQjgURXIGATPegAvA== "@types/send@*": version "0.17.4" @@ -259,7 +201,7 @@ "@types/mime" "^1" "@types/node" "*" -"@types/serve-index@^1.9.4": +"@types/serve-index@^1.9.1": version "1.9.4" resolved "https://registry.yarnpkg.com/@types/serve-index/-/serve-index-1.9.4.tgz#e6ae13d5053cb06ed36392110b4f9a49ac4ec898" integrity sha512-qLpGZ/c2fhSs5gnYsQxtDEq3Oy8SXPClIXkW5ghvAvsNuVSA8k+gCONcUCS/UjLEYvYps+e8uBtfgXgvhwfNug== @@ -275,7 +217,7 @@ "@types/mime" "*" "@types/node" "*" -"@types/serve-static@^1.15.5": +"@types/serve-static@^1.13.10": version "1.15.7" resolved "https://registry.yarnpkg.com/@types/serve-static/-/serve-static-1.15.7.tgz#22174bbd74fb97fe303109738e9b5c2f3064f714" integrity sha512-W8Ym+h8nhuRwaKPaDw34QUkwsGi6Rc4yYqvKFo5rm2FUEhCFbzVWrxXUxuKK8TASjWsysJY0nsmNCGhCOIsrOw== @@ -284,17 +226,17 @@ "@types/node" "*" "@types/send" "*" -"@types/sockjs@^0.3.36": +"@types/sockjs@^0.3.33": version "0.3.36" resolved "https://registry.yarnpkg.com/@types/sockjs/-/sockjs-0.3.36.tgz#ce322cf07bcc119d4cbf7f88954f3a3bd0f67535" integrity sha512-MK9V6NzAS1+Ud7JV9lJLFqW85VbC9dq3LmwZCuBe4wBDgKC0Kj/jd8Xl+nSviU+Qc3+m7umHHyHg//2KSa0a0Q== dependencies: "@types/node" "*" -"@types/ws@^8.5.10": - version "8.5.11" - resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.11.tgz#90ad17b3df7719ce3e6bc32f83ff954d38656508" - integrity sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w== +"@types/ws@^8.5.5": + version "8.5.13" + resolved "https://registry.yarnpkg.com/@types/ws/-/ws-8.5.13.tgz#6414c280875e2691d0d1e080b05addbf5cb91e20" + integrity sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA== dependencies: "@types/node" "*" @@ -452,10 +394,10 @@ accepts@~1.3.4, accepts@~1.3.5, accepts@~1.3.8: mime-types "~2.1.34" negotiator "0.6.3" -acorn-import-assertions@^1.9.0: - version "1.9.0" - resolved "https://registry.yarnpkg.com/acorn-import-assertions/-/acorn-import-assertions-1.9.0.tgz#507276249d684797c84e0734ef84860334cfb1ac" - integrity sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA== +acorn-import-attributes@^1.9.5: + version "1.9.5" + resolved "https://registry.yarnpkg.com/acorn-import-attributes/-/acorn-import-attributes-1.9.5.tgz#7eb1557b1ba05ef18b5ed0ec67591bfab04688ef" + integrity sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ== acorn@^8.7.1, acorn@^8.8.2: version "8.11.3" @@ -501,10 +443,10 @@ ajv@^8.0.0, ajv@^8.9.0: require-from-string "^2.0.2" uri-js "^4.2.2" -ansi-colors@4.1.1: - version "4.1.1" - resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.1.tgz#cbb9ae256bf750af1eab344f229aa27fe94ba348" - integrity sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA== +ansi-colors@^4.1.3: + version "4.1.3" + resolved "https://registry.yarnpkg.com/ansi-colors/-/ansi-colors-4.1.3.tgz#37611340eb2243e70cc604cad35d63270d48781b" + integrity sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw== ansi-html-community@^0.0.8: version "0.0.8" @@ -516,11 +458,6 @@ ansi-regex@^5.0.1: resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-5.0.1.tgz#082cb2c89c9fe8659a311a53bd6a4dc5301db304" integrity sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ== -ansi-regex@^6.0.1: - version "6.0.1" - resolved "https://registry.yarnpkg.com/ansi-regex/-/ansi-regex-6.0.1.tgz#3183e38fae9a65d7cb5e53945cd5897d0260a06a" - integrity sha512-n5M855fKb2SsfMIiFFoVrABHJC8QtHwVx+mHWP3QcEqBHYienj5dHSgjbxtC0WEZXYt4wcD6zrQElDPhFuZgfA== - ansi-styles@^4.0.0, ansi-styles@^4.1.0: version "4.3.0" resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-4.3.0.tgz#edd803628ae71c04c85ae7a0906edad34b648937" @@ -528,11 +465,6 @@ ansi-styles@^4.0.0, ansi-styles@^4.1.0: dependencies: color-convert "^2.0.1" -ansi-styles@^6.1.0: - version "6.2.1" - resolved "https://registry.yarnpkg.com/ansi-styles/-/ansi-styles-6.2.1.tgz#0e62320cf99c21afff3b3012192546aacbfb05c5" - integrity sha512-bN798gFfQX+viw3R7yrGWRqnrN2oRkEkUjjl4JNn4E8GxxbjtG3FbrEIIY3l8/hrwUwIeCZvi4QuOTP4MErVug== - anymatch@~3.1.2: version "3.1.2" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-3.1.2.tgz#c0557c096af32f106198f4f4e2a383537e378716" @@ -607,10 +539,10 @@ body-parser@^1.19.0: type-is "~1.6.18" unpipe "1.0.0" -bonjour-service@^1.2.1: - version "1.2.1" - resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.2.1.tgz#eb41b3085183df3321da1264719fbada12478d02" - integrity sha512-oSzCS2zV14bh2kji6vNe7vrpJYCHGvcZnlffFQ1MEoX/WOeQ/teD8SYWKR942OI3INjq8OMNJlbPK5LLLUxFDw== +bonjour-service@^1.0.11: + version "1.3.0" + resolved "https://registry.yarnpkg.com/bonjour-service/-/bonjour-service-1.3.0.tgz#80d867430b5a0da64e82a8047fc1e355bdb71722" + integrity sha512-3YuAUiSkWykd+2Azjgyxei8OWf8thdn8AITIog2M4UICzoqfjlqr64WIjEXZllf/W6vK1goqleSR6brGomxQqA== dependencies: fast-deep-equal "^3.1.3" multicast-dns "^7.2.5" @@ -637,7 +569,7 @@ braces@^3.0.2, braces@~3.0.2: dependencies: fill-range "^7.0.1" -browser-stdout@1.3.1: +browser-stdout@^1.3.1: version "1.3.1" resolved "https://registry.yarnpkg.com/browser-stdout/-/browser-stdout-1.3.1.tgz#baa559ee14ced73452229bad7326467c61fabd60" integrity sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw== @@ -657,13 +589,6 @@ buffer-from@^1.0.0: resolved "https://registry.yarnpkg.com/buffer-from/-/buffer-from-1.1.2.tgz#2b146a6fd72e80b4f55d255f35ed59a3a9a41bd5" integrity sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ== -bundle-name@^4.1.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/bundle-name/-/bundle-name-4.1.0.tgz#f3b96b34160d6431a19d7688135af7cfb8797889" - integrity sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q== - dependencies: - run-applescript "^7.0.0" - bytes@3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/bytes/-/bytes-3.0.0.tgz#d32815404d689699f85a4ea4fa8755dd13a96048" @@ -700,7 +625,7 @@ chalk@^4.1.0: ansi-styles "^4.1.0" supports-color "^7.1.0" -chokidar@3.5.3, chokidar@^3.5.1: +chokidar@^3.5.1: version "3.5.3" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.5.3.tgz#1cf37c8707b932bd1af1ae22c0432e2acd1903bd" integrity sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw== @@ -715,7 +640,7 @@ chokidar@3.5.3, chokidar@^3.5.1: optionalDependencies: fsevents "~2.3.2" -chokidar@^3.6.0: +chokidar@^3.5.3: version "3.6.0" resolved "https://registry.yarnpkg.com/chokidar/-/chokidar-3.6.0.tgz#197c6cc669ef2a8dc5e7b4d97ee4e092c3eb0d5b" integrity sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw== @@ -870,7 +795,7 @@ cors@~2.8.5: object-assign "^4" vary "^1" -cross-spawn@^7.0.0, cross-spawn@^7.0.3: +cross-spawn@^7.0.3: version "7.0.3" resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== @@ -896,31 +821,25 @@ debug@2.6.9: dependencies: ms "2.0.0" -debug@4.3.4, debug@^4.1.0, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2, debug@~4.3.4: +debug@^4.1.0, debug@^4.3.4, debug@~4.3.1, debug@~4.3.2, debug@~4.3.4: version "4.3.4" resolved "https://registry.yarnpkg.com/debug/-/debug-4.3.4.tgz#1319f6579357f2338d3337d2cdd4914bb5dcc865" integrity sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ== dependencies: ms "2.1.2" +debug@^4.3.5: + version "4.4.0" + resolved "https://registry.yarnpkg.com/debug/-/debug-4.4.0.tgz#2b3f2aea2ffeb776477460267377dc8710faba8a" + integrity sha512-6WTZ/IxCY/T6BALoZHaE4ctp9xm+Z5kY/pzYaCHRFeyVhojxlrm+46y68HA6hr0TcwEssoxNiDEUJQjfPZ/RYA== + dependencies: + ms "^2.1.3" + decamelize@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/decamelize/-/decamelize-4.0.0.tgz#aa472d7bf660eb15f3494efd531cab7f2a709837" integrity sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ== -default-browser-id@^5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/default-browser-id/-/default-browser-id-5.0.0.tgz#a1d98bf960c15082d8a3fa69e83150ccccc3af26" - integrity sha512-A6p/pu/6fyBcA1TRz/GqWYPViplrftcW2gZC9q79ngNCKAeR/X3gcEdXQHl4KNXV+3wgIJ1CPkJQ3IHM6lcsyA== - -default-browser@^5.2.1: - version "5.2.1" - resolved "https://registry.yarnpkg.com/default-browser/-/default-browser-5.2.1.tgz#7b7ba61204ff3e425b556869ae6d3e9d9f1712cf" - integrity sha512-WY/3TUME0x3KPYdRRxEJJvXRHV4PyPoUsxtZa78lwItwRQRHhd2U9xOscaT/YTf8uCXIAjeJOFBVEh/7FtD8Xg== - dependencies: - bundle-name "^4.1.0" - default-browser-id "^5.0.0" - default-gateway@^6.0.3: version "6.0.3" resolved "https://registry.yarnpkg.com/default-gateway/-/default-gateway-6.0.3.tgz#819494c888053bdb743edbf343d6cdf7f2943a71" @@ -928,10 +847,10 @@ default-gateway@^6.0.3: dependencies: execa "^5.0.0" -define-lazy-prop@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-3.0.0.tgz#dbb19adfb746d7fc6d734a06b72f4a00d021255f" - integrity sha512-N+MeXYoqr3pOgn8xfyRPREN7gHakLYjhsHhWGT3fWAiL4IkAt0iDw14QiiEm2bE30c5XX5q0FtAA3CK5f9/BUg== +define-lazy-prop@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/define-lazy-prop/-/define-lazy-prop-2.0.0.tgz#3f7ae421129bcaaac9bc74905c98a0009ec9ee7f" + integrity sha512-Ds09qNh8yw3khSjiJjiUInaGX9xlqZDY7JVryGxdxV7NPeuqQfplOpQ66yJFZut3jLa5zOwkXw1g9EI2uKh4Og== depd@2.0.0: version "2.0.0" @@ -958,10 +877,10 @@ di@^0.0.1: resolved "https://registry.yarnpkg.com/di/-/di-0.0.1.tgz#806649326ceaa7caa3306d75d985ea2748ba913c" integrity sha1-gGZJMmzqp8qjMG112YXqJ0i6kTw= -diff@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/diff/-/diff-5.0.0.tgz#7ed6ad76d859d030787ec35855f5b1daf31d852b" - integrity sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w== +diff@^5.2.0: + version "5.2.0" + resolved "https://registry.yarnpkg.com/diff/-/diff-5.2.0.tgz#26ded047cd1179b78b9537d5ef725503ce1ae531" + integrity sha512-uIFDxqpRZGZ6ThOk84hEfqWoHx2devRFvpTZcTHur85vImfaxUbTW9Ryh4CpCuDnToOP1CEtXKIgytHBPVff5A== dns-packet@^5.2.2: version "5.6.1" @@ -980,11 +899,6 @@ dom-serialize@^2.2.1: extend "^3.0.0" void-elements "^2.0.0" -eastasianwidth@^0.2.0: - version "0.2.0" - resolved "https://registry.yarnpkg.com/eastasianwidth/-/eastasianwidth-0.2.0.tgz#696ce2ec0aa0e6ea93a397ffcf24aa7840c827cb" - integrity sha512-I88TYZWc9XiYHRQ4/3c5rjjfgkjhLyW2luGIheGERbNQ6OY7yTybanSpDXZa8y7VUP9YmDcYa+eyq4ca7iLqWA== - ee-first@1.1.1: version "1.1.1" resolved "https://registry.yarnpkg.com/ee-first/-/ee-first-1.1.1.tgz#590c61156b0ae2f4f0255732a158b266bc56b21d" @@ -1000,11 +914,6 @@ emoji-regex@^8.0.0: resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-8.0.0.tgz#e818fd69ce5ccfcb404594f842963bf53164cc37" integrity sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A== -emoji-regex@^9.2.2: - version "9.2.2" - resolved "https://registry.yarnpkg.com/emoji-regex/-/emoji-regex-9.2.2.tgz#840c8803b0d8047f4ff0cf963176b32d4ef3ed72" - integrity sha512-L18DaJsXSUk2+42pv8mLs5jJT2hqFkFE4j21wOmgbUqsZ2hL72NsUU785g9RXgo3s0ZNgVl42TiHp3ZtOv/Vyg== - encodeurl@~1.0.2: version "1.0.2" resolved "https://registry.yarnpkg.com/encodeurl/-/encodeurl-1.0.2.tgz#ad3ff4c86ec2d029322f5a02c3a9a606c95b3f59" @@ -1031,10 +940,10 @@ engine.io@~6.5.2: engine.io-parser "~5.2.1" ws "~8.11.0" -enhanced-resolve@^5.16.0: - version "5.17.1" - resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.17.1.tgz#67bfbbcc2f81d511be77d686a90267ef7f898a15" - integrity sha512-LMHl3dXhTcfv8gM4kEzIUeTQ+7fpdA0l2tUf34BddXPkz2A5xJ5L/Pchd5BL6rdccM9QGvu0sWZzK1Z1t4wwyg== +enhanced-resolve@^5.17.1: + version "5.18.0" + resolved "https://registry.yarnpkg.com/enhanced-resolve/-/enhanced-resolve-5.18.0.tgz#91eb1db193896b9801251eeff1c6980278b1e404" + integrity sha512-0/r0MySGYG8YqlayBZ6MuCfECmHFdJ5qyPh8s8wa5Hnm6SaFLSK1VYCbj+NKp090Nm1caZhD+QTnmxO7esYGyQ== dependencies: graceful-fs "^4.2.4" tapable "^2.2.0" @@ -1069,7 +978,7 @@ escape-html@~1.0.3: resolved "https://registry.yarnpkg.com/escape-html/-/escape-html-1.0.3.tgz#0258eae4d3d0c0974de1c169188ef0051d1d1988" integrity sha1-Aljq5NPQwJdN4cFpGI7wBR0dGYg= -escape-string-regexp@4.0.0: +escape-string-regexp@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz#14ba83a5d373e3d311e5afca29cf5bfad965bf34" integrity sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA== @@ -1226,14 +1135,6 @@ finalhandler@1.2.0: statuses "2.0.1" unpipe "~1.0.0" -find-up@5.0.0: - version "5.0.0" - resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" - integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== - dependencies: - locate-path "^6.0.0" - path-exists "^4.0.0" - find-up@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/find-up/-/find-up-4.1.0.tgz#97afe7d6cdc0bc5928584b7c8d7b16e8a9aa5d19" @@ -1242,6 +1143,14 @@ find-up@^4.0.0: locate-path "^5.0.0" path-exists "^4.0.0" +find-up@^5.0.0: + version "5.0.0" + resolved "https://registry.yarnpkg.com/find-up/-/find-up-5.0.0.tgz#4c92819ecb7083561e4f4a240a86be5198f536fc" + integrity sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng== + dependencies: + locate-path "^6.0.0" + path-exists "^4.0.0" + flat@^5.0.2: version "5.0.2" resolved "https://registry.yarnpkg.com/flat/-/flat-5.0.2.tgz#8ca6fe332069ffa9d324c327198c598259ceb241" @@ -1257,14 +1166,6 @@ follow-redirects@^1.0.0: resolved "https://registry.yarnpkg.com/follow-redirects/-/follow-redirects-1.15.0.tgz#06441868281c86d0dda4ad8bdaead2d02dca89d4" integrity sha512-aExlJShTV4qOUOL7yF1U5tvLCB0xQuudbf6toyYA0E/acBNw71mvjFTnLaRp50aQaYocMR0a/RMMBIHeZnGyjQ== -foreground-child@^3.1.0: - version "3.2.1" - resolved "https://registry.yarnpkg.com/foreground-child/-/foreground-child-3.2.1.tgz#767004ccf3a5b30df39bed90718bab43fe0a59f7" - integrity sha512-PXUUyLqrR2XCWICfv6ukppP96sdFwWbNEnfEMt7jNsISjMsvaLNinAHNDYyvkyU+SZG2BTSbT5NjG+vZslfGTA== - dependencies: - cross-spawn "^7.0.0" - signal-exit "^4.0.1" - format-util@^1.0.5: version "1.0.5" resolved "https://registry.yarnpkg.com/format-util/-/format-util-1.0.5.tgz#1ffb450c8a03e7bccffe40643180918cc297d271" @@ -1289,6 +1190,11 @@ fs-extra@^8.1.0: jsonfile "^4.0.0" universalify "^0.1.0" +fs-monkey@^1.0.4: + version "1.0.6" + resolved "https://registry.yarnpkg.com/fs-monkey/-/fs-monkey-1.0.6.tgz#8ead082953e88d992cf3ff844faa907b26756da2" + integrity sha512-b1FMfwetIKymC0eioW7mTywihSQE4oLzQn1dB6rZB5fx/3NpNEdAWeCSMB+60/AeT0TCXsxzAlcYVEFCTAksWg== + fs.realpath@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/fs.realpath/-/fs.realpath-1.0.0.tgz#1504ad2523158caa40db4a2787cb01411994ea4f" @@ -1340,29 +1246,6 @@ glob-to-regexp@^0.4.1: resolved "https://registry.yarnpkg.com/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz#c75297087c851b9a578bd217dd59a92f59fe546e" integrity sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw== -glob@8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" - integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^5.0.1" - once "^1.3.0" - -glob@^10.3.7: - version "10.4.5" - resolved "https://registry.yarnpkg.com/glob/-/glob-10.4.5.tgz#f4d9f0b90ffdbab09c9d77f5f29b4262517b0956" - integrity sha512-7Bv8RF0k6xjo7d4A/PxYLbUCfb6c+Vpd2/mB2yRDlew7Jb5hEXiCD9ibfO7wpk8i4sevK6DFny9h7EYbM3/sHg== - dependencies: - foreground-child "^3.1.0" - jackspeak "^3.1.2" - minimatch "^9.0.4" - minipass "^7.1.2" - package-json-from-dist "^1.0.0" - path-scurry "^1.11.1" - glob@^7.1.3, glob@^7.1.7: version "7.2.0" resolved "https://registry.yarnpkg.com/glob/-/glob-7.2.0.tgz#d15535af7732e02e948f4c41628bd910293f6023" @@ -1375,6 +1258,17 @@ glob@^7.1.3, glob@^7.1.7: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^8.1.0: + version "8.1.0" + resolved "https://registry.yarnpkg.com/glob/-/glob-8.1.0.tgz#d388f656593ef708ee3e34640fdfb99a9fd1c33e" + integrity sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^5.0.1" + once "^1.3.0" + graceful-fs@^4.1.2, graceful-fs@^4.1.6, graceful-fs@^4.2.0, graceful-fs@^4.2.4, graceful-fs@^4.2.6: version "4.2.10" resolved "https://registry.yarnpkg.com/graceful-fs/-/graceful-fs-4.2.10.tgz#147d3a006da4ca3ce14728c7aefc287c367d7a6c" @@ -1414,7 +1308,7 @@ hasown@^2.0.0: dependencies: function-bind "^1.1.2" -he@1.2.0: +he@^1.2.0: version "1.2.0" resolved "https://registry.yarnpkg.com/he/-/he-1.2.0.tgz#84ae65fa7eafb165fddb61566ae14baf05664f0f" integrity sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw== @@ -1429,7 +1323,7 @@ hpack.js@^2.1.6: readable-stream "^2.0.1" wbuf "^1.1.0" -html-entities@^2.4.0: +html-entities@^2.3.2: version "2.5.2" resolved "https://registry.yarnpkg.com/html-entities/-/html-entities-2.5.2.tgz#201a3cf95d3a15be7099521620d19dfb4f65359f" integrity sha512-K//PSRMQk4FZ78Kyau+mZurHn3FH0Vwr+H36eE0rPbeYkRRi9YxceYPhuN60UwWorxyKHhqoAJl2OFKa4BVtaA== @@ -1490,11 +1384,6 @@ human-signals@^2.1.0: resolved "https://registry.yarnpkg.com/human-signals/-/human-signals-2.1.0.tgz#dc91fcba42e4d06e4abaed33b3e7a3c02f514ea0" integrity sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw== -hyperdyperid@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/hyperdyperid/-/hyperdyperid-1.2.0.tgz#59668d323ada92228d2a869d3e474d5a33b69e6b" - integrity sha512-Y93lCzHYgGWdrJ66yIktxiaGULYc6oGiABxhcO5AufBeOyoIdZF7bIfLaOrbM0iGIOXQQgxxRrFEnb+Y6w1n4A== - iconv-lite@0.4.24: version "0.4.24" resolved "https://registry.yarnpkg.com/iconv-lite/-/iconv-lite-0.4.24.tgz#2022b4b25fbddc21d2f524974a474aafe733908b" @@ -1545,7 +1434,7 @@ ipaddr.js@1.9.1: resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-1.9.1.tgz#bff38543eeb8984825079ff3a2a8e6cbd46781b3" integrity sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g== -ipaddr.js@^2.1.0: +ipaddr.js@^2.0.1: version "2.2.0" resolved "https://registry.yarnpkg.com/ipaddr.js/-/ipaddr.js-2.2.0.tgz#d33fa7bac284f4de7af949638c9d68157c6b92e8" integrity sha512-Ag3wB2o37wslZS19hZqorUnrnzSkpOVy+IiiDEiTqNubEYpYuHWIf6K4psgN2ZWKExS4xhVCrRVfb/wfW8fWJA== @@ -1564,10 +1453,10 @@ is-core-module@^2.13.0: dependencies: hasown "^2.0.0" -is-docker@^3.0.0: - version "3.0.0" - resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-3.0.0.tgz#90093aa3106277d8a77a5910dbae71747e15a200" - integrity sha512-eljcgEDlEns/7AXFosB5K/2nCM4P7FQPkGc/DWLy5rmFEWvZayGrik1d9/QIY5nJ4f9YsVvBkA6kJpHn9rISdQ== +is-docker@^2.0.0, is-docker@^2.1.1: + version "2.2.1" + resolved "https://registry.yarnpkg.com/is-docker/-/is-docker-2.2.1.tgz#33eeabe23cfe86f14bde4408a02c0cfb853acdaa" + integrity sha512-F+i2BKsFrH66iaUFc0woD8sLy8getkwTwtOBjvs56Cx4CgJDeKQeqfz8wAYiSb8JOprWhHH5p77PbmYCvvUuXQ== is-extglob@^2.1.1: version "2.1.1" @@ -1586,18 +1475,6 @@ is-glob@^4.0.1, is-glob@~4.0.1: dependencies: is-extglob "^2.1.1" -is-inside-container@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/is-inside-container/-/is-inside-container-1.0.0.tgz#e81fba699662eb31dbdaf26766a61d4814717ea4" - integrity sha512-KIYLCCJghfHZxqjYBE7rEy0OBuTd5xCHS7tHVgvCLkx7StIoaxwNW3hCALgEUjFfeRk+MG/Qxmp/vtETEF3tRA== - dependencies: - is-docker "^3.0.0" - -is-network-error@^1.0.0: - version "1.1.0" - resolved "https://registry.yarnpkg.com/is-network-error/-/is-network-error-1.1.0.tgz#d26a760e3770226d11c169052f266a4803d9c997" - integrity sha512-tUdRRAnhT+OtCZR/LxZelH/C7QtjtFrTu5tXCA8pl55eTUElUHT+GPYV8MBMBvea/j+NxQqVt3LbWMRir7Gx9g== - is-number@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/is-number/-/is-number-7.0.0.tgz#7535345b896734d5f80c4d06c50955527a14f12b" @@ -1630,12 +1507,12 @@ is-unicode-supported@^0.1.0: resolved "https://registry.yarnpkg.com/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz#3f26c76a809593b52bfa2ecb5710ed2779b522a7" integrity sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw== -is-wsl@^3.1.0: - version "3.1.0" - resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-3.1.0.tgz#e1c657e39c10090afcbedec61720f6b924c3cbd2" - integrity sha512-UcVfVfaK4Sc4m7X3dUSoHoozQGBEFeDC+zVo06t98xe8CzHSZZBekNXH+tu0NalHolcJ/QAGqS46Hef7QXBIMw== +is-wsl@^2.2.0: + version "2.2.0" + resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-2.2.0.tgz#74a4c76e77ca9fd3f932f290c17ea326cd157271" + integrity sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww== dependencies: - is-inside-container "^1.0.0" + is-docker "^2.0.0" isarray@~1.0.0: version "1.0.0" @@ -1657,15 +1534,6 @@ isobject@^3.0.1: resolved "https://registry.yarnpkg.com/isobject/-/isobject-3.0.1.tgz#4e431e92b11a9731636aa1f9c8d1ccbcfdab78df" integrity sha1-TkMekrEalzFjaqH5yNHMvP2reN8= -jackspeak@^3.1.2: - version "3.4.3" - resolved "https://registry.yarnpkg.com/jackspeak/-/jackspeak-3.4.3.tgz#8833a9d89ab4acde6188942bd1c53b6390ed5a8a" - integrity sha512-OGlZQpz2yfahA/Rd1Y8Cd9SIEsqvXkLVoSw/cgwhnhFMDbsQFeZYoJJ7bIZBS9BcamUW96asq/npPWugM+RQBw== - dependencies: - "@isaacs/cliui" "^8.0.2" - optionalDependencies: - "@pkgjs/parseargs" "^0.11.0" - jest-worker@^27.4.5: version "27.5.1" resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-27.5.1.tgz#8d146f0900e8973b106b6f73cc1e9a8cb86f8db0" @@ -1675,7 +1543,7 @@ jest-worker@^27.4.5: merge-stream "^2.0.0" supports-color "^8.0.0" -js-yaml@4.1.0: +js-yaml@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/js-yaml/-/js-yaml-4.1.0.tgz#c1fb65f8f5017901cdd2c951864ba18458a10602" integrity sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA== @@ -1734,10 +1602,10 @@ karma-webpack@5.0.1: minimatch "^9.0.3" webpack-merge "^4.1.5" -karma@6.4.3: - version "6.4.3" - resolved "https://registry.yarnpkg.com/karma/-/karma-6.4.3.tgz#763e500f99597218bbb536de1a14acc4ceea7ce8" - integrity sha512-LuucC/RE92tJ8mlCwqEoRWXP38UMAqpnq98vktmS9SznSoUPPUJQbc91dHcxcunROvfQjdORVA/YFviH+Xci9Q== +karma@6.4.4: + version "6.4.4" + resolved "https://registry.yarnpkg.com/karma/-/karma-6.4.4.tgz#dfa5a426cf5a8b53b43cd54ef0d0d09742351492" + integrity sha512-LrtUxbdvt1gOpo3gxG+VAJlJAEMhbWlM4YrFQgql98FwF7+K8K12LYO4hnDdUkNjeztYrOXEMqgTajSWgmtI/w== dependencies: "@colors/colors" "1.5.0" body-parser "^1.19.0" @@ -1769,10 +1637,17 @@ kind-of@^6.0.2: resolved "https://registry.yarnpkg.com/kind-of/-/kind-of-6.0.3.tgz#07c05034a6c349fa06e24fa35aa76db4580ce4dd" integrity sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw== -launch-editor@^2.6.1: - version "2.8.0" - resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.8.0.tgz#7255d90bdba414448e2138faa770a74f28451305" - integrity sha512-vJranOAJrI/llyWGRQqiDM+adrw+k83fvmmx3+nV47g3+36xM15jE+zyZ6Ffel02+xSvuM0b2GDRosXZkbb6wA== +kotlin-web-helpers@2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/kotlin-web-helpers/-/kotlin-web-helpers-2.0.0.tgz#b112096b273c1e733e0b86560998235c09a19286" + integrity sha512-xkVGl60Ygn/zuLkDPx+oHj7jeLR7hCvoNF99nhwXMn8a3ApB4lLiC9pk4ol4NHPjyoCbvQctBqvzUcp8pkqyWw== + dependencies: + format-util "^1.0.5" + +launch-editor@^2.6.0: + version "2.9.1" + resolved "https://registry.yarnpkg.com/launch-editor/-/launch-editor-2.9.1.tgz#253f173bd441e342d4344b4dae58291abb425047" + integrity sha512-Gcnl4Bd+hRO9P9icCP/RVVT2o8SFlPXofuCxvA2SaZuH45whSvf5p8x5oih5ftLiVhEI4sp5xDY+R+b3zJBh5w== dependencies: picocolors "^1.0.0" shell-quote "^1.8.1" @@ -1801,7 +1676,7 @@ lodash@^4.17.15, lodash@^4.17.21: resolved "https://registry.yarnpkg.com/lodash/-/lodash-4.17.21.tgz#679591c564c3bffaae8454cf0b3df370c3d6911c" integrity sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg== -log-symbols@4.1.0: +log-symbols@^4.1.0: version "4.1.0" resolved "https://registry.yarnpkg.com/log-symbols/-/log-symbols-4.1.0.tgz#3fbdbb95b4683ac9fc785111e792e558d4abd503" integrity sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg== @@ -1820,25 +1695,17 @@ log4js@^6.4.1: rfdc "^1.3.0" streamroller "^3.1.5" -lru-cache@^10.2.0: - version "10.4.3" - resolved "https://registry.yarnpkg.com/lru-cache/-/lru-cache-10.4.3.tgz#410fc8a17b70e598013df257c2446b7f3383f119" - integrity sha512-JNAzZcXrCt42VGLuYz0zfAzDfAvJWW6AfYlDBQyDV5DClI2m5sAmK+OIO7s59XfsRsWHp02jAJrRadPRGTt6SQ== - media-typer@0.3.0: version "0.3.0" resolved "https://registry.yarnpkg.com/media-typer/-/media-typer-0.3.0.tgz#8710d7af0aa626f8fffa1ce00168545263255748" integrity sha1-hxDXrwqmJvj/+hzgAWhUUmMlV0g= -memfs@^4.6.0: - version "4.9.3" - resolved "https://registry.yarnpkg.com/memfs/-/memfs-4.9.3.tgz#41a3218065fe3911d9eba836250c8f4e43f816bc" - integrity sha512-bsYSSnirtYTWi1+OPMFb0M048evMKyUYe0EbtuGQgq6BVQM1g1W8/KIUJCCvjgI/El0j6Q4WsmMiBwLUBSw8LA== +memfs@^3.4.3: + version "3.6.0" + resolved "https://registry.yarnpkg.com/memfs/-/memfs-3.6.0.tgz#d7a2110f86f79dd950a8b6df6d57bc984aa185f6" + integrity sha512-EGowvkkgbMcIChjMTMkESFDbZeSh8xZ7kNSF0hAiAN4Jh6jgHCRS0Ga/+C8y6Au+oqpezRHCfPsmJ2+DwAgiwQ== dependencies: - "@jsonjoy.com/json-pack" "^1.0.3" - "@jsonjoy.com/util" "^1.1.2" - tree-dump "^1.0.1" - tslib "^2.0.0" + fs-monkey "^1.0.4" merge-descriptors@1.0.1: version "1.0.1" @@ -1895,13 +1762,6 @@ minimalistic-assert@^1.0.0: resolved "https://registry.yarnpkg.com/minimalistic-assert/-/minimalistic-assert-1.0.1.tgz#2e194de044626d4a10e7f7fbc00ce73e83e4d5c7" integrity sha512-UtJcAD4yEaGtjPezWuO9wC4nwUnVH/8/Im3yEHQP4b67cXlD/Qr9hdITCU1xDbSEXg2XKNaP8jsReV7vQd00/A== -minimatch@5.0.1: - version "5.0.1" - resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.0.1.tgz#fb9022f7528125187c92bd9e9b6366be1cf3415b" - integrity sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g== - dependencies: - brace-expansion "^2.0.1" - minimatch@^3.0.4: version "3.1.2" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-3.1.2.tgz#19cd194bfd3e428f049a70817c038d89ab4be35b" @@ -1909,14 +1769,14 @@ minimatch@^3.0.4: dependencies: brace-expansion "^1.1.7" -minimatch@^5.0.1: +minimatch@^5.0.1, minimatch@^5.1.6: version "5.1.6" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-5.1.6.tgz#1cfcb8cf5522ea69952cd2af95ae09477f122a96" integrity sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g== dependencies: brace-expansion "^2.0.1" -minimatch@^9.0.3, minimatch@^9.0.4: +minimatch@^9.0.3: version "9.0.5" resolved "https://registry.yarnpkg.com/minimatch/-/minimatch-9.0.5.tgz#d74f9dd6b57d83d8e98cfb82133b03978bc929e5" integrity sha512-G6T0ZX48xgozx7587koeX9Ys2NYy6Gmv//P89sEte9V9whIapMNF4idKxnW2QtCcLiTWlb/wfCabAtAFWhhBow== @@ -1933,11 +1793,6 @@ minimist@^1.2.6: resolved "https://registry.yarnpkg.com/minimist/-/minimist-1.2.8.tgz#c1a464e7693302e082a075cee0c057741ac4772c" integrity sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA== -"minipass@^5.0.0 || ^6.0.2 || ^7.0.0", minipass@^7.1.2: - version "7.1.2" - resolved "https://registry.yarnpkg.com/minipass/-/minipass-7.1.2.tgz#93a9626ce5e5e66bd4db86849e7515e92340a707" - integrity sha512-qOOzS1cBTWYF4BH8fVePDBOO9iptMnGUEZwNc/cMWnTV2nVLZ7VoNWEPHkYczZA0pdoA7dl6e7FL659nX9S2aw== - mkdirp@^0.5.5: version "0.5.6" resolved "https://registry.yarnpkg.com/mkdirp/-/mkdirp-0.5.6.tgz#7def03d2432dcae4ba1d611445c48396062255f6" @@ -1945,31 +1800,31 @@ mkdirp@^0.5.5: dependencies: minimist "^1.2.6" -mocha@10.3.0: - version "10.3.0" - resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.3.0.tgz#0e185c49e6dccf582035c05fa91084a4ff6e3fe9" - integrity sha512-uF2XJs+7xSLsrmIvn37i/wnc91nw7XjOQB8ccyx5aEgdnohr7n+rEiZP23WkCYHjilR6+EboEnbq/ZQDz4LSbg== - dependencies: - ansi-colors "4.1.1" - browser-stdout "1.3.1" - chokidar "3.5.3" - debug "4.3.4" - diff "5.0.0" - escape-string-regexp "4.0.0" - find-up "5.0.0" - glob "8.1.0" - he "1.2.0" - js-yaml "4.1.0" - log-symbols "4.1.0" - minimatch "5.0.1" - ms "2.1.3" - serialize-javascript "6.0.0" - strip-json-comments "3.1.1" - supports-color "8.1.1" - workerpool "6.2.1" - yargs "16.2.0" - yargs-parser "20.2.4" - yargs-unparser "2.0.0" +mocha@10.7.3: + version "10.7.3" + resolved "https://registry.yarnpkg.com/mocha/-/mocha-10.7.3.tgz#ae32003cabbd52b59aece17846056a68eb4b0752" + integrity sha512-uQWxAu44wwiACGqjbPYmjo7Lg8sFrS3dQe7PP2FQI+woptP4vZXSMcfMyFL/e1yFEeEpV4RtyTpZROOKmxis+A== + dependencies: + ansi-colors "^4.1.3" + browser-stdout "^1.3.1" + chokidar "^3.5.3" + debug "^4.3.5" + diff "^5.2.0" + escape-string-regexp "^4.0.0" + find-up "^5.0.0" + glob "^8.1.0" + he "^1.2.0" + js-yaml "^4.1.0" + log-symbols "^4.1.0" + minimatch "^5.1.6" + ms "^2.1.3" + serialize-javascript "^6.0.2" + strip-json-comments "^3.1.1" + supports-color "^8.1.1" + workerpool "^6.5.1" + yargs "^16.2.0" + yargs-parser "^20.2.9" + yargs-unparser "^2.0.0" ms@2.0.0: version "2.0.0" @@ -1981,7 +1836,7 @@ ms@2.1.2: resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.2.tgz#d09d1f357b443f493382a8eb3ccd183872ae6009" integrity sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w== -ms@2.1.3: +ms@2.1.3, ms@^2.1.3: version "2.1.3" resolved "https://registry.yarnpkg.com/ms/-/ms-2.1.3.tgz#574c8138ce1d2b5861f0b44579dbadd60c6615b2" integrity sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA== @@ -2041,7 +1896,7 @@ obuf@^1.0.0, obuf@^1.1.2: resolved "https://registry.yarnpkg.com/obuf/-/obuf-1.1.2.tgz#09bea3343d41859ebd446292d11c9d4db619084e" integrity sha512-PX1wu0AmAdPqOL1mWhqmlOd8kOIZQwGZw6rh7uby9fTc5lhaOWFLX3I6R1hrF9k3zUY40e6igsLGkDXK92LJNg== -on-finished@2.4.1, on-finished@^2.4.1: +on-finished@2.4.1: version "2.4.1" resolved "https://registry.yarnpkg.com/on-finished/-/on-finished-2.4.1.tgz#58c8c44116e54845ad57f14ab10b03533184ac3f" integrity sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg== @@ -2074,15 +1929,14 @@ onetime@^5.1.2: dependencies: mimic-fn "^2.1.0" -open@^10.0.3: - version "10.1.0" - resolved "https://registry.yarnpkg.com/open/-/open-10.1.0.tgz#a7795e6e5d519abe4286d9937bb24b51122598e1" - integrity sha512-mnkeQ1qP5Ue2wd+aivTD3NHd/lZ96Lu0jgf0pwktLPtx6cTZiH7tyeGRRHs0zX0rbrahXPnXlUnbeXyaBBuIaw== +open@^8.0.9: + version "8.4.2" + resolved "https://registry.yarnpkg.com/open/-/open-8.4.2.tgz#5b5ffe2a8f793dcd2aad73e550cb87b59cb084f9" + integrity sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ== dependencies: - default-browser "^5.2.1" - define-lazy-prop "^3.0.0" - is-inside-container "^1.0.0" - is-wsl "^3.1.0" + define-lazy-prop "^2.0.0" + is-docker "^2.1.1" + is-wsl "^2.2.0" p-limit@^2.2.0: version "2.3.0" @@ -2112,13 +1966,12 @@ p-locate@^5.0.0: dependencies: p-limit "^3.0.2" -p-retry@^6.2.0: - version "6.2.0" - resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-6.2.0.tgz#8d6df01af298750009691ce2f9b3ad2d5968f3bd" - integrity sha512-JA6nkq6hKyWLLasXQXUrO4z8BUZGUt/LjlJxx8Gb2+2ntodU/SS63YZ8b0LUTbQ8ZB9iwOfhEPhg4ykKnn2KsA== +p-retry@^4.5.0: + version "4.6.2" + resolved "https://registry.yarnpkg.com/p-retry/-/p-retry-4.6.2.tgz#9baae7184057edd4e17231cee04264106e092a16" + integrity sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ== dependencies: - "@types/retry" "0.12.2" - is-network-error "^1.0.0" + "@types/retry" "0.12.0" retry "^0.13.1" p-try@^2.0.0: @@ -2126,11 +1979,6 @@ p-try@^2.0.0: resolved "https://registry.yarnpkg.com/p-try/-/p-try-2.2.0.tgz#cb2868540e313d61de58fafbe35ce9004d5540e6" integrity sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ== -package-json-from-dist@^1.0.0: - version "1.0.0" - resolved "https://registry.yarnpkg.com/package-json-from-dist/-/package-json-from-dist-1.0.0.tgz#e501cd3094b278495eb4258d4c9f6d5ac3019f00" - integrity sha512-dATvCeZN/8wQsGywez1mzHtTlP22H8OEfPrVMLNr4/eGa+ijtLn/6M5f0dY8UKNrC2O9UCU6SSoG3qRKnt7STw== - parseurl@~1.3.2, parseurl@~1.3.3: version "1.3.3" resolved "https://registry.yarnpkg.com/parseurl/-/parseurl-1.3.3.tgz#9da19e7bee8d12dff0513ed5b76957793bc2e8d4" @@ -2156,14 +2004,6 @@ path-parse@^1.0.7: resolved "https://registry.yarnpkg.com/path-parse/-/path-parse-1.0.7.tgz#fbc114b60ca42b30d9daf5858e4bd68bbedb6735" integrity sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw== -path-scurry@^1.11.1: - version "1.11.1" - resolved "https://registry.yarnpkg.com/path-scurry/-/path-scurry-1.11.1.tgz#7960a668888594a0720b12a911d1a742ab9f11d2" - integrity sha512-Xa4Nw17FS9ApQFJ9umLiJS4orGjm7ZzwUrwamcGQuHSzDyth9boKDaycYdDcZDuqYATXw4HFXgaqWTctW/v1HA== - dependencies: - lru-cache "^10.2.0" - minipass "^5.0.0 || ^6.0.2 || ^7.0.0" - path-to-regexp@0.1.7: version "0.1.7" resolved "https://registry.yarnpkg.com/path-to-regexp/-/path-to-regexp-0.1.7.tgz#df604178005f522f15eb4490e7247a1bfaa67f8c" @@ -2349,18 +2189,6 @@ rimraf@^3.0.0, rimraf@^3.0.2: dependencies: glob "^7.1.3" -rimraf@^5.0.5: - version "5.0.9" - resolved "https://registry.yarnpkg.com/rimraf/-/rimraf-5.0.9.tgz#c3baa1b886eadc2ec7981a06a593c3d01134ffe9" - integrity sha512-3i7b8OcswU6CpU8Ej89quJD4O98id7TtVM5U4Mybh84zQXdrFmDLouWBEEaD/QfO3gDDfH+AGFCGsR7kngzQnA== - dependencies: - glob "^10.3.7" - -run-applescript@^7.0.0: - version "7.0.0" - resolved "https://registry.yarnpkg.com/run-applescript/-/run-applescript-7.0.0.tgz#e5a553c2bffd620e169d276c1cd8f1b64778fbeb" - integrity sha512-9by4Ij99JUr/MCFBUkDKLWK3G9HVXmabKz9U5MlIAIuvuzkiOicRYs8XJLxX+xahD+mLiiCYDqF9dKAgtzKP1A== - safe-buffer@5.1.2, safe-buffer@~5.1.0, safe-buffer@~5.1.1: version "5.1.2" resolved "https://registry.yarnpkg.com/safe-buffer/-/safe-buffer-5.1.2.tgz#991ec69d296e0313747d59bdfd2b745c35f8828d" @@ -2394,7 +2222,7 @@ schema-utils@^3.2.0: ajv "^6.12.5" ajv-keywords "^3.5.2" -schema-utils@^4.0.0, schema-utils@^4.2.0: +schema-utils@^4.0.0: version "4.2.0" resolved "https://registry.yarnpkg.com/schema-utils/-/schema-utils-4.2.0.tgz#70d7c93e153a273a805801882ebd3bff20d89c8b" integrity sha512-L0jRsrPpjdckP3oPug3/VxNKt2trR8TcabrM6FOAAlvC/9Phcmm+cuAgTlxBqdBR1WJx7Naj9WHw+aOmheSVbw== @@ -2409,7 +2237,7 @@ select-hose@^2.0.0: resolved "https://registry.yarnpkg.com/select-hose/-/select-hose-2.0.0.tgz#625d8658f865af43ec962bfc376a37359a4994ca" integrity sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg== -selfsigned@^2.4.1: +selfsigned@^2.1.1: version "2.4.1" resolved "https://registry.yarnpkg.com/selfsigned/-/selfsigned-2.4.1.tgz#560d90565442a3ed35b674034cec4e95dceb4ae0" integrity sha512-th5B4L2U+eGLq1TVh7zNRGBapioSORUeymIydxgFpwww9d2qyKvtuPU2jJuHvYAwwqi2Y596QBL3eEqcPEYL8Q== @@ -2436,14 +2264,7 @@ send@0.18.0: range-parser "~1.2.1" statuses "2.0.1" -serialize-javascript@6.0.0: - version "6.0.0" - resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.0.tgz#efae5d88f45d7924141da8b5c3a7a7e663fefeb8" - integrity sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag== - dependencies: - randombytes "^2.1.0" - -serialize-javascript@^6.0.1: +serialize-javascript@^6.0.1, serialize-javascript@^6.0.2: version "6.0.2" resolved "https://registry.yarnpkg.com/serialize-javascript/-/serialize-javascript-6.0.2.tgz#defa1e055c83bf6d59ea805d8da862254eb6a6c2" integrity sha512-Saa1xPByTTq2gdeFZYLLo+RFE35NHZkAbqZeWNd3BpzppeVisAqpDjcp8dyf6uIvEqJRd46jemmyA4iFIeVk8g== @@ -2521,11 +2342,6 @@ signal-exit@^3.0.3: resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-3.0.7.tgz#a9a1767f8af84155114eaabd73f99273c8f59ad9" integrity sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ== -signal-exit@^4.0.1: - version "4.1.0" - resolved "https://registry.yarnpkg.com/signal-exit/-/signal-exit-4.1.0.tgz#952188c1cbd546070e2dd20d0f41c0ae0530cb04" - integrity sha512-bzyZ1e88w9O1iNJbKnOlvYTrWPDl46O1bG0D3XInv+9tkPrxrN8jUUTiFlDkkmKWgn1M6CfIA13SuGqOa9Korw== - socket.io-adapter@~2.5.2: version "2.5.4" resolved "https://registry.yarnpkg.com/socket.io-adapter/-/socket.io-adapter-2.5.4.tgz#4fdb1358667f6d68f25343353bd99bd11ee41006" @@ -2632,7 +2448,7 @@ streamroller@^3.1.5: debug "^4.3.4" fs-extra "^8.1.0" -"string-width-cjs@npm:string-width@^4.2.0", string-width@^4.1.0, string-width@^4.2.0: +string-width@^4.1.0, string-width@^4.2.0: version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -2641,15 +2457,6 @@ streamroller@^3.1.5: is-fullwidth-code-point "^3.0.0" strip-ansi "^6.0.1" -string-width@^5.0.1, string-width@^5.1.2: - version "5.1.2" - resolved "https://registry.yarnpkg.com/string-width/-/string-width-5.1.2.tgz#14f8daec6d81e7221d2a357e668cab73bdbca794" - integrity sha512-HnLOCR3vjcY8beoNLtcjZ5/nxn2afmME6lhrDrebokqMap+XbeW8n9TXpPDOqdGK5qcI3oT0GKTW6wC7EMiVqA== - dependencies: - eastasianwidth "^0.2.0" - emoji-regex "^9.2.2" - strip-ansi "^7.0.1" - string_decoder@^1.1.1: version "1.3.0" resolved "https://registry.yarnpkg.com/string_decoder/-/string_decoder-1.3.0.tgz#42f114594a46cf1a8e30b0a84f56c78c3edac21e" @@ -2664,37 +2471,23 @@ string_decoder@~1.1.1: dependencies: safe-buffer "~5.1.0" -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +strip-ansi@^6.0.0, strip-ansi@^6.0.1: version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== dependencies: ansi-regex "^5.0.1" -strip-ansi@^7.0.1: - version "7.1.0" - resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" - integrity sha512-iq6eVVI64nQQTRYq2KtEg2d2uU7LElhTJwsH4YzIHZshxlgZms/wIc4VoDQTlG/IvVIrBKG06CrZnp0qv7hkcQ== - dependencies: - ansi-regex "^6.0.1" - strip-final-newline@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/strip-final-newline/-/strip-final-newline-2.0.0.tgz#89b852fb2fcbe936f6f4b3187afb0a12c1ab58ad" integrity sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA== -strip-json-comments@3.1.1: +strip-json-comments@^3.1.1: version "3.1.1" resolved "https://registry.yarnpkg.com/strip-json-comments/-/strip-json-comments-3.1.1.tgz#31f1281b3832630434831c310c01cccda8cbe006" integrity sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig== -supports-color@8.1.1, supports-color@^8.0.0: - version "8.1.1" - resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" - integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== - dependencies: - has-flag "^4.0.0" - supports-color@^7.1.0: version "7.2.0" resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-7.2.0.tgz#1b7dcdcb32b8138801b3e478ba6a51caa89648da" @@ -2702,6 +2495,13 @@ supports-color@^7.1.0: dependencies: has-flag "^4.0.0" +supports-color@^8.0.0, supports-color@^8.1.1: + version "8.1.1" + resolved "https://registry.yarnpkg.com/supports-color/-/supports-color-8.1.1.tgz#cd6fc17e28500cff56c1b86c0a7fd4a54a73005c" + integrity sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q== + dependencies: + has-flag "^4.0.0" + supports-preserve-symlinks-flag@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz#6eda4bd344a3c94aea376d4cc31bc77311039e09" @@ -2733,11 +2533,6 @@ terser@^5.26.0: commander "^2.20.0" source-map-support "~0.5.20" -thingies@^1.20.0: - version "1.21.0" - resolved "https://registry.yarnpkg.com/thingies/-/thingies-1.21.0.tgz#e80fbe58fd6fdaaab8fad9b67bd0a5c943c445c1" - integrity sha512-hsqsJsFMsV+aD4s3CWKk85ep/3I9XzYV/IXaSouJMYIoDlgyi11cBhsqYe9/geRfB0YIikBQg6raRaM+nIMP9g== - thunky@^1.0.2: version "1.1.0" resolved "https://registry.yarnpkg.com/thunky/-/thunky-1.1.0.tgz#5abaf714a9405db0504732bbccd2cedd9ef9537d" @@ -2762,16 +2557,6 @@ toidentifier@1.0.1: resolved "https://registry.yarnpkg.com/toidentifier/-/toidentifier-1.0.1.tgz#3be34321a88a820ed1bd80dfaa33e479fbb8dd35" integrity sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA== -tree-dump@^1.0.1: - version "1.0.2" - resolved "https://registry.yarnpkg.com/tree-dump/-/tree-dump-1.0.2.tgz#c460d5921caeb197bde71d0e9a7b479848c5b8ac" - integrity sha512-dpev9ABuLWdEubk+cIaI9cHwRNNDjkBBLXTwI4UCUFdQ5xXKqNXoK4FEciw/vxf+NQ7Cb7sGUyeUtORvHIdRXQ== - -tslib@^2.0.0: - version "2.6.3" - resolved "https://registry.yarnpkg.com/tslib/-/tslib-2.6.3.tgz#0438f810ad7a9edcde7a241c3d80db693c8cbfe0" - integrity sha512-xNvxJEOUiWPGhUuUdQgAJPKOOJfGnIyKySOc09XkKsgdUV/3E2zvwZYdejjmRgPCgcym1juLH3226yA7sEFJKQ== - type-is@~1.6.18: version "1.6.18" resolved "https://registry.yarnpkg.com/type-is/-/type-is-1.6.18.tgz#4e552cd05df09467dcbc4ef739de89f2cf37c131" @@ -2780,10 +2565,10 @@ type-is@~1.6.18: media-typer "0.3.0" mime-types "~2.1.24" -typescript@5.4.3: - version "5.4.3" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.4.3.tgz#5c6fedd4c87bee01cd7a528a30145521f8e0feff" - integrity sha512-KrPd3PKaCLr78MalgiwJnA25Nm8HAmdwN3mYUYZgG/wizIo9EainNVQI9/yDavtVFRN2h3k8uf3GLHuhDMgEHg== +typescript@5.5.4: + version "5.5.4" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-5.5.4.tgz#d9852d6c82bad2d2eda4fd74a5762a8f5909e9ba" + integrity sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q== ua-parser-js@^0.7.30: version "0.7.37" @@ -2874,53 +2659,52 @@ webpack-cli@5.1.4: rechoir "^0.8.0" webpack-merge "^5.7.3" -webpack-dev-middleware@^7.1.0: - version "7.3.0" - resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-7.3.0.tgz#5975ea41271083dc5678886b99d4c058382fb311" - integrity sha512-xD2qnNew+F6KwOGZR7kWdbIou/ud7cVqLEXeK1q0nHcNsX/u7ul/fSdlOTX4ntSL5FNFy7ZJJXbf0piF591JYw== +webpack-dev-middleware@^5.3.4: + version "5.3.4" + resolved "https://registry.yarnpkg.com/webpack-dev-middleware/-/webpack-dev-middleware-5.3.4.tgz#eb7b39281cbce10e104eb2b8bf2b63fce49a3517" + integrity sha512-BVdTqhhs+0IfoeAf7EoH5WE+exCmqGerHfDM0IL096Px60Tq2Mn9MAbnaGUe6HiMa41KMCYF19gyzZmBcq/o4Q== dependencies: colorette "^2.0.10" - memfs "^4.6.0" + memfs "^3.4.3" mime-types "^2.1.31" - on-finished "^2.4.1" range-parser "^1.2.1" schema-utils "^4.0.0" -webpack-dev-server@5.0.4: - version "5.0.4" - resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-5.0.4.tgz#cb6ea47ff796b9251ec49a94f24a425e12e3c9b8" - integrity sha512-dljXhUgx3HqKP2d8J/fUMvhxGhzjeNVarDLcbO/EWMSgRizDkxHQDZQaLFL5VJY9tRBj2Gz+rvCEYYvhbqPHNA== - dependencies: - "@types/bonjour" "^3.5.13" - "@types/connect-history-api-fallback" "^1.5.4" - "@types/express" "^4.17.21" - "@types/serve-index" "^1.9.4" - "@types/serve-static" "^1.15.5" - "@types/sockjs" "^0.3.36" - "@types/ws" "^8.5.10" +webpack-dev-server@4.15.2: + version "4.15.2" + resolved "https://registry.yarnpkg.com/webpack-dev-server/-/webpack-dev-server-4.15.2.tgz#9e0c70a42a012560860adb186986da1248333173" + integrity sha512-0XavAZbNJ5sDrCbkpWL8mia0o5WPOd2YGtxrEiZkBK9FjLppIUK2TgxK6qGD2P3hUXTJNNPVibrerKcx5WkR1g== + dependencies: + "@types/bonjour" "^3.5.9" + "@types/connect-history-api-fallback" "^1.3.5" + "@types/express" "^4.17.13" + "@types/serve-index" "^1.9.1" + "@types/serve-static" "^1.13.10" + "@types/sockjs" "^0.3.33" + "@types/ws" "^8.5.5" ansi-html-community "^0.0.8" - bonjour-service "^1.2.1" - chokidar "^3.6.0" + bonjour-service "^1.0.11" + chokidar "^3.5.3" colorette "^2.0.10" compression "^1.7.4" connect-history-api-fallback "^2.0.0" default-gateway "^6.0.3" express "^4.17.3" graceful-fs "^4.2.6" - html-entities "^2.4.0" + html-entities "^2.3.2" http-proxy-middleware "^2.0.3" - ipaddr.js "^2.1.0" - launch-editor "^2.6.1" - open "^10.0.3" - p-retry "^6.2.0" - rimraf "^5.0.5" - schema-utils "^4.2.0" - selfsigned "^2.4.1" + ipaddr.js "^2.0.1" + launch-editor "^2.6.0" + open "^8.0.9" + p-retry "^4.5.0" + rimraf "^3.0.2" + schema-utils "^4.0.0" + selfsigned "^2.1.1" serve-index "^1.9.1" sockjs "^0.3.24" spdy "^4.0.2" - webpack-dev-middleware "^7.1.0" - ws "^8.16.0" + webpack-dev-middleware "^5.3.4" + ws "^8.13.0" webpack-merge@^4.1.5: version "4.2.2" @@ -2942,21 +2726,20 @@ webpack-sources@^3.2.3: resolved "https://registry.yarnpkg.com/webpack-sources/-/webpack-sources-3.2.3.tgz#2d4daab8451fd4b240cc27055ff6a0c2ccea0cde" integrity sha512-/DyMEOrDgLKKIG0fmvtz+4dUX/3Ghozwgm6iPp8KRhvn+eQf9+Q7GWxVNMk3+uCPWfdXYC4ExGBckIXdFEfH1w== -webpack@5.91.0: - version "5.91.0" - resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.91.0.tgz#ffa92c1c618d18c878f06892bbdc3373c71a01d9" - integrity sha512-rzVwlLeBWHJbmgTC/8TvAcu5vpJNII+MelQpylD4jNERPwpBJOE2lEcko1zJX3QJeLjTTAnQxn/OJ8bjDzVQaw== +webpack@5.94.0: + version "5.94.0" + resolved "https://registry.yarnpkg.com/webpack/-/webpack-5.94.0.tgz#77a6089c716e7ab90c1c67574a28da518a20970f" + integrity sha512-KcsGn50VT+06JH/iunZJedYGUJS5FGjow8wb9c0v5n1Om8O1g4L6LjtfxwlXIATopoQu+vOXXa7gYisWxCoPyg== dependencies: - "@types/eslint-scope" "^3.7.3" "@types/estree" "^1.0.5" "@webassemblyjs/ast" "^1.12.1" "@webassemblyjs/wasm-edit" "^1.12.1" "@webassemblyjs/wasm-parser" "^1.12.1" acorn "^8.7.1" - acorn-import-assertions "^1.9.0" + acorn-import-attributes "^1.9.5" browserslist "^4.21.10" chrome-trace-event "^1.0.2" - enhanced-resolve "^5.16.0" + enhanced-resolve "^5.17.1" es-module-lexer "^1.2.1" eslint-scope "5.1.1" events "^3.2.0" @@ -3005,12 +2788,12 @@ wildcard@^2.0.0: resolved "https://registry.yarnpkg.com/wildcard/-/wildcard-2.0.0.tgz#a77d20e5200c6faaac979e4b3aadc7b3dd7f8fec" integrity sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw== -workerpool@6.2.1: - version "6.2.1" - resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" - integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== +workerpool@^6.5.1: + version "6.5.1" + resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.5.1.tgz#060f73b39d0caf97c6db64da004cd01b4c099544" + integrity sha512-Fs4dNYcsdpYSAfVxhnl1L5zTksjvOJxtC5hzMNl+1t9B8hTJTdKDyZ5ju7ztgPy+ft9tBFXoOlDNiOT9WUXZlA== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +wrap-ansi@^7.0.0: version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -3019,21 +2802,12 @@ workerpool@6.2.1: string-width "^4.1.0" strip-ansi "^6.0.0" -wrap-ansi@^8.1.0: - version "8.1.0" - resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" - integrity sha512-si7QWI6zUMq56bESFvagtmzMdGOtoxfR+Sez11Mobfc7tm+VkUckk9bW2UeffTGVUbOksxmSw0AA2gs8g71NCQ== - dependencies: - ansi-styles "^6.1.0" - string-width "^5.0.1" - strip-ansi "^7.0.1" - wrappy@1: version "1.0.2" resolved "https://registry.yarnpkg.com/wrappy/-/wrappy-1.0.2.tgz#b5243d8f3ec1aa35f1364605bc0d1036e30ab69f" integrity sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8= -ws@^8.16.0: +ws@^8.13.0: version "8.18.0" resolved "https://registry.yarnpkg.com/ws/-/ws-8.18.0.tgz#0d7505a6eafe2b0e712d232b42279f53bc289bbc" integrity sha512-8VbfWfHLbbwu3+N6OKsOMpBdT4kXPDDB9cJk2bJ6mh9ucxdlnNvH1e+roYkKmN9Nxw2yjz7VzeO9oOz2zJ04Pw== @@ -3048,17 +2822,12 @@ y18n@^5.0.5: resolved "https://registry.yarnpkg.com/y18n/-/y18n-5.0.8.tgz#7f4934d0f7ca8c56f95314939ddcd2dd91ce1d55" integrity sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA== -yargs-parser@20.2.4: - version "20.2.4" - resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.4.tgz#b42890f14566796f85ae8e3a25290d205f154a54" - integrity sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA== - -yargs-parser@^20.2.2: +yargs-parser@^20.2.2, yargs-parser@^20.2.9: version "20.2.9" resolved "https://registry.yarnpkg.com/yargs-parser/-/yargs-parser-20.2.9.tgz#2eb7dc3b0289718fc295f362753845c41a0c94ee" integrity sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w== -yargs-unparser@2.0.0: +yargs-unparser@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/yargs-unparser/-/yargs-unparser-2.0.0.tgz#f131f9226911ae5d9ad38c432fe809366c2325eb" integrity sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA== @@ -3068,7 +2837,7 @@ yargs-unparser@2.0.0: flat "^5.0.2" is-plain-obj "^2.1.0" -yargs@16.2.0, yargs@^16.1.1: +yargs@^16.1.1, yargs@^16.2.0: version "16.2.0" resolved "https://registry.yarnpkg.com/yargs/-/yargs-16.2.0.tgz#1c82bf0f6b6a66eafce7ef30e376f49a12477f66" integrity sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw== diff --git a/kotlinx.interval.datetime/build.gradle.kts b/kotlinx.interval.datetime/build.gradle.kts index 6d1b71a..06983e0 100644 --- a/kotlinx.interval.datetime/build.gradle.kts +++ b/kotlinx.interval.datetime/build.gradle.kts @@ -19,7 +19,7 @@ kotlin { commonMain { dependencies { api(project(":kotlinx-interval")) - implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.0") + implementation("org.jetbrains.kotlinx:kotlinx-datetime:0.6.1") } } commonTest { diff --git a/kotlinx.interval.datetime/src/commonMain/kotlin/DateTimeTypeOperations.kt b/kotlinx.interval.datetime/src/commonMain/kotlin/DateTimeTypeOperations.kt index 6790d87..5583128 100644 --- a/kotlinx.interval.datetime/src/commonMain/kotlin/DateTimeTypeOperations.kt +++ b/kotlinx.interval.datetime/src/commonMain/kotlin/DateTimeTypeOperations.kt @@ -4,6 +4,7 @@ import io.github.whathecode.kotlinx.interval.TypeOperations import kotlinx.datetime.Instant import kotlin.time.Duration import kotlin.time.Duration.Companion.milliseconds +import kotlin.time.DurationUnit internal object InstantOperations : TypeOperations @@ -22,6 +23,23 @@ internal object InstantOperations : TypeOperations a.epochSeconds - b.epochSeconds, a.nanosecondsOfSecond - b.nanosecondsOfSecond ) + + private const val NANOS_IN_SECOND = 1_000_000_000 + + override fun fromDouble( double: Double ): Instant + { + val seconds = double.toLong() + val fraction = double - seconds + val nanoseconds = (NANOS_IN_SECOND * fraction).toLong() + return Instant.fromEpochSeconds( seconds, nanoseconds ) + } + + override fun toDouble( value: Instant ): Double + { + val seconds = value.epochSeconds + val fraction = value.nanosecondsOfSecond.toDouble() / NANOS_IN_SECOND + return seconds + fraction + } } @@ -29,11 +47,14 @@ internal object DurationOperations : TypeOperations { override val additiveIdentity: Duration = Duration.ZERO - private const val MAX_MILLIS = Long.MAX_VALUE / 2 + internal const val MAX_MILLIS = Long.MAX_VALUE / 2 override val minValue: Duration = -MAX_MILLIS.milliseconds override val maxValue: Duration = MAX_MILLIS.milliseconds override val spacing: Duration? = null override fun unsafeAdd( a: Duration, b: Duration ): Duration = a + b override fun unsafeSubtract( a: Duration, b: Duration ): Duration = a - b + + override fun fromDouble( double: Double ): Duration = double.milliseconds + override fun toDouble( value: Duration ): Double = value.toDouble( DurationUnit.MILLISECONDS ) } diff --git a/kotlinx.interval.datetime/src/commonMain/kotlin/InstantInterval.kt b/kotlinx.interval.datetime/src/commonMain/kotlin/InstantInterval.kt index 2d74655..973eab7 100644 --- a/kotlinx.interval.datetime/src/commonMain/kotlin/InstantInterval.kt +++ b/kotlinx.interval.datetime/src/commonMain/kotlin/InstantInterval.kt @@ -3,9 +3,8 @@ package io.github.whathecode.kotlinx.interval.datetime import io.github.whathecode.kotlinx.interval.Interval import io.github.whathecode.kotlinx.interval.IntervalTypeOperations import kotlinx.datetime.Instant +import kotlin.math.absoluteValue import kotlin.time.Duration -import kotlin.time.Duration.Companion.nanoseconds -import kotlin.time.Duration.Companion.seconds /** @@ -17,8 +16,26 @@ class InstantInterval( start: Instant, isStartIncluded: Boolean, end: Instant, i { companion object { - internal val Operations = IntervalTypeOperations( InstantOperations, DurationOperations ) - { time: Instant -> (time.epochSeconds.seconds + time.nanosecondsOfSecond.nanoseconds).absoluteValue } + internal val Operations = object : IntervalTypeOperations( + InstantOperations, + DurationOperations, + getDistance = { a, b -> (b - a).absoluteValue }, + unsafeValueAt = { InstantOperations.additiveIdentity + it.absoluteValue } + ) + { + // Maximum positive/negative value to ensure the interval size can be represented by Duration. + // One is subtracted to exclude `Duration.Infinity`. + private val MAX = (DurationOperations.MAX_MILLIS - 1) / 2 + + // Some platforms have a smaller range than `MAX` duration, and values are clamped on initialization. + private val COERCED_MAX_SECONDS = minOf( + Instant.fromEpochMilliseconds( -MAX ).epochSeconds.absoluteValue, + Instant.fromEpochMilliseconds( MAX ).epochSeconds.absoluteValue + ) + + override val minValue: Instant = Instant.fromEpochSeconds( -COERCED_MAX_SECONDS, 0 ) + override val maxValue: Instant = Instant.fromEpochSeconds( COERCED_MAX_SECONDS, 0 ) + } } } diff --git a/kotlinx.interval.datetime/src/commonTest/kotlin/InstantIntervalTest.kt b/kotlinx.interval.datetime/src/commonTest/kotlin/InstantIntervalTest.kt index 9dbde3c..254d047 100644 --- a/kotlinx.interval.datetime/src/commonTest/kotlin/InstantIntervalTest.kt +++ b/kotlinx.interval.datetime/src/commonTest/kotlin/InstantIntervalTest.kt @@ -7,6 +7,6 @@ import kotlin.time.Duration private val a = Instant.fromEpochSeconds( 0, 50 ) private val b = Instant.fromEpochSeconds( 0, 100 ) -private val c = Instant.fromEpochSeconds( 100, 50 ) +private val c = Instant.fromEpochSeconds( 0, 150 ) object InstantIntervalTest : IntervalTest( a, b, c, b - a, InstantInterval.Operations ) diff --git a/kotlinx.interval.datetime/src/commonTest/kotlin/Readme.kt b/kotlinx.interval.datetime/src/commonTest/kotlin/Readme.kt index 89c67ec..69d8c83 100644 --- a/kotlinx.interval.datetime/src/commonTest/kotlin/Readme.kt +++ b/kotlinx.interval.datetime/src/commonTest/kotlin/Readme.kt @@ -5,6 +5,7 @@ package io.github.whathecode.kotlinx.interval.datetime import kotlinx.datetime.Clock import kotlin.test.* import kotlin.time.Duration +import kotlin.time.Duration.Companion.hours import kotlin.time.Duration.Companion.seconds @@ -17,5 +18,6 @@ class Readme val interval: InstantInterval = interval( now, now + 100.seconds ) val areIncluded = now + 50.seconds in interval // true val size: Duration = interval.size // 100 seconds + val shifted = interval shr 24.hours // 100 seconds 24 hours from now } } diff --git a/kotlinx.interval.testcases/src/commonMain/kotlin/IntervalTest.kt b/kotlinx.interval.testcases/src/commonMain/kotlin/IntervalTest.kt index 82b4f64..48953e2 100644 --- a/kotlinx.interval.testcases/src/commonMain/kotlin/IntervalTest.kt +++ b/kotlinx.interval.testcases/src/commonMain/kotlin/IntervalTest.kt @@ -8,23 +8,26 @@ import kotlin.test.* /** * Tests for [Interval] which creates intervals for testing using [a], which should be smaller than [b], - * which should be smaller than [c]. - * For evenly-spaced types of [T], the distance between [a] and [b], and [b] and [c], should be greater than the spacing - * between subsequent values in the set. + * which should be smaller than [c]. The distance between [a] and [b], and [b] and [c], should be equal. + * In addition, for evenly-spaced types of [T], this distance should be greater than the spacing between any two + * subsequent values in the set. + * And, for non-evenly-spaced types, this distance should be small enough so that there isn't sufficient precision to + * represent them as individual values when shifted close to the max possible values represented by [T]. */ +@Suppress( "FunctionName" ) abstract class IntervalTest, TSize : Comparable>( private val a: T, private val b: T, private val c: T, /** - * Expected size of the interval between [a] and [b]. + * Expected size of the interval between [a] and [b]. Should be positive. */ private val abSize: TSize, /** * Provide access to the predefined set of operators of [T] and [TSize] and conversions between them. */ private val operations: IntervalTypeOperations -) +) : IntervalTypeOperationsTest( operations, abSize ) { private val valueOperations = operations.valueOperations private val sizeOperations = operations.sizeOperations @@ -54,15 +57,16 @@ abstract class IntervalTest, TSize : Comparable>( fun is_correct_test_configuration() { assertTrue( a < b && b < c ) + assertTrue( abSize > sizeOperations.additiveIdentity ) - // For evenly-spaced types, the distance between a-b, and b-c, should be greater than the spacing. + // The distance between a-b and b-c should be equal. + val abSize = valueOperations.unsafeSubtract( b, a ) + val bcSize = valueOperations.unsafeSubtract( c, b ) + assertTrue( abSize == bcSize ) + + // For evenly-spaced types, the distance should be e greater than the spacing. val spacing = valueOperations.spacing - if ( spacing != null ) - { - val abSize = valueOperations.unsafeSubtract( b, a ) - val bcSize = valueOperations.unsafeSubtract( c, b ) - assertTrue( abSize > spacing && bcSize > spacing ) - } + if ( spacing != null ) assertTrue( abSize > spacing ) } @Test @@ -137,25 +141,11 @@ abstract class IntervalTest, TSize : Comparable>( } @Test - fun size_for_empty_interval_is_zero() + fun size_for_interval_with_one_value_is_zero() { val zero = sizeOperations.additiveIdentity - val emptyInterval = createClosedInterval( a, a ) - assertEquals( zero, emptyInterval.size ) - } - - @Test - fun size_can_be_greater_than_max_value() - { - val fullRange = createClosedInterval( valueOperations.minValue, valueOperations.maxValue ).size - val identity = valueOperations.additiveIdentity - val rangeBelowIdentity = createClosedInterval( valueOperations.minValue, identity ).size - val rangeAboveIdentity = createClosedInterval( identity, valueOperations.maxValue ).size - - assertEquals( - fullRange, - sizeOperations.unsafeAdd( rangeBelowIdentity, rangeAboveIdentity ) - ) + val oneValue = createClosedInterval( a, a ) + assertEquals( zero, oneValue.size ) } @Test @@ -189,6 +179,82 @@ abstract class IntervalTest, TSize : Comparable>( openIntervals.forEach { assertTrue( a !in it && b !in it ) } } + @Test + fun getValueAt_inside_interval() + { + val acIntervals = createAllInclusionTypeIntervals( a, c ) + + for ( ac in acIntervals ) + { + assertEquals( a,ac.getValueAt( 0.0 ) ) + assertEquals( b, ac.getValueAt( 0.5 ) ) + assertEquals( c, ac.getValueAt( 1.0 ) ) + } + } + + @Test + fun getValueAt_outside_interval() + { + val abIntervals = createAllInclusionTypeIntervals( a, b ) + for ( ab in abIntervals ) + { + assertEquals( c, ab.getValueAt( 2.0 ) ) + } + + val bcIntervals = createAllInclusionTypeIntervals( b, c ) + for ( bc in bcIntervals ) + { + assertEquals( a, bc.getValueAt( -1.0 ) ) + } + } + + @Test + fun getValueAt_reverse_intervals() + { + val adIntervals = createAllInclusionTypeIntervals( a, d ) + + for ( ad in adIntervals ) + { + val nonReversed = ad.getValueAt( 0.2 ) + val reversed = ad.reverse().getValueAt( 0.8 ) + assertEquals( + valueOperations.toDouble( nonReversed ), + valueOperations.toDouble( reversed ), + absoluteTolerance = 0.000000001 + ) + } + } + + @Test + fun getValueAt_returned_value_overflows() + { + val maxIntervals = createAllInclusionTypeIntervals( operations.minValue, operations.maxValue ) + + for ( max in maxIntervals ) + { + assertEquals( max.start, max.getValueAt( 0.0 ) ) + // Loss of precision for large values as part of double conversion is expected. + // Therefore, compare double-converted values which have the same loss of precision. + assertEquals( + valueOperations.toDouble(max.end ), + valueOperations.toDouble( max.getValueAt( 1.0 ) ) + ) + assertFailsWith { max.getValueAt( -0.1 ) } + assertFailsWith { max.getValueAt( 1.1 ) } + } + } + + @Test + fun getValueAt_percentage_overflows() + { + val ab = createClosedInterval( a, b ) + + val maxPercentage = sizeOperations.toDouble( sizeOperations.maxValue ) / sizeOperations.toDouble( ab.size ) + val tooBigPercentage = maxPercentage * 2 + + assertFailsWith { ab.getValueAt( tooBigPercentage ) } + } + @Test fun minus_for_interval_lying_within() { @@ -408,6 +474,122 @@ abstract class IntervalTest, TSize : Comparable>( assertEquals( expected, bNextC + ab ) } + @Test + fun shift_succeeds() + { + val bcIntervals = createAllInclusionTypeIntervals( b, c ) + val shiftSize = abSize + val expectedShiftSize: T = valueOperations.unsafeSubtract( b, a ) + + for ( bc in bcIntervals ) + { + val shifted = bc.shift( shiftSize ) + val expectedInterval = createInterval( + valueOperations.unsafeAdd( b, expectedShiftSize ), + bc.isStartIncluded, + valueOperations.unsafeAdd( c, expectedShiftSize ), + bc.isEndIncluded + ) + assertEquals( expectedInterval, shifted.shiftedInterval ) + assertEquals( shiftSize, shifted.offsetAmount ) + assertEquals( bc.size, shifted.shiftedInterval.size ) + } + } + + @Test + fun shift_using_invertedDirection_succeeds() + { + val bcIntervals = createAllInclusionTypeIntervals( b, c ) + val shiftSize = abSize + val expectedShiftSize: T = valueOperations.unsafeSubtract( b, a ) + + for ( bc in bcIntervals ) + { + val shifted = bc.shift( shiftSize, invertDirection = true ) + val expectedInterval = createInterval( + a, + bc.isStartIncluded, + valueOperations.unsafeSubtract( c, expectedShiftSize ), + bc.isEndIncluded + ) + assertEquals( expectedInterval, shifted.shiftedInterval ) + assertEquals( shiftSize, shifted.offsetAmount ) + assertEquals( bc.size, shifted.shiftedInterval.size ) + } + } + + @Test + fun shift_negative_amount_equals_shift_positive_amount_using_invertedDirection() + { + if ( !sizeOperations.isSignedType ) return + + val bc = createClosedInterval( b, c ) + val abSizeNegative = sizeOperations.unsafeSubtract( sizeOperations.additiveIdentity, abSize ) + val shiftNegative = bc.shift( abSizeNegative ) + + val shiftPositive = bc.shift( abSize, invertDirection = true ) + assertEquals( shiftPositive.shiftedInterval, shiftNegative.shiftedInterval ) + assertEquals( abSizeNegative, shiftNegative.offsetAmount ) + } + + @Test + fun shift_by_zero_returns_same_interval() + { + val toShift = createAllInclusionTypeIntervals( a, b ) + for ( original in toShift ) + { + val zero = sizeOperations.additiveIdentity + + val shiftResult = original.shift( zero ) + assertSame( original, shiftResult.shiftedInterval ) + + val shiftResultInverted = original.shift( zero, invertDirection = true ) + assertSame( original, shiftResultInverted.shiftedInterval ) + } + } + + @Test + fun shift_with_overflow_shifts_up_to_maximum_value() + { + val bottomHalf = createClosedInterval( operations.minValue, valueOperations.additiveIdentity ) + val maxRange = operations.getDistance( operations.minValue, operations.maxValue ) + + val shifted = bottomHalf.shift( maxRange ) + + assertEquals( operations.maxValue, shifted.shiftedInterval.upperBound ) + assertEquals( bottomHalf.size, shifted.shiftedInterval.size ) + val expectedShift = sizeOperations.unsafeSubtract( maxRange, bottomHalf.size ) + assertEquals( expectedShift, shifted.offsetAmount ) + } + + @Test + fun shift_using_invertedDirection_with_overflow_shifts_down_to_minimum_value() + { + val upperHalf = createClosedInterval( valueOperations.additiveIdentity, operations.maxValue ) + val maxRange = operations.getDistance( operations.minValue, operations.maxValue ) + + val shifted = upperHalf.shift( maxRange, invertDirection = true ) + + assertEquals( operations.minValue, shifted.shiftedInterval.lowerBound ) + assertEquals( upperHalf.size, shifted.shiftedInterval.size ) + val expectedShift = sizeOperations.unsafeSubtract( maxRange, upperHalf.size ) + assertEquals( expectedShift, shifted.offsetAmount ) + } + + @Test + fun shift_tiny_intervals_long_distances_for_value_types_with_lossy_precision() + { + if ( valueOperations.spacing != null ) return // Evenly-spaced types don't lose precision. + + val tinyInterval = createOpenInterval( a, b ) + val shifted = tinyInterval.shift( sizeOperations.maxValue ) + + // Due to loss of precision, there is no more distinction between `a` and `b` after shifting, resulting in an + // interval with `size` 0. This causes the interval to "collapse" into a single value, represented as a closed + // interval. + assertTrue( shifted.shiftedInterval.isClosedInterval ) + } + @Test fun intersects_for_fully_contained_intervals() { diff --git a/kotlinx.interval.testcases/src/commonMain/kotlin/IntervalTypeOperationsTest.kt b/kotlinx.interval.testcases/src/commonMain/kotlin/IntervalTypeOperationsTest.kt new file mode 100644 index 0000000..2618a6c --- /dev/null +++ b/kotlinx.interval.testcases/src/commonMain/kotlin/IntervalTypeOperationsTest.kt @@ -0,0 +1,181 @@ +package io.github.whathecode.kotlinx.interval.test + +import io.github.whathecode.kotlinx.interval.IntervalTypeOperations +import kotlin.test.* + + +/** + * Test for [IntervalTypeOperations]. + */ +@Suppress( "FunctionName" ) +open class IntervalTypeOperationsTest, TSize : Comparable>( + private val operations: IntervalTypeOperations, + private val positiveSize: TSize +) +{ + private val valueOperations = operations.valueOperations + private val valueZero = valueOperations.additiveIdentity + + private val sizeOperations = operations.sizeOperations + private val sizeZero = sizeOperations.additiveIdentity + + + @Test + fun minValue_can_be_converted_back_and_forth_using_TSize() + { + val min: T = operations.minValue + val minSize: TSize = operations.getDistance( valueZero, min ) + val minSizeValue: T = operations.unsafeValueAt( minSize ) + val subtractedMinSize: T = valueOperations.unsafeSubtract( valueZero, minSizeValue ) + assertEquals( min, subtractedMinSize ) + } + + @Test + fun maxValue_can_be_converted_back_and_forth_using_TSize() + { + val max: T = operations.maxValue + val maxSize: TSize = operations.getDistance( valueZero, max ) + val maxSizeValue: T = operations.unsafeValueAt( maxSize ) + assertEquals( max, maxSizeValue ) + } + + @Test + fun TSize_can_represent_full_range() + { + val rangeBelowZero = operations.getDistance( operations.minValue, valueZero ) + val rangeAboveZero = operations.getDistance( valueZero, operations.maxValue ) + val fullRange = operations.getDistance( operations.minValue, operations.maxValue ) + + assertEquals( + fullRange, + sizeOperations.unsafeAdd( rangeBelowZero, rangeAboveZero ) + ) + } + + @Test + fun getDistance_is_commutative() + { + val value = operations.unsafeValueAt( positiveSize ) + + val distance1 = operations.getDistance( valueZero, value ) + val distance2 = operations.getDistance( value, valueZero ) + + assertEquals( positiveSize, distance1 ) + assertEquals( positiveSize, distance2 ) + } + + @Test + fun getDistance_is_always_positive() + { + if ( !sizeOperations.isSignedType ) return + + val negativeSize = sizeOperations.unsafeSubtract( sizeZero, positiveSize ) + val valueAtNegative = operations.unsafeValueAt( negativeSize ) + + val distance1 = operations.getDistance( valueZero, valueAtNegative ) + val distance2 = operations.getDistance( valueAtNegative, valueZero ) + + assertEquals( positiveSize, distance1 ) + assertEquals( positiveSize, distance2 ) + } + + @Test + fun unsafeValueAt_is_always_positive() + { + val valueAtPositive = operations.unsafeValueAt( positiveSize ) + assertTrue( valueAtPositive > valueZero, "$valueAtPositive isn't > $valueZero." ) + + if ( sizeOperations.isSignedType ) + { + val negativeSize = sizeOperations.unsafeSubtract( sizeZero, positiveSize ) + val valueAtNegative = operations.unsafeValueAt( negativeSize ) + assertTrue( valueAtNegative > valueZero, "$valueAtNegative isn't > $valueZero." ) + } + } + + @Test + fun unsafeShift_with_positive_value() + { + val shiftedRight = operations.unsafeShift( valueZero, positiveSize, invertDirection = false ) + + val positiveValue = operations.unsafeValueAt( positiveSize ) + assertEquals( positiveValue, shiftedRight ) + + val shiftedLeft = operations.unsafeShift( positiveValue, positiveSize, invertDirection = true ) + assertEquals( valueZero, shiftedLeft ) + } + + @Test + fun unsafeShift_with_negative_value() + { + if ( !sizeOperations.isSignedType ) return + + val negativeSize = sizeOperations.unsafeSubtract( sizeZero, positiveSize ) + assertEquals( + operations.unsafeShift( valueZero, positiveSize, invertDirection = true ), + operations.unsafeShift( valueZero, negativeSize, invertDirection = false ) + ) + assertEquals( + operations.unsafeShift( valueZero, positiveSize, invertDirection = false ), + operations.unsafeShift( valueZero, negativeSize, invertDirection = true ) + ) + } + + @Test + fun unsafeShift_zero() + { + val shiftedRight = operations.unsafeShift( valueZero, sizeZero, invertDirection = false ) + assertEquals( valueZero, shiftedRight ) + + val shiftedLeft = operations.unsafeShift( valueZero, sizeZero, invertDirection = true ) + assertEquals( valueZero, shiftedLeft ) + } + + @Test + fun unsafeShift_full_range() + { + val fullRange = operations.getDistance( operations.minValue, operations.maxValue ) + + val toMax = operations.unsafeShift( operations.minValue, fullRange, invertDirection = false ) + assertEquals( operations.maxValue, toMax ) + + val toMin = operations.unsafeShift( operations.maxValue, fullRange, invertDirection = true ) + assertEquals( operations.minValue, toMin ) + } + + @Test + fun unsafeShift_can_overflow() + { + val max = valueOperations.maxValue + val shiftMaxOverflow = operations.unsafeShift( valueOperations.maxValue, positiveSize, invertDirection = false ) + + val min = valueOperations.minValue + val shiftMinOverflow = operations.unsafeShift( valueOperations.minValue, positiveSize, invertDirection = true ) + + // Types either overflow, or are coerced to max value (e.g. floating points). + assertTrue( shiftMaxOverflow < max || shiftMaxOverflow == max ) + assertTrue( shiftMinOverflow > min || shiftMinOverflow == min ) + } + + @Test + fun unsafeShift_with_coerced_max_size_values() + { + val maxShift = sizeOperations.maxValue + val maxOverflow = sizeOperations.unsafeAdd( maxShift, maxShift ) + if ( maxShift == maxOverflow ) + { + assertFailsWith { + operations.unsafeShift( valueZero, maxShift, invertDirection = false ) + } + } + + val minShift = sizeOperations.minValue + val minOverflow = sizeOperations.unsafeSubtract( minShift, maxShift ) + if ( minShift == minOverflow ) + { + assertFailsWith { + operations.unsafeShift(valueZero, minShift, invertDirection = true) + } + } + } +} diff --git a/kotlinx.interval.testcases/src/commonMain/kotlin/TypeOperationsTest.kt b/kotlinx.interval.testcases/src/commonMain/kotlin/TypeOperationsTest.kt index 83bffa8..fb5d315 100644 --- a/kotlinx.interval.testcases/src/commonMain/kotlin/TypeOperationsTest.kt +++ b/kotlinx.interval.testcases/src/commonMain/kotlin/TypeOperationsTest.kt @@ -8,6 +8,7 @@ import kotlin.test.* * Tests for [typeOperations] which uses the variables [a] and [b], * and expected results of operations as specified by the corresponding test properties (e.g., [aMinusB]). */ +@Suppress( "FunctionName" ) abstract class TypeOperationsTest>( private val typeOperations: TypeOperations, private val a: T, @@ -61,4 +62,70 @@ abstract class TypeOperationsTest>( val result = typeOperations.unsafeSubtract( a, a ) assertEquals( typeOperations.additiveIdentity, result ) } + + @Test + fun toDouble_fromDouble_roundtrip_returns_same_value() + { + val original = a + + val toDouble = typeOperations.toDouble( original ) + val fromDouble = typeOperations.fromDouble( toDouble ) + + assertEquals( original, fromDouble ) + } + + @Test + fun toDouble_fromDouble_roundtrip_for_maxima() + { + val maxima = listOf( typeOperations.minValue, typeOperations.maxValue ) + maxima.forEach { + val toDouble = typeOperations.toDouble( it ) + val fromDouble = typeOperations.fromDouble( toDouble ) + assertEquals( it, fromDouble ) + } + } + + @Test + fun fromDouble_rounds_to_nearest_value() + { + // Rounding only needed for evenly-spaced types. + val spacing = typeOperations.spacing + if ( spacing == null ) return + + val next = typeOperations.unsafeAdd( a, spacing ) + val aDouble = typeOperations.toDouble( a ) + val nextDouble = typeOperations.toDouble( next ) + val oneThird = (nextDouble - aDouble) / 3 + val closerToA = aDouble + oneThird + val closerToNext = nextDouble - oneThird + + assertEquals( a, typeOperations.fromDouble( closerToA ) ) + assertEquals( next, typeOperations.fromDouble( closerToNext ) ) + } + + @Test + fun fromDouble_overflows_past_max() + { + val max = typeOperations.maxValue + val maxDouble = typeOperations.toDouble( max ) + val pastMaxDouble = maxDouble + 1.0 + + val overflowMax = typeOperations.fromDouble( pastMaxDouble ) + + // Types either overflow, or are coerced to max value (e.g. floating points). + if ( overflowMax != max ) assertEquals( typeOperations.minValue, overflowMax ) + } + + @Test + fun fromDouble_overflows_past_min() + { + val min = typeOperations.minValue + val minDouble = typeOperations.toDouble( min ) + val pastMinDouble = minDouble - 1.0 + + val overflowMin = typeOperations.fromDouble( pastMinDouble ) + + // Types either overflow, or are coerced to min value (e.g. floating points). + if ( overflowMin != min ) assertEquals( typeOperations.maxValue, overflowMin ) + } } diff --git a/kotlinx.interval/src/commonMain/kotlin/BasicTypeIntervals.kt b/kotlinx.interval/src/commonMain/kotlin/BasicTypeIntervals.kt index 4d78f1d..9419c75 100644 --- a/kotlinx.interval/src/commonMain/kotlin/BasicTypeIntervals.kt +++ b/kotlinx.interval/src/commonMain/kotlin/BasicTypeIntervals.kt @@ -12,11 +12,10 @@ class ByteInterval( start: Byte, isStartIncluded: Boolean, end: Byte, isEndInclu { companion object { - internal val Operations = createIntervalTypeOperations() - { - if ( it < 0 ) (0 - it).toUByte() - else it.toUByte() - } + internal val Operations = createIntervalTypeOperations( + getDistance = { a, b -> (b - a).absoluteValue.toUByte() }, + unsafeValueAt = { it.toByte() } + ) } } @@ -37,11 +36,10 @@ class ShortInterval( start: Short, isStartIncluded: Boolean, end: Short, isEndIn { companion object { - internal val Operations = createIntervalTypeOperations() - { - if ( it < 0 ) (0 - it).toUShort() - else it.toUShort() - } + internal val Operations = createIntervalTypeOperations( + getDistance = { a, b -> (b - a).absoluteValue.toUShort() }, + unsafeValueAt = { it.toShort() } + ) } } @@ -62,11 +60,13 @@ class IntInterval( start: Int, isStartIncluded: Boolean, end: Int, isEndIncluded { companion object { - internal val Operations = createIntervalTypeOperations() - { - if ( it < 0 ) (0 - it).toUInt() - else it.toUInt() - } + internal val Operations = createIntervalTypeOperations( + getDistance = { a, b -> + if ( a < 0 != b < 0 ) a.absoluteValue.toUInt() + b.absoluteValue.toUInt() + else (b - a).absoluteValue.toUInt() + }, + unsafeValueAt = { it.toInt() } + ) } } @@ -87,11 +87,13 @@ class LongInterval( start: Long, isStartIncluded: Boolean, end: Long, isEndInclu { companion object { - internal val Operations = createIntervalTypeOperations() - { - if ( it < 0 ) (0 - it).toULong() - else it.toULong() - } + internal val Operations = createIntervalTypeOperations( + getDistance = { a, b -> + if ( a < 0 != b < 0 ) a.absoluteValue.toULong() + b.absoluteValue.toULong() + else (b - a).absoluteValue.toULong() + }, + unsafeValueAt = { it.toLong() } + ) } } @@ -112,7 +114,17 @@ class FloatInterval( start: Float, isStartIncluded: Boolean, end: Float, isEndIn { companion object { - internal val Operations = createIntervalTypeOperations { it.absoluteValue.toDouble() } + internal val Operations = object : IntervalTypeOperations( + FloatOperations, + DoubleOperations, + getDistance = { a, b -> (b.toDouble() - a.toDouble()).absoluteValue }, + unsafeValueAt = { it.absoluteValue.toFloat() } + ) + { + private val MAX = Float.MAX_VALUE / 2 + override val minValue: Float = -MAX + override val maxValue: Float = MAX + } } } @@ -127,15 +139,23 @@ fun interval( start: Float, end: Float, isStartIncluded: Boolean = true, isEndIn /** * An [Interval] representing the set of all [Double] values lying between a provided [start] and [end] value. * The interval can be closed, open, or half-open, as determined by [isStartIncluded] and [isEndIncluded]. - * - * The [size] of [Double] intervals which exceed [Double.MAX_VALUE] will be [Double.POSITIVE_INFINITY]. */ class DoubleInterval( start: Double, isStartIncluded: Boolean, end: Double, isEndIncluded: Boolean ) : Interval( start, isStartIncluded, end, isEndIncluded, Operations ) { companion object { - internal val Operations = createIntervalTypeOperations { it.absoluteValue } + internal val Operations = object : IntervalTypeOperations( + DoubleOperations, + DoubleOperations, + getDistance = { a, b -> (b - a).absoluteValue }, + unsafeValueAt = { it.absoluteValue } + ) + { + private val MAX = Double.MAX_VALUE / 2 + override val minValue: Double = -MAX + override val maxValue: Double = MAX + } } } @@ -156,7 +176,13 @@ class UByteInterval( start: UByte, isStartIncluded: Boolean, end: UByte, isEndIn { companion object { - internal val Operations = createIntervalTypeOperations { it } + internal val Operations = createIntervalTypeOperations( + getDistance = { a, b -> + if ( a < b ) (b - a).toUByte() + else (a - b).toUByte() + }, + unsafeValueAt = { it }, + ) } } @@ -177,7 +203,13 @@ class UShortInterval( start: UShort, isStartIncluded: Boolean, end: UShort, isEn { companion object { - internal val Operations = createIntervalTypeOperations { it } + internal val Operations = createIntervalTypeOperations( + getDistance = { a, b -> + if ( a < b ) (b - a).toUShort() + else (a - b).toUShort() + }, + unsafeValueAt = { it } + ) } } @@ -198,7 +230,10 @@ class UIntInterval( start: UInt, isStartIncluded: Boolean, end: UInt, isEndInclu { companion object { - internal val Operations = createIntervalTypeOperations { it } + internal val Operations = createIntervalTypeOperations( + getDistance = { a, b -> if ( a < b ) b - a else a - b }, + unsafeValueAt = { it } + ) } } @@ -219,7 +254,10 @@ class ULongInterval( start: ULong, isStartIncluded: Boolean, end: ULong, isEndIn { companion object { - internal val Operations = createIntervalTypeOperations { it } + internal val Operations = createIntervalTypeOperations( + getDistance = { a, b -> if ( a < b ) b - a else a - b }, + unsafeValueAt = { it } + ) } } @@ -240,7 +278,10 @@ class CharInterval( start: Char, isStartIncluded: Boolean, end: Char, isEndInclu { companion object { - internal val Operations = createIntervalTypeOperations { it.code.toUShort() } + internal val Operations = createIntervalTypeOperations( + getDistance = { a, b -> (b - a).absoluteValue.toUShort() }, + unsafeValueAt = { Char( it ) } + ) } } diff --git a/kotlinx.interval/src/commonMain/kotlin/BasicTypeOperations.kt b/kotlinx.interval/src/commonMain/kotlin/BasicTypeOperations.kt index 86f2e15..dddfc02 100644 --- a/kotlinx.interval/src/commonMain/kotlin/BasicTypeOperations.kt +++ b/kotlinx.interval/src/commonMain/kotlin/BasicTypeOperations.kt @@ -1,5 +1,7 @@ package io.github.whathecode.kotlinx.interval +import kotlin.math.roundToInt +import kotlin.math.roundToLong import kotlin.reflect.KClass @@ -45,6 +47,9 @@ internal object ByteOperations : TypeOperations override fun unsafeAdd( a: Byte, b: Byte ): Byte = (a + b).toByte() override fun unsafeSubtract( a: Byte, b: Byte ): Byte = (a - b).toByte() + + override fun fromDouble( double: Double ) = double.roundToInt().toByte() + override fun toDouble( value: Byte ) = value.toDouble() } internal object ShortOperations : TypeOperations @@ -56,6 +61,9 @@ internal object ShortOperations : TypeOperations override fun unsafeAdd( a: Short, b: Short ): Short = (a + b).toShort() override fun unsafeSubtract( a: Short, b: Short ): Short = (a - b).toShort() + + override fun fromDouble( double: Double ) = double.roundToInt().toShort() + override fun toDouble( value: Short ) = value.toDouble() } internal object IntOperations : TypeOperations @@ -67,6 +75,9 @@ internal object IntOperations : TypeOperations override fun unsafeAdd( a: Int, b: Int ): Int = a + b override fun unsafeSubtract( a: Int, b: Int ): Int = a - b + + override fun fromDouble( double: Double ): Int = double.roundToInt() + override fun toDouble( value: Int ): Double = value.toDouble() } internal object LongOperations : TypeOperations @@ -78,28 +89,37 @@ internal object LongOperations : TypeOperations override fun unsafeAdd( a: Long, b: Long ): Long = a + b override fun unsafeSubtract( a: Long, b: Long ): Long = a - b + + override fun fromDouble( double: Double ): Long = double.roundToLong() + override fun toDouble( value: Long ): Double = value.toDouble() } internal object FloatOperations : TypeOperations { override val additiveIdentity: Float = 0f - override val minValue: Float = -Float.MAX_VALUE - override val maxValue: Float = Float.MAX_VALUE + override val minValue: Float = Float.NEGATIVE_INFINITY + override val maxValue: Float = Float.POSITIVE_INFINITY override val spacing: Float? = null override fun unsafeAdd( a: Float, b: Float ): Float = a + b override fun unsafeSubtract( a: Float, b: Float ): Float = a - b + + override fun fromDouble( double: Double ): Float = double.toFloat() + override fun toDouble( value: Float ): Double = value.toDouble() } internal object DoubleOperations : TypeOperations { override val additiveIdentity: Double = 0.0 - override val minValue: Double = -Double.MAX_VALUE - override val maxValue: Double = Double.MAX_VALUE + override val minValue: Double = Double.NEGATIVE_INFINITY + override val maxValue: Double = Double.POSITIVE_INFINITY override val spacing: Double? = null override fun unsafeAdd( a: Double, b: Double ): Double = a + b override fun unsafeSubtract( a: Double, b: Double ): Double = a - b + + override fun fromDouble( double: Double ): Double = double + override fun toDouble( value: Double ): Double = value } internal object UByteOperations : TypeOperations @@ -111,6 +131,9 @@ internal object UByteOperations : TypeOperations override fun unsafeAdd( a: UByte, b: UByte ): UByte = (a + b).toUByte() override fun unsafeSubtract( a: UByte, b: UByte ): UByte = (a - b).toUByte() + + override fun fromDouble( double: Double ): UByte = double.roundToInt().toUByte() + override fun toDouble( value: UByte ): Double = value.toDouble() } internal object UShortOperations : TypeOperations @@ -122,6 +145,9 @@ internal object UShortOperations : TypeOperations override fun unsafeAdd( a: UShort, b: UShort ): UShort = (a + b).toUShort() override fun unsafeSubtract( a: UShort, b: UShort ): UShort = (a - b).toUShort() + + override fun fromDouble( double: Double ): UShort = double.roundToInt().toUShort() + override fun toDouble( value: UShort ): Double = value.toDouble() } internal object UIntOperations : TypeOperations @@ -133,6 +159,13 @@ internal object UIntOperations : TypeOperations override fun unsafeAdd( a: UInt, b: UInt ): UInt = a + b override fun unsafeSubtract( a: UInt, b: UInt ): UInt = a - b + + override fun fromDouble( double: Double ): UInt + { + val uInt = double.toUInt() + return if ( double - uInt.toDouble() >= 0.5 ) uInt + 1.toUInt() else uInt + } + override fun toDouble( value: UInt ): Double = value.toDouble() } internal object ULongOperations : TypeOperations @@ -144,6 +177,13 @@ internal object ULongOperations : TypeOperations override fun unsafeAdd( a: ULong, b: ULong ): ULong = a + b override fun unsafeSubtract( a: ULong, b: ULong ): ULong = a - b + + override fun fromDouble( double: Double ): ULong + { + val uLong = double.toULong() + return if ( double - uLong.toDouble() >= 0.5 ) uLong + 1.toULong() else uLong + } + override fun toDouble( value: ULong ): Double = value.toDouble() } internal object CharOperations : TypeOperations @@ -155,4 +195,7 @@ internal object CharOperations : TypeOperations override fun unsafeAdd( a: Char, b: Char ): Char = a + b.code override fun unsafeSubtract( a: Char, b: Char ): Char = (a - b).toChar() + + override fun fromDouble( double: Double ): Char = double.roundToInt().toChar() + override fun toDouble( value: Char ): Double = value.code.toDouble() } diff --git a/kotlinx.interval/src/commonMain/kotlin/Interval.kt b/kotlinx.interval/src/commonMain/kotlin/Interval.kt index ae46ebd..d7bbb02 100644 --- a/kotlinx.interval/src/commonMain/kotlin/Interval.kt +++ b/kotlinx.interval/src/commonMain/kotlin/Interval.kt @@ -22,6 +22,11 @@ open class Interval, TSize : Comparable>( { init { + require( lowerBound >= operations.minValue ) + { "Lower bound shouldn't be smaller than ${operations.minValue} to prevent interval size overflows." } + require( upperBound <= operations.maxValue ) + { "Upper bound shouldn't be greater than ${operations.maxValue} to prevent interval size overflows." } + if ( !isStartIncluded || !isEndIncluded ) { require( start != end ) { "Open or half-open intervals should have differing start and end value." } @@ -72,24 +77,34 @@ open class Interval, TSize : Comparable>( /** * The absolute difference between [start] and [end]. */ - val size: TSize get() + val size: TSize get() = operations.getDistance( start, end ) + + + /** + * Safe initialization of a calculated interval in case loss of precision can cause invalid initialization. + * I.e., coerce values to the minimum and maximum allowed values, and collapse the interval to a single value in + * case its size is zero. + */ + private fun safeInterval( + start: T, + isStartIncluded: Boolean, + end: T, + isEndIncluded: Boolean + ): Interval { - val zero = valueOperations.additiveIdentity - val startDistance = operations.getDistanceTo( start ) - val endDistance = operations.getDistanceTo( end ) - val valuesHaveOppositeSign = start <= zero != end <= zero + val coercedStart = coerceMinMax( start ) + val coercedEnd = coerceMinMax( end ) - return if ( valuesHaveOppositeSign ) - { - sizeOperations.unsafeAdd( startDistance, endDistance ) - } - else - { - if ( startDistance < endDistance ) sizeOperations.unsafeSubtract( endDistance, startDistance ) - else sizeOperations.unsafeSubtract( startDistance, endDistance ) - } + val interval = + if ( coercedStart == coercedEnd ) Interval( coercedStart, true, coercedEnd, true, operations ) + else Interval( coercedStart, isStartIncluded, coercedEnd, isEndIncluded, operations ) + return interval } + private fun coerceMinMax( value: T ) = + if ( value > operations.maxValue ) operations.maxValue + else if ( value < operations.minValue ) operations.minValue + else value override fun iterator(): Iterator> = listOf( this ).iterator() @@ -107,6 +122,34 @@ open class Interval, TSize : Comparable>( && ( upperCompare < 0 || (upperCompare == 0 && isUpperBoundIncluded) ) } + /** + * Get the value at a given percentage within (0.0–1.0) or outside (< 0.0, > 1.0) of the interval. + * 0.0 corresponds to [start] and 1.0 to [end]. + * The calculation is performed using [Double] arithmetic and the result is rounded to the to nearest value of [T]. + * + * @throws ArithmeticException if the resulting value falls outside the range which can be represented by [T]. + */ + fun getValueAt( percentage: Double ): T + { + val normalizedPercentage = if ( isReversed ) 1.0 - percentage else percentage + val shiftLeft = normalizedPercentage < 0 + val absPercentage = if ( shiftLeft ) -normalizedPercentage else normalizedPercentage + val addToLowerBoundDouble = sizeOperations.toDouble( size ) * absPercentage + + // Throw when the resulting value can't be represented by T. + if ( shiftLeft || normalizedPercentage > 1 ) + { + val max = if ( shiftLeft ) operations.minValue else operations.maxValue + val toMax = operations.getDistance( lowerBound, max ) + val toMaxDouble = sizeOperations.toDouble( toMax ) + if ( addToLowerBoundDouble > toMaxDouble ) + throw ArithmeticException( "The resulting value is out of bounds for this type." ) + } + + val addToLowerBoundSize = sizeOperations.fromDouble( addToLowerBoundDouble ) + return operations.unsafeShift( lowerBound, addToLowerBoundSize, shiftLeft ) + } + /** * Return an [IntervalUnion] representing all [T] values in this interval, * excluding all [T] values in the specified interval [toSubtract]. @@ -171,6 +214,42 @@ open class Interval, TSize : Comparable>( pairCompare.lower.operations ) } + override fun shift( amount: TSize, invertDirection: Boolean ): ShiftResult, TSize> + { + val sizeZero = sizeOperations.additiveIdentity + if ( amount == sizeZero ) return ShiftResult( this, amount ) + + // Clamp maximum amount to shift to min/max value which can be represented by values of T. + val shiftRight = if ( amount >= sizeZero ) !invertDirection else invertDirection + val valueZero = valueOperations.additiveIdentity + val (max, bound) = + if ( shiftRight ) Pair( operations.maxValue, upperBound ) + else Pair( operations.minValue, lowerBound ) + var maxSize = operations.getDistance( valueZero, max ) + val boundSize = operations.getDistance( valueZero, bound ) + if ( amount < sizeZero ) + { + maxSize = sizeOperations.unsafeSubtract( sizeZero, maxSize ) + } + val maxShift = + if ( (bound < valueZero && shiftRight) || (bound > valueZero && !shiftRight) ) + { + sizeOperations.unsafeAdd( maxSize, boundSize ) + } + else + { + sizeOperations.unsafeSubtract( maxSize, boundSize ) + } + val overflows = if ( amount < sizeZero ) amount <= maxShift else amount >= maxShift + val toShift = if ( overflows ) maxShift else amount + + // Return shifted interval. + val shiftedStart = operations.unsafeShift( start, toShift, invertDirection ) + val shiftedEnd = operations.unsafeShift( end, toShift, invertDirection ) + val shifted = safeInterval( shiftedStart, isStartIncluded, shiftedEnd, isEndIncluded ) + return ShiftResult( shifted, toShift ) + } + /** * Determines whether [interval] has at least one value in common with this interval. */ diff --git a/kotlinx.interval/src/commonMain/kotlin/IntervalTypeOperations.kt b/kotlinx.interval/src/commonMain/kotlin/IntervalTypeOperations.kt index 860ead4..70bdeac 100644 --- a/kotlinx.interval/src/commonMain/kotlin/IntervalTypeOperations.kt +++ b/kotlinx.interval/src/commonMain/kotlin/IntervalTypeOperations.kt @@ -3,9 +3,9 @@ package io.github.whathecode.kotlinx.interval /** * Provides generic access to the predefined set of type operators of interval values of type [T] ([valueOperations]) - * and distances between values of type [TSize] ([sizeOperations]). + * and distances between values of type [TSize] ([sizeOperations]), and operations to convert between them. */ -class IntervalTypeOperations, TSize : Comparable>( +abstract class IntervalTypeOperations, TSize : Comparable>( /** * Provide access to the predefined set of operators of [T]. */ @@ -15,10 +15,85 @@ class IntervalTypeOperations, TSize : Comparable>( */ val sizeOperations: TypeOperations, /** - * Return the distance from a specified value [T] to the additive identity (usually "zero") of [T]. + * Return the distance between two values of [T]. */ - val getDistanceTo: (T) -> TSize + val getDistance: (T, T) -> TSize, + /** + * Returns the positive value [T] at the specified distance from the additive identity (usually "zero") of [T]. + * This isn't safeguarded against overflows in case the value is larger than the maximum value of [T]. + */ + val unsafeValueAt: (TSize) -> T ) +{ + /** + * The minimum allowed value of [T] to ensure that the interval size can still be represented by [TSize]. + */ + abstract val minValue: T + + /** + * The maximum allowed value of [T] to ensure that the interval size can still be represented by [TSize]. + */ + abstract val maxValue: T + + + /** + * Returns a new value offset by the specified [amount] from the given [value]. + * This isn't safeguarded against overflows in case the resulting value goes out of bounds of [T]. + * + * @param invertDirection Inverts the direction by which the value is shifted. I.e., if [amount] is positive, + * shift left instead of shift right, and vice verse. This can be used to shift values of unsigned types, + * which can't represent a negative [amount], left. + * @throws IllegalArgumentException if [amount] represents an infinite value; no meaningful value can be returned. + */ + fun unsafeShift( value: T, amount: TSize, invertDirection: Boolean ): T + { + val sizeZero = sizeOperations.additiveIdentity + val minSize = + if ( sizeOperations.isSignedType ) + { + sizeOperations.unsafeSubtract( sizeZero, getDistance( valueOperations.additiveIdentity, minValue ) ) + } + else { sizeOperations.additiveIdentity } + val maxSize = getDistance( valueOperations.additiveIdentity, maxValue ) + + var toShift = amount + var curValue = value + while ( true ) + { + val prevShift = toShift + + if ( toShift > maxSize ) + { + toShift = sizeOperations.unsafeSubtract( toShift, maxSize ) + curValue = + if ( invertDirection ) valueOperations.unsafeSubtract( curValue, maxValue ) + else valueOperations.unsafeAdd( curValue, maxValue ) + } + else if ( toShift < minSize ) + { + toShift = sizeOperations.unsafeSubtract( toShift, minSize ) + curValue = + if ( invertDirection ) valueOperations.unsafeSubtract( curValue, minValue ) + else valueOperations.unsafeAdd( curValue, minValue ) + } + else + { + val shiftRight = if ( toShift >= sizeZero ) !invertDirection else invertDirection + val shiftAbs = unsafeValueAt( toShift ) + curValue = + if ( shiftRight ) valueOperations.unsafeAdd( curValue, shiftAbs ) + else valueOperations.unsafeSubtract( curValue, shiftAbs ) + break; + } + + // If the amount to shift isn't reduced after one iteration, it must be coerced to an "infinity" value. + // This makes this an infinite operation with no meaningful output. + require( toShift != prevShift ) { "The passed amount to shift is not allowed to be infinite." } + } + + return curValue + } +} /** @@ -39,7 +114,16 @@ inline fun , reified TSize : Comparable> create */ sizeOperations: TypeOperations = getBasicTypeOperationsFor(), /** - * A function returning the distance from a specified value [T] to the additive identity (usually "zero") of [T]. + * A function returning the distance between values of [T]. + */ + noinline getDistance: (T, T) -> TSize, + /** + * A function returning the value [T] at the specified distance from the additive identity (usually "zero") of [T], + * not safeguarded against overflows in case the value is larger than the maximum value of [T]. */ - noinline getDistanceTo: (T) -> TSize -) = IntervalTypeOperations( valueOperations, sizeOperations, getDistanceTo ) + noinline unsafeValueAt: (TSize) -> T +) = object : IntervalTypeOperations( valueOperations, sizeOperations, getDistance, unsafeValueAt ) +{ + override val minValue: T = valueOperations.minValue + override val maxValue: T = valueOperations.maxValue +} diff --git a/kotlinx.interval/src/commonMain/kotlin/IntervalUnion.kt b/kotlinx.interval/src/commonMain/kotlin/IntervalUnion.kt index d8fd21b..88236fe 100644 --- a/kotlinx.interval/src/commonMain/kotlin/IntervalUnion.kt +++ b/kotlinx.interval/src/commonMain/kotlin/IntervalUnion.kt @@ -38,6 +38,31 @@ sealed interface IntervalUnion, TSize : Comparable> : I */ operator fun plus( toAdd: Interval ): IntervalUnion + /** + * Returns an [IntervalUnion] offset from this interval union by the specified [amount], + * or as much as possible before the minimum or maximum value that can be represented by [T] is reached. + * In case the interval couldn't be offset the full [amount], the final [ShiftResult.offsetAmount] will differ. + * + * @param invertDirection Inverts the direction by which the interval is offset. I.e., if [amount] is positive, + * shift left instead of shift right, and vice verse. This can be used to shift intervals with unsigned types, + * which can't represent a negative [amount], left. + */ + fun shift( amount: TSize, invertDirection: Boolean = false ): ShiftResult, TSize> + + /** + * Returns an [IntervalUnion] offset to the right from this interval union by the specified [amount], + * or as much as possible before the minimum or maximum value that can be represented by [T] is reached. + * Use [shift] in case you want to verify whether the interval could be offset the full [amount]. + */ + infix fun shr( amount: TSize ): IntervalUnion = shift( amount ).shiftedInterval + + /** + * Returns an [IntervalUnion] offset to the left from this interval union by the specified [amount], + * or as much as possible before the minimum or maximum value that can be represented by [T] is reached. + * Use [shift] in case you want to verify whether the interval could be offset the full [amount]. + */ + infix fun shl( amount: TSize ): IntervalUnion = shift( amount, invertDirection = true ).shiftedInterval + /** * Determines whether [interval] has at least one value in common with this set. */ @@ -50,28 +75,55 @@ sealed interface IntervalUnion, TSize : Comparable> : I } +/** + * The result of an [IntervalUnion.shift] operation on an interval union of type [TInterval]. + */ +data class ShiftResult, TSize : Comparable>( + /** + * A new [IntervalUnion], offset from the interval on which the [IntervalUnion.shift] operation was performed + * by [offsetAmount]. + */ + val shiftedInterval: TInterval, + /** + * The final amount by which [shiftedInterval] is offset from the interval on which the [IntervalUnion.shift] + * operation was performed. This may be smaller than the originally requested offset in case a larger offset would + * result in a value which can't be represented by values in the interval. + */ + val offsetAmount: TSize +) + + /** * Create an [IntervalUnion] which represents a set which contains no values. */ @Suppress( "UNCHECKED_CAST" ) -internal inline fun , TSize : Comparable> emptyIntervalUnion() = - EmptyIntervalUnion as IntervalUnion +fun , TSize : Comparable> emptyIntervalUnion() = EmptyIntervalUnion as IntervalUnion -private data object EmptyIntervalUnion : IntervalUnion +private data object EmptyIntervalUnion : IntervalUnion, Comparable> { - override fun getBounds(): Interval? = null + override fun getBounds(): Interval, Comparable>? = null + + override fun contains( value: Comparable ): Boolean = false - override fun contains( value: Nothing ): Boolean = false + override fun minus( + toSubtract: Interval, Comparable> + ): IntervalUnion, Comparable> = this - override fun minus( toSubtract: Interval ): IntervalUnion = this + override fun plus( + toAdd: Interval, Comparable> + ): IntervalUnion, Comparable> = toAdd - override fun plus( toAdd: Interval ): IntervalUnion = toAdd + override fun shift( + amount: Comparable, + invertDirection: Boolean + ): ShiftResult> = ShiftResult( this, amount ) - override fun intersects( interval: Interval ): Boolean = false + override fun intersects( interval: Interval, Comparable> ): Boolean = false - override fun setEquals( other: IntervalUnion ): Boolean = other == this + override fun setEquals( other: IntervalUnion, Comparable> ): Boolean = other == this - override fun iterator(): Iterator> = emptyList>().iterator() + override fun iterator(): Iterator, Comparable>> = + emptyList, Comparable>>().iterator() } @@ -112,6 +164,16 @@ private class IntervalUnionPair, TSize : Comparable, TU unionPair.upperBounds.end, unionPair.upperBounds.isEndIncluded, unionPair.lowerBounds.operations ) + // Retrieve type operations from any underlying interval. + private val operations: IntervalTypeOperations = getOperations( this ) + @Suppress( "UNCHECKED_CAST" ) + private fun getOperations( pair: IntervalUnionPair<*, *, *> ) : IntervalTypeOperations = + when ( pair.lower ) { + is Interval<*, *> -> pair.lower.operations as IntervalTypeOperations + is IntervalUnionPair<*, *, *> -> getOperations( pair.lower ) + else -> throw IllegalStateException( "Unexpected underlying interval type: ${pair.lower}." ) + } + override fun iterator(): Iterator> = ( lower.asSequence() + upper.asSequence() ).iterator() @@ -177,6 +239,36 @@ private class IntervalUnionPair, TSize : Comparable, TU return upper.fold( lower + toAdd ) { result, upperInterval -> result + upperInterval } } + override fun shift( + amount: TSize, + invertDirection: Boolean + ): ShiftResult, TSize> + { + val sizeZero = operations.sizeOperations.additiveIdentity + val shiftRight = if ( amount >= sizeZero ) !invertDirection else invertDirection + + val lowerShifted: ShiftResult, TSize> + val upperShifted: ShiftResult, TSize> + val coercedAmount: TSize + if ( shiftRight ) + { + upperShifted = upper.shift( amount, invertDirection ) + lowerShifted = lower.shift( upperShifted.offsetAmount, invertDirection ) + coercedAmount = upperShifted.offsetAmount + } + else + { + lowerShifted = lower.shift( amount, invertDirection ) + upperShifted = upper.shift( lowerShifted.offsetAmount, invertDirection ) + coercedAmount = lowerShifted.offsetAmount + } + + return ShiftResult( + intervalUnionPair( lowerShifted.shiftedInterval, upperShifted.shiftedInterval ), + coercedAmount + ) + } + override fun intersects( interval: Interval ): Boolean { if ( lower is Interval<*, *> && lower.intersects( interval ) ) return true diff --git a/kotlinx.interval/src/commonMain/kotlin/TypeOperations.kt b/kotlinx.interval/src/commonMain/kotlin/TypeOperations.kt index 463240f..b51f683 100644 --- a/kotlinx.interval/src/commonMain/kotlin/TypeOperations.kt +++ b/kotlinx.interval/src/commonMain/kotlin/TypeOperations.kt @@ -38,4 +38,22 @@ interface TypeOperations> * The resulting type is cast back to type [T]. */ fun unsafeSubtract( a: T, b: T ): T + + /** + * Convert a [double] to the closest corresponding value of [T]. + * If the [double] value is larger than what can be represented by [T] ([minValue]..[maxValue]), the returned value + * will overflow. + */ + fun fromDouble( double: Double ): T + + /** + * Convert [value] to a [Double], which may be lossy. + */ + fun toDouble( value: T ): Double + + + /** + * Determines whether this type can represent negative values. + */ + val isSignedType: Boolean get() = minValue < additiveIdentity } diff --git a/kotlinx.interval/src/commonTest/kotlin/IntervalUnionTest.kt b/kotlinx.interval/src/commonTest/kotlin/IntervalUnionTest.kt index f8275d3..85b3700 100644 --- a/kotlinx.interval/src/commonTest/kotlin/IntervalUnionTest.kt +++ b/kotlinx.interval/src/commonTest/kotlin/IntervalUnionTest.kt @@ -1,5 +1,6 @@ package io.github.whathecode.kotlinx.interval +import kotlin.math.absoluteValue import kotlin.test.* @@ -11,10 +12,13 @@ class EmptyIntervalUnionTest private val empty = emptyIntervalUnion() @Test - fun isEmpty() = assertTrue( empty.isEmpty() ) + fun isEmpty_is_true() = assertTrue( empty.isEmpty() ) @Test - fun getBounds_for_empty_union_is_null() = assertEquals( null, empty.getBounds() ) + fun getBounds_is_null() = assertEquals( null, empty.getBounds() ) + + @Test + fun contains_is_always_false() = assertFalse( empty.contains( 42 ) ) @Test fun minus_returns_empty_interval() @@ -31,7 +35,21 @@ class EmptyIntervalUnionTest } @Test - fun setEquals_only_other_empty_union() = assertTrue( empty.setEquals( emptyIntervalUnion() ) ) + fun shift_returns_empty_interval_and_never_overflows() + { + val toShift = UInt.MAX_VALUE + + val emptyShifted = empty.shift( toShift ) + + assertEquals( empty, emptyShifted.shiftedInterval ) + assertEquals( toShift, emptyShifted.offsetAmount ) + } + + @Test + fun intersects_is_always_false() = assertFalse( empty.intersects( interval( 0, 10 ) ) ) + + @Test + fun setEquals_only_true_compared_to_other_empty_union() = assertTrue( empty.setEquals( emptyIntervalUnion() ) ) } @@ -346,6 +364,58 @@ class IntervalUnionPairTest ) } + @Test + fun shift_shifts_both_intervals() + { + val lower = interval( 5, 10 ) + val upper = interval( 15, 20 ) + val union = intervalUnionPair( lower, upper ) + + val shifted = union.shift( 5u, invertDirection = false ) + + assertUnionEquals( + setOf( interval( 10, 15 ), interval( 20, 25 ) ), + shifted.shiftedInterval + ) + assertEquals( 5u, shifted.offsetAmount ) + } + + @Test + fun shift_with_overflow_shifts_up_to_maximum_value() + { + val lower = interval( 0, 1000 ) + val upper = interval( 10_000, 30_000 ) + val union = intervalUnionPair( lower, upper ) + + val maxShift = UInt.MAX_VALUE + val shifted = union.shift( maxShift ) + + val expectedShift = (Int.MAX_VALUE - 30_000).toUInt() + assertEquals( + union.shift( expectedShift ).shiftedInterval.toSet(), + shifted.shiftedInterval.toSet() + ) + assertEquals( expectedShift, shifted.offsetAmount ) + } + + @Test + fun shift_using_invertDirection_with_overflow_shifts_down_to_minimum_value() + { + val lower = interval( -30_000, -10_000 ) + val upper = interval( -1000, 0 ) + val union = intervalUnionPair( lower, upper ) + + val maxShift = UInt.MAX_VALUE + val shifted = union.shift( maxShift, invertDirection = true ) + + val expectedShift = (Int.MIN_VALUE + 30_000).absoluteValue.toUInt() + assertEquals( + union.shift( expectedShift, invertDirection = true ).shiftedInterval.toSet(), + shifted.shiftedInterval.toSet() + ) + assertEquals( expectedShift, shifted.offsetAmount ) + } + @Test fun intersects_succeeds() { diff --git a/kotlinx.interval/src/commonTest/kotlin/Readme.kt b/kotlinx.interval/src/commonTest/kotlin/Readme.kt index dfb3f00..cd2a358 100644 --- a/kotlinx.interval/src/commonTest/kotlin/Readme.kt +++ b/kotlinx.interval/src/commonTest/kotlin/Readme.kt @@ -14,6 +14,7 @@ class Readme val areIncluded = 0 in interval && 5 in interval // true val areExcluded = 10 !in interval && 15 !in interval // true val size: UInt = interval.size // 10 + val shifted = interval shr 10u // Shifted right by 10: [10, 20) } @Test @@ -22,6 +23,7 @@ class Readme val start = interval( 0, 100 ) // Interval: [0, 100] val areIncluded = 50 in start && 100 in start // true val splitInTwo = start - interval( 25, 85 ) // Union: [[0, 25), (85, 100]] + val shiftBackAndForth = splitInTwo shr 100u shl 100u // == splitInTwo val areExcluded = 50 !in splitInTwo && 85 !in splitInTwo // true val unite = splitInTwo + interval( 10, 90 ) // Interval: [0, 100] val backToStart = start == unite // true