diff --git a/.circleci/config.yml b/.circleci/config.yml index 353ee058..22d0f78b 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -3,17 +3,17 @@ orbs: codecov: codecov/codecov@3.2.2 executor: machine jobs: - test: + build: machine: image: ubuntu-2004:202201-02 steps: - checkout - run: name: build - command: ./gradlew test + command: ./gradlew detekt assemble test --info --stacktrace - codecov/upload: file: build/reports/jacoco/test/*.xml workflows: build-workflow: jobs: - - test + - build diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md new file mode 100644 index 00000000..57630235 --- /dev/null +++ b/.github/pull_request_template.md @@ -0,0 +1,41 @@ + +> + +### Related Issue + +[Issue-X](https://github.com/janbarari/gradle-analytics-plugin/issues/X) + +### Motivation and Context + + +### Description + + +### Type of change + +- [ ] Feature +- [ ] POC +- [ ] Bug fix +- [ ] Hot fix +- [ ] Optimization +- [ ] Refactor +- [ ] Noref + +### Checklist +- [ ] Are local unit tests passed? +- [ ] Is Detekt passed? +- [ ] Is code coverage affected? +- [ ] Is any new test added? +- [ ] Is CI workflow affected? +- [ ] Is a next refactor needed? + +### How has this been tested? + + + + +### Screenshots + + + + \ No newline at end of file diff --git a/.gitignore b/.gitignore index 2853c74b..4023a549 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,5 @@ .idea/ build/ .DS_Store +temporary-notes.txt +site/ diff --git a/LICENSE b/LICENSE index 748d0618..7316ba37 100644 --- a/LICENSE +++ b/LICENSE @@ -1,6 +1,6 @@ MIT License -Copyright (c) 2022 Mehdi +Copyright (c) 2022 Mehdi Janbarari (@janbarari) Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal diff --git a/README.md b/README.md index b81070e3..60b94852 100644 --- a/README.md +++ b/README.md @@ -1,17 +1,10 @@ plugin logo -# Gradle Analytics Plugin +## Gradle Analytics Plugin [![CircleCI](https://circleci.com/gh/janbarari/gradle-analytics-plugin/tree/develop.svg?style=svg)](https://circleci.com/gh/janbarari/gradle-analytics-plugin/tree/develop) [![codecov](https://codecov.io/gh/janbarari/gradle-analytics-plugin/branch/develop/graph/badge.svg)](https://codecov.io/gh/janbarari/gradle-analytics-plugin) - - - - -Request Feature or Report Bug -
-Hey ๐Ÿ‘‹, Mark this repo in your browser, see you soon ;D +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=janbarari_gradle-analytics-plugin&metric=alert_status)](https://sonarcloud.io/dashboard?id=janbarari_gradle-analytics-plugin) -License ---- -Copyright ยฉ 2022 [Janbarari](https://github.com/janbarari) -This project binaries and source code can be used according to the [MIT LICENSE](https://github.com/janbarari/gradle-analytics-plugin/blob/main/LICENSE). +A free Gradle plugin to analyze your project builds. It provides unique visual and text metrics in HTML format. + +### Documentation is at janbarari.github.io/gradle-analytics-plugin diff --git a/build.gradle.kts b/build.gradle.kts index 59feb4b9..b63c636a 100644 --- a/build.gradle.kts +++ b/build.gradle.kts @@ -1,20 +1,63 @@ import org.jetbrains.kotlin.gradle.tasks.KotlinCompile +val pluginId: String by project +val pluginDisplayName: String by project +val pluginDescription: String by project +val pluginImplementationClass: String by project +val pluginDeclarationName: String by project +val pluginGroupPackageName: String by project +val pluginVersion: String by project +val pluginWebsite: String by project +val pluginVcsUrl: String by project +val pluginTags: String by project + +@Suppress("DSL_SCOPE_VIOLATION") plugins { - kotlin("jvm") version "1.6.10" + kotlin("jvm") version(libs.versions.kotlin) + alias(libs.plugins.detekt) + `java-gradle-plugin` + `maven-publish` jacoco + kotlin("kapt") version(libs.versions.kotlin) + id("com.gradle.plugin-publish") version "1.0.0-rc-1" } +group = pluginGroupPackageName +version = pluginVersion + repositories { mavenCentral() } dependencies { testImplementation(kotlin("test")) + testImplementation(libs.mockk) + + compileOnly(gradleApi()) + + implementation(kotlin("stdlib-jdk8")) + implementation(libs.sqlite.driver) + implementation(libs.mysql.driver) + implementation(libs.jetbrains.exposed.core) + implementation(libs.jetbrains.exposed.jdbc) + implementation(libs.moshi) + kapt(libs.moshi.codegen) + implementation(libs.commons.io) + implementation(libs.coroutines) + testImplementation(libs.coroutines.test) +} + +tasks.withType().configureEach { + kotlinOptions { + jvmTarget = JavaVersion.VERSION_11.toString() + } } -tasks.withType { - kotlinOptions.jvmTarget = JavaVersion.VERSION_1_8.toString() +plugins.withType().configureEach { + extensions.configure { + sourceCompatibility = JavaVersion.VERSION_11 + targetCompatibility = JavaVersion.VERSION_11 + } } tasks.test { @@ -28,4 +71,55 @@ tasks.jacocoTestReport { csv.required.set(true) html.required.set(true) } -} \ No newline at end of file +} + +publishing { + publications { + repositories { + mavenLocal() + } + } +} + +pluginBundle { + website = pluginWebsite + vcsUrl = pluginVcsUrl + tags = "$pluginTags".split(",") +} + +gradlePlugin { + plugins { + create(pluginDeclarationName) { + id = pluginId + displayName = pluginDisplayName + description = pluginDescription + implementationClass = pluginImplementationClass + } + } +} + +tasks.wrapper { + distributionType = Wrapper.DistributionType.BIN +} + +tasks.register("publishToLocal") { + doLast { + exec { + commandLine( + "./gradlew", + "detekt", + "build", + "test", + "publishToMavenLocal" + ).args("--info") + } + } +} + +detekt { + config = files("detekt-config.yml") + buildUponDefaultConfig = true + source = files( + "src/main/kotlin" + ) +} diff --git a/codecov.yml b/codecov.yml new file mode 100644 index 00000000..73991bcc --- /dev/null +++ b/codecov.yml @@ -0,0 +1,9 @@ +codecov: + require_ci_to_pass: yes + notify: + wait_for_ci: yes + +coverage: + precision: 2 + round: down + range: 65...100 diff --git a/console-screenshot.png b/console-screenshot.png new file mode 100644 index 00000000..0b7cd66c Binary files /dev/null and b/console-screenshot.png differ diff --git a/detekt-config.yml b/detekt-config.yml new file mode 100644 index 00000000..e824d42f --- /dev/null +++ b/detekt-config.yml @@ -0,0 +1,45 @@ +complexity: + active: true + TooManyFunctions: + active: true + excludes: [ '**/test/**', '**/androidTest/**', '**/commonTest/**', '**/jvmTest/**', '**/jsTest/**', '**/iosTest/**' ] + thresholdInFiles: 50 + thresholdInClasses: 50 + thresholdInInterfaces: 50 + thresholdInObjects: 50 + thresholdInEnums: 50 + ignoreDeprecated: false + ignorePrivate: false + ignoreOverridden: false + LongParameterList: + active: false + NestedBlockDepth: + active: true + threshold: 6 + LongMethod: + active: true + threshold: 75 + ComplexMethod: + active: true + threshold: 25 + +exceptions: + active: true + SwallowedException: + active: false + TooGenericExceptionCaught: + active: false + +style: + active: true + SerialVersionUIDInSerializableClass: + active: false + MagicNumber: + active: false + ReturnCount: + active: false + FunctionOnlyReturningConstant: + active: false + MaxLineLength: + active: true + maxLineLength: 128 diff --git a/docs/changelog.md b/docs/changelog.md new file mode 100644 index 00000000..aa9cc0bd --- /dev/null +++ b/docs/changelog.md @@ -0,0 +1,5 @@ +# Changelog + +## 1.0.0-beta1 +* Hello World :) + diff --git a/docs/contribution.md b/docs/contribution.md new file mode 100644 index 00000000..d371ef4c --- /dev/null +++ b/docs/contribution.md @@ -0,0 +1,2 @@ +# Contribution +Gradle Analytics Plugin is Open Source and accepts contributions of new members, feel free to submit your PR and stay connected on the PR page and I will be glad to finalize and merge your PR in the upcoming release. diff --git a/docs/getting-report.md b/docs/getting-report.md new file mode 100644 index 00000000..50dfa82f --- /dev/null +++ b/docs/getting-report.md @@ -0,0 +1,29 @@ +# Getting Report +Gradle Analytics Plugin uses daily basis data to generate reports. So you could use the below instructions to generate your build analysis. + +Execute Gradle Task
+```Gradle +./gradlew reportAnalytics --task="REQUESTED_TASK" --branch="BRANCH_NAME" --period="can be like today, s:yyyy/MM/dd,e:yyyy/MM/dd, 1y, 4m, 38d, 3m 06d" +``` + +
+!!! Note "" + + --period Examples
+ + - today - Generates report only for the current day. + - 1d - Generates report from 1 day ago till now. + - 1m 3d - Generates report from 1 month and 3 days ago till now. + - 1y - Generates report from 1 year ago till now. + - s:2022/03/24,e:2022/04/25 - Generates report from `2022/03/24` till `2022/04/25`. + + + Plugin only holds the metrics results in the caching database up to one year. + +
+ +To understand the metrics and report that plugin provides, It is required to understand Gradle basics and how this build +system works.
https://docs.gradle.org/current/userguide/what_is_gradle.html +
+ +
diff --git a/docs/img/build-status.png b/docs/img/build-status.png new file mode 100644 index 00000000..3d760e76 Binary files /dev/null and b/docs/img/build-status.png differ diff --git a/docs/img/cache-hit.png b/docs/img/cache-hit.png new file mode 100644 index 00000000..61c699f7 Binary files /dev/null and b/docs/img/cache-hit.png differ diff --git a/docs/img/configuration-process.png b/docs/img/configuration-process.png new file mode 100644 index 00000000..0a86368c Binary files /dev/null and b/docs/img/configuration-process.png differ diff --git a/docs/img/dependency-details.png b/docs/img/dependency-details.png new file mode 100644 index 00000000..0501b120 Binary files /dev/null and b/docs/img/dependency-details.png differ diff --git a/docs/img/dependency-resolve-process.png b/docs/img/dependency-resolve-process.png new file mode 100644 index 00000000..47245f1f Binary files /dev/null and b/docs/img/dependency-resolve-process.png differ diff --git a/docs/img/execution-process.png b/docs/img/execution-process.png new file mode 100644 index 00000000..2b24c3b3 Binary files /dev/null and b/docs/img/execution-process.png differ diff --git a/docs/img/initialization-process.png b/docs/img/initialization-process.png new file mode 100644 index 00000000..2576869d Binary files /dev/null and b/docs/img/initialization-process.png differ diff --git a/docs/img/module-execution-process-1.png b/docs/img/module-execution-process-1.png new file mode 100644 index 00000000..7e4415d6 Binary files /dev/null and b/docs/img/module-execution-process-1.png differ diff --git a/docs/img/module-execution-process-2.png b/docs/img/module-execution-process-2.png new file mode 100644 index 00000000..d1870b84 Binary files /dev/null and b/docs/img/module-execution-process-2.png differ diff --git a/docs/img/modules-build-heatmap.png b/docs/img/modules-build-heatmap.png new file mode 100644 index 00000000..b50f5f9c Binary files /dev/null and b/docs/img/modules-build-heatmap.png differ diff --git a/docs/img/modules-crash-count.png b/docs/img/modules-crash-count.png new file mode 100644 index 00000000..082972c5 Binary files /dev/null and b/docs/img/modules-crash-count.png differ diff --git a/docs/img/modules-dependency-graph.png b/docs/img/modules-dependency-graph.png new file mode 100644 index 00000000..ae50c4cf Binary files /dev/null and b/docs/img/modules-dependency-graph.png differ diff --git a/docs/img/modules-execution-timeline.png b/docs/img/modules-execution-timeline.png new file mode 100644 index 00000000..94fcf836 Binary files /dev/null and b/docs/img/modules-execution-timeline.png differ diff --git a/docs/img/modules-method-count.png b/docs/img/modules-method-count.png new file mode 100644 index 00000000..15582c84 Binary files /dev/null and b/docs/img/modules-method-count.png differ diff --git a/docs/img/modules-source-count.png b/docs/img/modules-source-count.png new file mode 100644 index 00000000..da2439ff Binary files /dev/null and b/docs/img/modules-source-count.png differ diff --git a/docs/img/modules-source-size.png b/docs/img/modules-source-size.png new file mode 100644 index 00000000..6c46c95d Binary files /dev/null and b/docs/img/modules-source-size.png differ diff --git a/docs/img/non-cacheable-tasks.png b/docs/img/non-cacheable-tasks.png new file mode 100644 index 00000000..7786ca28 Binary files /dev/null and b/docs/img/non-cacheable-tasks.png differ diff --git a/docs/img/overall-build-process.png b/docs/img/overall-build-process.png new file mode 100644 index 00000000..0f6a4784 Binary files /dev/null and b/docs/img/overall-build-process.png differ diff --git a/docs/img/parallel-execution-rate.png b/docs/img/parallel-execution-rate.png new file mode 100644 index 00000000..941d1880 Binary files /dev/null and b/docs/img/parallel-execution-rate.png differ diff --git a/docs/img/plugin-logo.png b/docs/img/plugin-logo.png new file mode 100755 index 00000000..404a4c24 Binary files /dev/null and b/docs/img/plugin-logo.png differ diff --git a/docs/img/successful-build-rate.png b/docs/img/successful-build-rate.png new file mode 100644 index 00000000..5a8a3d47 Binary files /dev/null and b/docs/img/successful-build-rate.png differ diff --git a/docs/index.md b/docs/index.md new file mode 100644 index 00000000..bec0d2dc --- /dev/null +++ b/docs/index.md @@ -0,0 +1,150 @@ +plugin logo + +# Gradle Analytics Plugin +[![CircleCI](https://circleci.com/gh/janbarari/gradle-analytics-plugin/tree/develop.svg?style=svg)](https://circleci.com/gh/janbarari/gradle-analytics-plugin/tree/develop) +[![codecov](https://codecov.io/gh/janbarari/gradle-analytics-plugin/branch/develop/graph/badge.svg)](https://codecov.io/gh/janbarari/gradle-analytics-plugin) +[![Quality Gate Status](https://sonarcloud.io/api/project_badges/measure?project=janbarari_gradle-analytics-plugin&metric=alert_status)](https://sonarcloud.io/dashboard?id=janbarari_gradle-analytics-plugin) + +A free Gradle plugin to analyze your project builds. It provides unique visual and text metrics in HTML format. + +
+Below you can see the metrics provided by the plugin ๐Ÿ‘‡ + +!!! Note "" + + To understand the metrics and report that plugin provides, It is required to understand Gradle basics and how this build + system works.
https://docs.gradle.org/current/userguide/what_is_gradle.html + +## Build Status +An overview of metrics results of the `requested task` in the build processes over the aforementioned period. + +![](img/build-status.png) + +
+## Initialization Process +Gradle supports single and multi-project builds. During the initialization process, Gradle determines which projects are going to take part in the build, and creates a Project instance for each of these projects. By adding more projects or modules the process will take longer. + +It denotes the average initialization process time over the report period. + +![](img/initialization-process.png) + +
+## Configuration Process +Constructs and configures the task graph for the build and then determines which tasks need to run and in which order, based on the task the user wants to run. Be careful about the tasks you register to the project and try to make them cacheable. + +It shows the average configuration process time over the report period. + +![](img/configuration-process.png) + +
+## Dependency Resolve Process +Downloading and resolving the project's dependencies is one of the configuration process' stages. If the project has multiple third-party libraries dependencies, make sure that you have a good network speed. + +It represents the download(Dependency Resolve) process average duration during the report period. + +![](img/dependency-resolve-process.png) + +
+## Execution Process +Runs the selected tasks based on `requested tasks` task tree. Gradle executes `requested task` according to the dependency order. + +It represents the Execution Process average duration during the report period. + +![](img/execution-process.png) + +
+## Modules Execution Process +It represents the (Median) process execution time of each module over the report period. + +![](img/module-execution-process-1.png) +![](img/module-execution-process-2.png) + +
+## Overall Build Process +It represents the average duration of overall build process. + +![](img/overall-build-process.png) + +
+## Modules Source Count +It represents the project and its modules source file count. (files with extension of kt, java). + +![](img/modules-source-count.png) + +
+## Modules Source Size +It represents the project and its modules source file size. + +![](img/modules-source-size.png) + +
+## Modules Method Count +It represents the project and its modules source method count. + +![](img/modules-method-count.png) + +
+## Cache Hit +Gradle creates a cache for the executed task to be reused in the next incremental builds, the more cached tasks lead to faster builds. + +It represents the project and modules tasks average cache hit rate (tasks run with FROM_CACHE or UP_TO_DATE). + +![](img/cache-hit.png) + +
+## Successful Build Rate +It represents the successful build rate of the `requested task` during the report period. + +![](img/successful-build-rate.png) + +
+## Modules Crash Count +It represents how many build failures happened during the `requested task` execution caused by project modules during the report period. + +![](img/modules-crash-count.png) + +
+## Parallel Execution Rate +Gradle uses CPU cores to execute more tasks simultaneously, leading to a faster build. + +It represents a rate that how much time was saved in the execution of the build process with parallel execution versus real elapsed time. + +![](img/parallel-execution-rate.png) + +
+## Modules Dependency Graph +It represents the project module's dependency graph and their connection types. + +Modules have colors that warm colors have represents more dependent modules, and It is recommended to have fewer warm color modules because by applying any change in these modules, all other dependent modules need to rebuild and this cost more time and resources from your machines and put the builds in queue. + +![](img/modules-dependency-graph.png) + +
+## Modules Execution Timeline +It represents the latest modules execution process timeline graph. + +![](img/modules-execution-timeline.png) + +
+## Modules Build Heatmap +This plugin uses `Modules Cache Usage` and `Modules Dependency Graph` to generate this metric that shows how many times a module was built during the report period. + +Each bar has the name of the module and the number of dependent modules, smaller warm bars lead to faster builds as those modules with warm colors have more dependent modules. + +In addition, it helps to modify the modular structure by tracing the graph and finding the cause to avoid rebuilding the modules that are most shared with others. + +![](img/modules-build-heatmap.png) + +
+## Dependency Details +It represents the project(including all modules) dependencies with their sizes. + +![](img/dependency-details.png) + +
+## Non-cacheable Tasks +These tasks are executed in the `requested task` tree without being cached. Try to avoid creating tasks that are not cacheable. Track this chart and detect tasks that are time-consuming. + +![](img/non-cacheable-tasks.png) + +


\ No newline at end of file diff --git a/docs/license.md b/docs/license.md new file mode 100644 index 00000000..fe3467d9 --- /dev/null +++ b/docs/license.md @@ -0,0 +1,21 @@ +# License +MIT License
+Copyright © 2022 Mehdi Janbarari (@janbarari) + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/docs/privacy-policy.md b/docs/privacy-policy.md new file mode 100644 index 00000000..c1befdbe --- /dev/null +++ b/docs/privacy-policy.md @@ -0,0 +1,2 @@ +# Privacy Policy +Gradle Analytics Plugin does NOT send any collected data to any server apart from the user's own database config. It uses offline HTML renders to provide the analytics, So there is no need to upload any data from the database. \ No newline at end of file diff --git a/docs/report-bug.md b/docs/report-bug.md new file mode 100644 index 00000000..7e602a55 --- /dev/null +++ b/docs/report-bug.md @@ -0,0 +1,4 @@ +# Report Bug +I appreciate reporting the bugs and crashes in the plugin. In order to submit please use either below ways: + +[Github Link](https://github.com/janbarari/gradle-analytics-plugin/issues/new?title=RB:) or [Send Me Email](mailto:mehdi.janbarari@outlook.com?subject=[GAP]%20Report%20Bug) diff --git a/docs/request-feature.md b/docs/request-feature.md new file mode 100644 index 00000000..4346242d --- /dev/null +++ b/docs/request-feature.md @@ -0,0 +1,4 @@ +# Request Feature +I thankfully appreciate to send me your ideas about new features. In order to submit your ideas please use the below ways: + +[Github Link](https://github.com/janbarari/gradle-analytics-plugin/issues/new?title=RF:) or [Send Me Email](mailto:mehdi.janbarari@outlook.com?subject=[GAP]%20Request%20Feature) diff --git a/docs/setup.md b/docs/setup.md new file mode 100644 index 00000000..a42f1bed --- /dev/null +++ b/docs/setup.md @@ -0,0 +1,107 @@ +# Setup +To set up the plugin in your project, follow the below instructions: + +### Step 1 +Make sure your project uses Git VCS. +!!! Note "" + + Gradle Analytics Plugin uses Git terminal to get the branch names and latest HEAD commit hash. + so It is required for your project to use Git VCS. + +

+ +### Step 2 +Apply the Gradle Plugin to the root of your project. +=== "Kotlin" + ``` kotlin + plugins { + id("io.github.janbarari.gradle-analytics-plugin") version "1.0.0-beta1" + } + ``` +=== "Groovy" + ``` groovy + plugins { + id "io.github.janbarari.gradle-analytics-plugin" version "1.0.0-beta1" + } + ``` +[For legacy plugin application, see the Gradle Plugin Portal.](https://plugins.gradle.org/plugin/io.github.janbarari.gradle-analytics-plugin) +

+ +### Step 3 +Add plugin configuration in the root of your project. + +=== "Kotlin" + ``` kotlin + gradleAnalyticsPlugin { + database { + local = sqlite { + path = "DATABASE_PATH" + name = "DATABASE_NAME" + user = "DATABASE_USER" + password = "DATABASE_PASSWORD" + } + ci = mysql { + host = "MYSQL_DATABASE_HOST", + name = "MYSQL_DATABASE_NAME", + user = "MYSQL_DATABASE_USER", + password = "MYSQL_DATABASE_PASSWORD" + port = MYSQL_DATABASE_PORT // Default is 3306 + } + } + + trackingTasks = listOf( + // Add your requested tasks to be analyzed, Example: + ":app:assembleDebug" + ) + + trackingBranches = listOf( + // requested tasks only analyzed in the branches you add here, Example: + "master", + "develop" + ) + + outputPath = "OUTPUT_REPORT_PATH" + } + ``` +=== "Groovy" + ``` groovy + gradleAnalyticsPlugin { + database { + local = sqlite { + path = "DATABASE_PATH" + name = "DATABASE_NAME" + user = "DATABASE_USER" + password = "DATABASE_PASSWORD" + } + ci = mysql { + host = "MYSQL_DATABASE_HOST", + name = "MYSQL_DATABASE_NAME", + user = "MYSQL_DATABASE_USER", + password = "MYSQL_DATABASE_PASSWORD" + port = MYSQL_DATABASE_PORT // Default is 3306 + } + } + + trackingTasks = [ + // Add your requested tasks to be analyzed, Example: + ":app:assembleDebug" + ] + + trackingBranches = [ + // requested tasks only analyzed in the branches you add here, Example: + "master", + "develop" + ] + + outputPath = "OUTPUT_REPORT_PATH" + } + ``` +
+Important Notes
+ +- If you don't have a sqlite database, the plugin will create one automatically. You only need to the `name` and `path` for it. +- You can choose both `sqlite / mysql` for `local` or `ci`. +- You can skip `local` or `ci` database if you don't need analytics on each of them. +- If you use `ci` make sure the `CI=true` environment variable exists in your CI system environments. + +
diff --git a/docs/special-thanks.md b/docs/special-thanks.md new file mode 100644 index 00000000..e10da739 --- /dev/null +++ b/docs/special-thanks.md @@ -0,0 +1,7 @@ +# Special Thanks +I am very proud to give special thanks to those around me in making and presenting this open source project. + +I am grateful to Yazdan Sharifi(@yazdan-sharifi2020), who helped write the plugin report renders. + +Best regards,
+Mehdi diff --git a/docs/support.md b/docs/support.md new file mode 100644 index 00000000..99809751 --- /dev/null +++ b/docs/support.md @@ -0,0 +1,15 @@ +# Support +Gradle Analytics Plugin is Open Source and completely free, I spent more than a year analyzing build bottlenecks and finding a way to track them, and finally implemented it into the plugin. + +Of course, this project will be continued as a free open-source project. But to give me some energy, you can use either below ways to support me. + +Send me Crypto
+Bitcoin: bc1q8ecepuax6je6s70j4l76nr978w34msc929sfuf +
+Tether(ERC20): 0x2f23a4F4502C5e699777f3F89da6Ba965C7800FF + +Don't forget to tap the โญ button on the project [Github link](https://github.com/janbarari/gradle-analytics-plugin). + + +Thanks,
+Mehdi \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 7fc6f1ff..3a600d2f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1,13 @@ kotlin.code.style=official +org.gradle.warning.mode=all + +pluginWebsite = https://github.com/janbarari/gradle-analytics-plugin +pluginVcsUrl = https://github.com/janbarari/gradle-analytics-plugin.git +pluginId = io.github.janbarari.gradle-analytics-plugin +pluginDisplayName = gradle-analytics-plugin +pluginDescription = A free Gradle plugin to analyze your project builds. It provides unique visual and text metrics in HTML format. +pluginTags = kotlin,plugin,analytics,analysis,gradle,gradle-plugin,gradle-plugins,build,metrics,performance,tracker +pluginImplementationClass = io.github.janbarari.gradle.analytics.GradleAnalyticsPlugin +pluginDeclarationName = gradleAnalyticsPlugin +pluginGroupPackageName = io.github.janbarari +pluginVersion = 1.0.0-beta1 diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml new file mode 100644 index 00000000..5010b008 --- /dev/null +++ b/gradle/libs.versions.toml @@ -0,0 +1,25 @@ +[versions] +kotlin = "1.6.20" +detekt = "1.20.0-RC2" +sqlite_driver = "3.36.0.3" +mysql_driver = "8.0.29" +jetbrains_exposed = "0.38.1" +moshi = "1.13.0" +commons-io = "2.11.0" +mockk = "1.12.4" +coroutines = "1.6.3" + +[libraries] +sqlite_driver = { module = "org.xerial:sqlite-jdbc", version.ref = "sqlite_driver" } +mysql_driver = { module = "mysql:mysql-connector-java", version.ref = "mysql_driver" } +jetbrains_exposed_core = { module = "org.jetbrains.exposed:exposed-core", version.ref = "jetbrains_exposed" } +jetbrains_exposed_jdbc = { module = "org.jetbrains.exposed:exposed-jdbc", version.ref = "jetbrains_exposed" } +moshi = { module = "com.squareup.moshi:moshi-kotlin", version.ref = "moshi" } +moshi-codegen = { module = "com.squareup.moshi:moshi-kotlin-codegen", version.ref = "moshi" } +commons-io = { module = "commons-io:commons-io", version.ref = "commons-io" } +mockk = { module = "io.mockk:mockk", version.ref = "mockk" } +coroutines = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" } +coroutines-test = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-test", version.ref = "coroutines" } + +[plugins] +detekt = { id = "io.gitlab.arturbosch.detekt", version.ref = "detekt" } diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar old mode 100644 new mode 100755 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 05679dc3..92f06b50 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-7.1.1-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip zipStoreBase=GRADLE_USER_HOME zipStorePath=wrapper/dists diff --git a/mkdocs.yml b/mkdocs.yml new file mode 100644 index 00000000..507bb448 --- /dev/null +++ b/mkdocs.yml @@ -0,0 +1,46 @@ +site_name: Gradle Analytics Plugin +site_url: https://janbarari.github.io/gradle-analytics-plugin/ +repo_name: Gradle Analytics Plugin +repo_url: https://github.com/janbarari/gradle-analytics-plugin/ +site_description: A free Gradle plugin to analyze your project builds. It provides unique visual and text metrics in the HTML format. +site_author: Mehdi Janbarari + +nav: + - Overview: index.md + - Setup: setup.md + - Getting Report: getting-report.md + - Changelog: changelog.md + - Support: support.md + - Contribution: contribution.md + - Privacy & Policy: privacy-policy.md + - Request Feature: request-feature.md + - Report Bug: report-bug.md + - Special Thanks: special-thanks.md + - License: license.md + +theme: + name: material + palette: + primary: white + logo: img/plugin-logo.png + favicon: img/plugin-logo.png + features: + - header.autohide + +markdown_extensions: + - codehilite + - admonition + - pymdownx.superfences + - pymdownx.inlinehilite + - pymdownx.tabbed: + alternate_style: true + +copyright: Made with ๐Ÿงก for everyone | Copyright © 2022 + +extra: + generator: false + social: + - icon: fontawesome/brands/twitter + link: https://twitter.com/medicodroid + - icon: fontawesome/brands/github + link: https://github.com/janbarari \ No newline at end of file diff --git a/plugin-logo.png b/plugin-logo.png old mode 100644 new mode 100755 diff --git a/privacy-policy.md b/privacy-policy.md new file mode 100644 index 00000000..c23fb6a3 --- /dev/null +++ b/privacy-policy.md @@ -0,0 +1,4 @@ +# Privacy policy + +This plugin does NOT send any collected data to any server apart from the user's own database config. This plugin uses +offline HTML renders to provide the analytics, So there is no need to upload any data from the database. \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index a24240d5..4190815c 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -1,2 +1 @@ rootProject.name = "gradle-analytics-plugin" - diff --git a/src/main/kotlin/Mehdi.kt b/src/main/kotlin/Mehdi.kt deleted file mode 100644 index 51212fda..00000000 --- a/src/main/kotlin/Mehdi.kt +++ /dev/null @@ -1,5 +0,0 @@ -class Mehdi { - fun getMehdi(): String { - return "Mehdi" - } -} \ No newline at end of file diff --git a/src/main/kotlin/io/github/janbarari/gradle/ExcludeJacocoGenerated.kt b/src/main/kotlin/io/github/janbarari/gradle/ExcludeJacocoGenerated.kt new file mode 100644 index 00000000..1e537ec9 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/ExcludeJacocoGenerated.kt @@ -0,0 +1,40 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle + +/** + * In order to have reliable code coverage sometimes it's needed to exclude some + * functions or classes that their lifecycle is based on the framework and not + * in developer hand. + * + * Since Jacoco 0.8.2, developers can exclude classes and methods by annotating + * them with a custom annotation with the following properties: + * + * 1- The name of the annotation should include the 'Generated' keyword. + * 2- The retention policy of annotation should be runtime or class. + */ +@MustBeDocumented +@Retention(AnnotationRetention.RUNTIME) +@Target(AnnotationTarget.TYPE, AnnotationTarget.FUNCTION, + AnnotationTarget.CLASS, AnnotationTarget.FIELD) +annotation class ExcludeJacocoGenerated diff --git a/src/main/kotlin/io/github/janbarari/gradle/IncompatibleVersionException.kt b/src/main/kotlin/io/github/janbarari/gradle/IncompatibleVersionException.kt new file mode 100644 index 00000000..4212cc51 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/IncompatibleVersionException.kt @@ -0,0 +1,34 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle + +@ExcludeJacocoGenerated +class IncompatibleVersionException( + private val title: String, + private val minimumRequiredVersion: String +) : Throwable() { + + override val message: String + get() = "$title is compatible with Gradle version $minimumRequiredVersion and above." + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/NotAccessibleGitTerminalException.kt b/src/main/kotlin/io/github/janbarari/gradle/NotAccessibleGitTerminalException.kt new file mode 100644 index 00000000..ce6f9211 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/NotAccessibleGitTerminalException.kt @@ -0,0 +1,28 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle + +@ExcludeJacocoGenerated +class NotAccessibleGitTerminalException(title: String): Throwable( + message = "$title works only on projects which use Git." +) diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/DatabaseConfig.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/DatabaseConfig.kt new file mode 100644 index 00000000..c33d301d --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/DatabaseConfig.kt @@ -0,0 +1,93 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics + +import groovy.lang.Closure +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.database.DatabaseConnection +import io.github.janbarari.gradle.analytics.database.MySqlDatabaseConnection +import io.github.janbarari.gradle.analytics.database.SqliteDatabaseConnection + +@ExcludeJacocoGenerated +class DatabaseConfig @JvmOverloads constructor() : java.io.Serializable { + + /** + * It is the database config of user local machine, this variable should be initialized + * with one of the database configs that the plugin supports. + */ + var local: DatabaseConnection? = null + + /** + * It is the database config of CI. Should be initialized + * with one of the database configs that the plugin supports. Keep in mind that + * if this variable is initialized, then the plugin only uses this database + * config on CI. + * + * Note: please make sure the CI has an environment variable named `CI`. + */ + var ci: DatabaseConnection? = null + + /** + * Factory method for create a new instance + * of [io.github.janbarari.gradle.analytics.data.database.config.MySqlDatabaseConfig]. + */ + fun mysql(block: MySqlDatabaseConnection.() -> Unit): MySqlDatabaseConnection { + return MySqlDatabaseConnection { + also(block) + } + } + + /** + * Factory method for create a new instance + * of [io.github.janbarari.gradle.analytics.data.database.config.MySqlDatabaseConfig]. + */ + fun mysql(closure: Closure<*>): MySqlDatabaseConnection { + val temp = MySqlDatabaseConnection { } + closure.delegate = temp + closure.call() + return temp + } + + /** + * Factory method for create a new instance + * of [io.github.janbarari.gradle.analytics.data.database.config.SqliteDatabaseConfig]. + */ + fun sqlite(block: SqliteDatabaseConnection.() -> Unit): SqliteDatabaseConnection { + return SqliteDatabaseConnection { + also(block) + } + } + + /** + * Factory method for create a new instance + * of [io.github.janbarari.gradle.analytics.data.database.config.SqliteDatabaseConfig]. + */ + fun sqlite(closure: Closure<*>): SqliteDatabaseConnection { + val temp = SqliteDatabaseConnection { } + closure.delegate = temp + closure.call() + return temp + } + + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/GradleAnalyticsPlugin.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/GradleAnalyticsPlugin.kt new file mode 100644 index 00000000..4baa5551 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/GradleAnalyticsPlugin.kt @@ -0,0 +1,108 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics + +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.IncompatibleVersionException +import io.github.janbarari.gradle.NotAccessibleGitTerminalException +import io.github.janbarari.gradle.analytics.reporttask.ReportAnalyticsTask +import io.github.janbarari.gradle.analytics.scanner.ScannerUtils +import io.github.janbarari.gradle.utils.GitUtils +import io.github.janbarari.gradle.utils.ProjectUtils +import org.gradle.api.Plugin +import org.gradle.api.Project +import org.gradle.build.event.BuildEventsListenerRegistry +import javax.inject.Inject + +/** + * A free Gradle plugin for analytics of your projects. Provides unique visual and + * text metrics in HTML format. + */ +@ExcludeJacocoGenerated +class GradleAnalyticsPlugin @Inject constructor( + private val registry: BuildEventsListenerRegistry +) : Plugin { + + companion object { + const val PLUGIN_NAME = "gradleAnalyticsPlugin" + const val PLUGIN_VERSION = "1.0.0-beta1" + } + + /** + * Gradle will invoke this function once the plugin is added into the project build script. + */ + override fun apply(project: Project) { + ensureProjectGradleCompatible() + ensureGitTerminalAccessible() + val config = setupPluginConfig(project) + registerTasks(config) + ScannerUtils.setupScannerServices(config, registry) + } + + /** + * The plugin is compatible with Gradle version 6.1 and above, This function ensures + * the plugin Gradle version is compatible with the user project version. + * + * @throws io.github.janbarari.gradle.IncompatibleVersionException when the Gradle version is not compatible. + */ + @kotlin.jvm.Throws(IncompatibleVersionException::class) + private fun ensureProjectGradleCompatible() { + val requiredGradleVersion = ProjectUtils.GradleVersions.V6_1 + if (!ProjectUtils.isCompatibleWith(requiredGradleVersion)) { + throw IncompatibleVersionException(PLUGIN_NAME, requiredGradleVersion.versionNumber) + } + } + + /** + * The plugin only works on projects which use Git. This function ensures the Git terminal accessible in project directory. + */ + @kotlin.jvm.Throws(NotAccessibleGitTerminalException::class) + private fun ensureGitTerminalAccessible() { + try { + GitUtils.currentBranch() + } catch (e: Throwable) { + throw NotAccessibleGitTerminalException(PLUGIN_NAME) + } + } + + /** + * Setups plugin config. + * + * Note: extension will be initialized after projectsEvaluated(configuration process). + */ + private fun setupPluginConfig(project: Project): GradleAnalyticsPluginConfig { + return project.extensions.create( + PLUGIN_NAME, + GradleAnalyticsPluginConfig::class.java, + project + ) + } + + /** + * Registers the plugin custom tasks. + */ + private fun registerTasks(config: GradleAnalyticsPluginConfig) { + ReportAnalyticsTask.register(config) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/GradleAnalyticsPluginConfig.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/GradleAnalyticsPluginConfig.kt new file mode 100644 index 00000000..b31878ee --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/GradleAnalyticsPluginConfig.kt @@ -0,0 +1,56 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics + +import groovy.lang.Closure +import io.github.janbarari.gradle.analytics.database.DatabaseConnection +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import org.gradle.api.NamedDomainObjectContainer +import org.gradle.api.Project + +/** + * Configuration options for the [io.github.janbarari.gradle.analytics.GradleAnalyticsPlugin]. + */ +@ExcludeJacocoGenerated +open class GradleAnalyticsPluginConfig(val project: Project) { + + private var databaseConfig: DatabaseConfig = DatabaseConfig() + + var trackingTasks: List = listOf() + + var trackingBranches: List = listOf() + + var outputPath: String = project.rootProject.buildDir.absolutePath + + fun database(closure: Closure<*>) { + closure.delegate = databaseConfig + closure.call() + } + + fun database(block: DatabaseConfig.() -> Unit) { + databaseConfig = DatabaseConfig().also(block) + } + + fun getDatabaseConfig(): DatabaseConfig = databaseConfig + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/data/DatabaseRepositoryImp.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/data/DatabaseRepositoryImp.kt new file mode 100644 index 00000000..4bcdb2ce --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/data/DatabaseRepositoryImp.kt @@ -0,0 +1,254 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.data + +import com.squareup.moshi.Moshi +import io.github.janbarari.gradle.analytics.database.Database +import io.github.janbarari.gradle.analytics.database.ResetAutoIncremental +import io.github.janbarari.gradle.analytics.database.table.MetricTable +import io.github.janbarari.gradle.analytics.database.table.SingleMetricTable +import io.github.janbarari.gradle.analytics.database.table.TemporaryMetricTable +import io.github.janbarari.gradle.analytics.database.table.TemporaryMetricTable.value +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetricJsonAdapter +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.extension.separateElementsWithSpace +import io.github.janbarari.gradle.utils.DateTimeUtils +import org.jetbrains.exposed.sql.ResultRow +import org.jetbrains.exposed.sql.SortOrder +import org.jetbrains.exposed.sql.and +import org.jetbrains.exposed.sql.deleteAll +import org.jetbrains.exposed.sql.deleteWhere +import org.jetbrains.exposed.sql.insert +import org.jetbrains.exposed.sql.select +import org.jetbrains.exposed.sql.update + +class DatabaseRepositoryImp( + private val db: Database, + private val branch: String, + private val requestedTasks: String, + private val moshi: Moshi +) : DatabaseRepository { + + private var jsonAdapter: BuildMetricJsonAdapter = BuildMetricJsonAdapter(moshi) + + override fun saveNewMetric(metric: BuildMetric): Long { + return db.transaction { + val queryResult = MetricTable.insert { + it[createdAt] = metric.createdAt + it[value] = jsonAdapter.toJson(metric) + it[branch] = metric.branch + it[requestedTasks] = metric.requestedTasks.separateElementsWithSpace() + } + dropOutdatedMetrics() + return@transaction queryResult[MetricTable.number] + } + } + + override fun saveTemporaryMetric(metric: BuildMetric): Long { + return db.transaction { + val queryResult = TemporaryMetricTable.insert { + it[createdAt] = metric.createdAt + it[value] = jsonAdapter.toJson(metric) + it[branch] = metric.branch + it[requestedTasks] = metric.requestedTasks.separateElementsWithSpace() + } + return@transaction queryResult[TemporaryMetricTable.number] + } + } + + override fun getDayMetric(): Pair { + return db.transaction { + val queryResult = MetricTable.select { + (MetricTable.createdAt greaterEq DateTimeUtils.getDayStartMs()) and + (MetricTable.createdAt less DateTimeUtils.getDayEndMs()) and + (MetricTable.branch eq branch) and + (MetricTable.requestedTasks eq requestedTasks) + }.single() + return@transaction Pair( + jsonAdapter.fromJson(queryResult[MetricTable.value])!!, queryResult[MetricTable.number] + ) + } + } + + override fun isDayMetricExists(): Boolean { + return db.transaction { + val queryResult = MetricTable.select { + (MetricTable.createdAt greaterEq DateTimeUtils.getDayStartMs()) and + (MetricTable.createdAt less DateTimeUtils.getDayEndMs()) and + (MetricTable.branch eq branch) and + (MetricTable.requestedTasks eq requestedTasks) + } + return@transaction queryResult.count() > 0 + } + } + + override fun getMetrics(period: Pair): List { + return db.transaction { + val result = arrayListOf() + MetricTable.select { + (MetricTable.createdAt greaterEq period.first) and + (MetricTable.createdAt lessEq period.second) and + (MetricTable.branch eq branch) and + (MetricTable.requestedTasks eq requestedTasks) + }.orderBy(MetricTable.number, SortOrder.ASC).forEach { + result.add( + jsonAdapter.fromJson(it[MetricTable.value])!! + ) + } + return@transaction result + } + } + + override fun updateDayMetric(number: Long, metric: BuildMetric): Boolean { + return db.transaction { + val queryResult = MetricTable.update({ + MetricTable.number eq number + }) { + it[value] = jsonAdapter.toJson(metric) + it[createdAt] = System.currentTimeMillis() + } + return@transaction queryResult == 1 + } + } + + override fun dropOutdatedTemporaryMetrics(): Boolean { + return db.transaction { + val dayMetrics = mutableListOf() + TemporaryMetricTable.select { + TemporaryMetricTable.createdAt greaterEq DateTimeUtils.getDayStartMs() + }.forEach { + dayMetrics.add(it) + } + TemporaryMetricTable.deleteAll() + ResetAutoIncremental.getQuery("temporary_metric")?.let { + exec(it) + } + if (dayMetrics.isNotEmpty()) { + dayMetrics.forEach { dayMetric -> + TemporaryMetricTable.insert { + it[createdAt] = dayMetric[createdAt] + it[value] = dayMetric[value] + it[branch] = dayMetric[branch] + it[requestedTasks] = dayMetric[requestedTasks] + } + } + } + return@transaction true + } + } + + override fun dropOutdatedMetrics() { + return db.transaction { + MetricTable.deleteWhere { + // Delete all metrics that are created more than 1 year ago. + MetricTable.createdAt less (System.currentTimeMillis() - 32_140_800_000L) + } + } + } + + override fun dropMetrics(): Boolean { + return db.transaction { + MetricTable.deleteAll() + ResetAutoIncremental.getQuery("metric")?.let { + exec(it) + } + return@transaction true + } + } + + override fun getTemporaryMetrics(): List { + return db.transaction { + if (dropOutdatedTemporaryMetrics()) { + val metrics = arrayListOf() + val queryResult = TemporaryMetricTable.select { + (TemporaryMetricTable.branch eq branch) and + (TemporaryMetricTable.requestedTasks eq requestedTasks) + } + queryResult.toList().forEach { + jsonAdapter.fromJson(it[value])?.let { metric -> + metrics.add(metric) + } + } + return@transaction metrics + } else { + return@transaction emptyList() + } + } + } + + override fun getSingleMetric(key: String, branch: String): String? { + return db.transaction { + val queryResult = SingleMetricTable.select { + (SingleMetricTable.key eq key) and (SingleMetricTable.branch eq branch) + } + if (queryResult.count() > 0) { + return@transaction queryResult.single()[SingleMetricTable.value] + } + return@transaction null + } + } + + override fun updateSingleMetric(key: String, branch: String, value: String): Boolean { + return db.transaction { + val queryResult = SingleMetricTable.update( + { + (SingleMetricTable.key eq key) and (SingleMetricTable.branch eq branch) + } + ) { + it[SingleMetricTable.value] = value + it[createdAt] = System.currentTimeMillis() + } + return@transaction queryResult == 1 + } + } + + override fun saveSingleMetric(key: String, branch: String, value: String): Boolean { + return db.transaction { + SingleMetricTable.insert { + it[SingleMetricTable.key] = key + it[createdAt] = System.currentTimeMillis() + it[SingleMetricTable.value] = value + it[SingleMetricTable.branch] = branch + } + return@transaction true + } + } + + override fun dropSingleMetric(key: String, branch: String): Boolean { + return db.transaction { + SingleMetricTable.deleteWhere { + (SingleMetricTable.key eq key) and (SingleMetricTable.branch eq branch) + } + return@transaction true + } + } + + override fun dropSingleMetrics(): Boolean { + return db.transaction { + SingleMetricTable.deleteAll() + return@transaction true + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/database/Database.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/database/Database.kt new file mode 100644 index 00000000..26658386 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/database/Database.kt @@ -0,0 +1,120 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.database + +import io.github.janbarari.gradle.analytics.database.table.MetricTable +import io.github.janbarari.gradle.analytics.database.table.TemporaryMetricTable +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.DatabaseConfig +import io.github.janbarari.gradle.analytics.database.table.SingleMetricTable +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.toRealPath +import io.github.janbarari.gradle.extension.whenNotNull +import org.jetbrains.exposed.sql.Table +import org.jetbrains.exposed.sql.Transaction +import org.jetbrains.exposed.sql.SchemaUtils +import org.jetbrains.exposed.sql.transactions.transaction +import org.jetbrains.exposed.sql.Database +import org.jetbrains.exposed.sql.transactions.transactionManager + +class Database( + config: DatabaseConfig, + private var isCI: Boolean +) { + + companion object { + const val DEFAULT_VARCHAR_LENGTH = 256 + } + + private lateinit var _database: Database + private var databaseConfig: DatabaseConnection? = null + + init { + connect(config) + } + + private fun connect(config: DatabaseConfig) { + databaseConfig = config.local + + if (isCI && config.ci.isNotNull()) { + databaseConfig = config.ci + } + + databaseConfig.whenNotNull { + + when (databaseConfig) { + is MySqlDatabaseConnection -> { + LongTextColumnType.longTextType = LongTextColumnType.Companion.LongTextType.MEDIUMTEXT + connectToMysqlDatabase(databaseConfig as MySqlDatabaseConnection) + ResetAutoIncremental.dbType = MySqlDatabaseConnection::class.java + } + is SqliteDatabaseConnection -> { + LongTextColumnType.longTextType = LongTextColumnType.Companion.LongTextType.TEXT + connectSqliteDatabase(databaseConfig as SqliteDatabaseConnection) + ResetAutoIncremental.dbType = SqliteDatabaseConnection::class.java + } + } + + createTables(MetricTable, TemporaryMetricTable, SingleMetricTable) + + } + } + + private fun connectToMysqlDatabase(config: MySqlDatabaseConnection) { + _database = Database.connect( + url = "jdbc:mysql://${config.host}:${config.port}/${config.name}", + driver = "com.mysql.cj.jdbc.Driver", + user = config.user, + password = config.password + ) + } + + private fun connectSqliteDatabase(config: SqliteDatabaseConnection) { + _database = Database.connect( + url = "jdbc:sqlite:${config.path!!.toRealPath()}/${config.name}.db", + driver = "org.sqlite.JDBC", + user = config.user, + password = config.password + ) + } + + /** + * Creates the database tables if not exist. + */ + private fun createTables(vararg entities: Table) { + transaction { + SchemaUtils.createMissingTablesAndColumns(*entities, withLogs = false) + } + } + + @ExcludeJacocoGenerated + fun transaction(statement: Transaction.() -> T): T { + return transaction( + _database.transactionManager.defaultIsolationLevel, + _database.transactionManager.defaultRepetitionAttempts, + _database, + statement + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/database/DatabaseConnection.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/database/DatabaseConnection.kt new file mode 100644 index 00000000..39078dbe --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/database/DatabaseConnection.kt @@ -0,0 +1,28 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.database + +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +open class DatabaseConnection : java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/database/LongTextColumnType.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/database/LongTextColumnType.kt new file mode 100644 index 00000000..bbfe4645 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/database/LongTextColumnType.kt @@ -0,0 +1,47 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.database + +import org.jetbrains.exposed.sql.IColumnType +import org.jetbrains.exposed.sql.TextColumnType + +/** + * Since the MySql database `text` column can only save a text with a maximum + * 65kb size. this is an extension structure to hold big text values for it. + */ +class LongTextColumnType : IColumnType by TextColumnType() { + + companion object { + var longTextType: LongTextType = LongTextType.TEXT + + enum class LongTextType(val value: String) { + TEXT("TEXT"), + MEDIUMTEXT("MEDIUMTEXT") + } + } + + override fun sqlType(): String { + return longTextType.value + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/database/MySqlDatabaseConnection.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/database/MySqlDatabaseConnection.kt new file mode 100644 index 00000000..8553fe50 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/database/MySqlDatabaseConnection.kt @@ -0,0 +1,48 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.database + +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +class MySqlDatabaseConnection(block: MySqlDatabaseConnection.() -> Unit): DatabaseConnection() { + + companion object { + const val DEFAULT_MYSQL_PORT = 3306 + } + + init { + also(block) + } + + lateinit var host: String + + lateinit var name: String + + var port: Int = DEFAULT_MYSQL_PORT + + var user: String = "root" + + var password: String = "" + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/database/ResetAutoIncremental.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/database/ResetAutoIncremental.kt new file mode 100644 index 00000000..2aada625 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/database/ResetAutoIncremental.kt @@ -0,0 +1,44 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.database + +/** + * Since tables uses auto incremental number to generate unique id, the auto-incremental + * won't reset after table cleared, This class helps to reset auto-incremental numbers + * of a table. also it will do it for both MySql and Sqlite database. + */ +object ResetAutoIncremental { + lateinit var dbType: Class + + fun getQuery(tableName: String): String? { + if (this::dbType.isInitialized) { + if (dbType == MySqlDatabaseConnection::class.java) { + return "ALTER TABLE $tableName AUTO_INCREMENT = 0;" + } + if (dbType == SqliteDatabaseConnection::class.java) { + return "UPDATE SQLITE_SEQUENCE SET SEQ=0 WHERE NAME='$tableName';" + } + } + return null + } +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/database/SqliteDatabaseConnection.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/database/SqliteDatabaseConnection.kt new file mode 100644 index 00000000..51b280c5 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/database/SqliteDatabaseConnection.kt @@ -0,0 +1,42 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.database + +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +class SqliteDatabaseConnection(block: SqliteDatabaseConnection.() -> Unit): DatabaseConnection() { + + init { + also(block) + } + + var path: String? = null + + var name: String? = null + + var user: String = "root" + + var password: String = "" + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/database/table/MetricTable.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/database/table/MetricTable.kt new file mode 100644 index 00000000..2523b634 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/database/table/MetricTable.kt @@ -0,0 +1,48 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.database.table + +import io.github.janbarari.gradle.analytics.database.Database +import io.github.janbarari.gradle.analytics.database.LongTextColumnType +import org.jetbrains.exposed.sql.Table + +object MetricTable : Table("metric") { + + /** + * The unique auto-generated number which represents the build-number. + * + * It also is the primary-key of the table. + */ + val number = long("number").autoIncrement().uniqueIndex() + + val createdAt = long("created_at") + + val branch = varchar("branch", Database.DEFAULT_VARCHAR_LENGTH) + + val requestedTasks = varchar("requested_tasks", Database.DEFAULT_VARCHAR_LENGTH) + + val value = registerColumn("value", LongTextColumnType()) + + override val primaryKey = PrimaryKey(number) + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/database/table/SingleMetricTable.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/database/table/SingleMetricTable.kt new file mode 100644 index 00000000..8279d073 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/database/table/SingleMetricTable.kt @@ -0,0 +1,50 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.database.table + +import io.github.janbarari.gradle.analytics.database.Database +import io.github.janbarari.gradle.analytics.database.LongTextColumnType +import io.github.janbarari.gradle.analytics.database.table.TemporaryMetricTable.autoIncrement +import io.github.janbarari.gradle.analytics.database.table.TemporaryMetricTable.uniqueIndex +import org.jetbrains.exposed.sql.Table + +object SingleMetricTable : Table("single_metric") { + + /** + * The unique auto-generated id. + * + * It also is the primary-key of the table. + */ + val id = long("id").autoIncrement().uniqueIndex() + + val key = varchar("key", Database.DEFAULT_VARCHAR_LENGTH) + + val createdAt = long("created_at") + + val branch = varchar("branch", Database.DEFAULT_VARCHAR_LENGTH) + + val value = registerColumn("value", LongTextColumnType()) + + override val primaryKey = PrimaryKey(id) + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/database/table/TemporaryMetricTable.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/database/table/TemporaryMetricTable.kt new file mode 100644 index 00000000..377d6946 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/database/table/TemporaryMetricTable.kt @@ -0,0 +1,48 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.database.table + +import io.github.janbarari.gradle.analytics.database.Database +import io.github.janbarari.gradle.analytics.database.LongTextColumnType +import org.jetbrains.exposed.sql.Table + +object TemporaryMetricTable : Table("temporary_metric") { + + /** + * The unique auto-generated number which represents the build-number. + * + * It also is the primary-key of the table. + */ + val number = long("number").autoIncrement().uniqueIndex() + + val createdAt = long("created_at") + + val branch = varchar("branch", Database.DEFAULT_VARCHAR_LENGTH) + + val requestedTasks = varchar("requested_tasks", Database.DEFAULT_VARCHAR_LENGTH) + + val value = registerColumn("value", LongTextColumnType()) + + override val primaryKey = PrimaryKey(number) + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/BuildInfo.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/BuildInfo.kt new file mode 100644 index 00000000..589eab91 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/BuildInfo.kt @@ -0,0 +1,184 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model + +import io.github.janbarari.gradle.extension.whenNotNull +import org.gradle.tooling.Failure +import java.time.Duration + +data class BuildInfo( + val createdAt: Long, + val startedAt: Long, + val initializedAt: Long, + val configuredAt: Long, + var dependenciesResolveInfo: Collection, + val executedTasks: List, + val finishedAt: Long, + val branch: String, + val gitHeadCommitHash: String, + val requestedTasks: List, + val isSuccessful: Boolean, + val failure: List? = null +) : java.io.Serializable { + + init { + // Replace pass-by-reference to pass-by-value, cause the collection will be reset after creation of BuildInfo. + dependenciesResolveInfo = dependenciesResolveInfo.toList() + } + + /** + * Returns the total build duration. + */ + fun getTotalDuration(): Duration { + if (finishedAt < startedAt) return Duration.ofMillis(0) + return Duration.ofMillis(finishedAt - startedAt) + } + + /** + * Returns the build initialization process duration. + */ + fun getInitializationDuration(): Duration { + if (initializedAt < startedAt) return Duration.ofMillis(0) + return Duration.ofMillis(initializedAt - startedAt) + } + + /** + * Returns the build configuration process duration. + */ + fun getConfigurationDuration(): Duration { + if (configuredAt < initializedAt) return Duration.ofMillis(0) + return Duration.ofMillis(configuredAt - initializedAt) + } + + /** + * Returns the build execution process duration. + */ + fun getExecutionDuration(): Duration { + if (finishedAt < configuredAt) return Duration.ofMillis(0) + return Duration.ofMillis(finishedAt - configuredAt) + } + + /** + * Returns the total dependencies resolve duration. + */ + fun getTotalDependenciesResolveDuration(): Duration { + var result = 0L + val iterator = dependenciesResolveInfo.iterator() + while (iterator.hasNext()) { + val info = iterator.next() + result += info.getDuration() + } + return Duration.ofMillis(result) + } + + /** + * Calculates the cumulative parallel execution duration in milliseconds. + */ + fun calculateParallelExecutionByMillis(): Long { + return executedTasks.sumOf { it.getDurationInMillis() } + } + + /** + * Gradle executes the project tasks in parallel to use maximum performance of + * the system resources, Which means by adding the task's duration together, + * We calculated the serial duration. to calculate the non-parallel + * duration(real-life duration) we need to ignore those tasks that are executed at + * the same time or covered times by another task. + */ + fun calculateNonParallelExecutionInMillis(executedTasks: List = this.executedTasks): Long { + fun checkIfCanMerge( + parallelTask: TaskInfo, + nonParallelTask: Map.Entry>, + nonParallelTasks: HashMap> + ) { + if (parallelTask.startedAt <= nonParallelTask.value.second && + parallelTask.finishedAt >= nonParallelTask.value.second) { + + var start = nonParallelTask.value.first + var end = nonParallelTask.value.second + + if (parallelTask.startedAt < nonParallelTask.value.first) + start = parallelTask.startedAt + + if (parallelTask.finishedAt > nonParallelTask.value.second) + end = parallelTask.finishedAt + nonParallelTasks[nonParallelTask.key] = Pair(start, end) + } + } + + val nonParallelDurations = hashMapOf>() + + val executedTaskIterator = executedTasks + .sortedBy { task -> task.startedAt } + .iterator() + + while (executedTaskIterator.hasNext()) { + val executedTask = executedTaskIterator.next() + if (nonParallelDurations.isEmpty()) { + nonParallelDurations[nonParallelDurations.size] = Pair(executedTask.startedAt, executedTask.finishedAt) + continue + } + + var tempTask: Pair? = null + val nonParallelTasksIterator = nonParallelDurations.iterator() + while (nonParallelTasksIterator.hasNext()) { + val nonParallelTask = nonParallelTasksIterator.next() + + checkIfCanMerge(executedTask, nonParallelTask, nonParallelDurations) + + if (executedTask.startedAt > nonParallelTask.value.first && + executedTask.finishedAt > nonParallelTask.value.second && + executedTask.finishedAt > executedTask.startedAt + ) { + tempTask = Pair(executedTask.startedAt, executedTask.finishedAt) + } + } + + tempTask.whenNotNull { + val iterator = nonParallelDurations.iterator() + while (iterator.hasNext()) { + val nonParallelTask = iterator.next() + if (nonParallelTask.value.second in first..second) { + var start = nonParallelTask.value.first + var end = nonParallelTask.value.second + if (first < nonParallelTask.value.first) { + start = first + } + if (second > nonParallelTask.value.second) { + end = second + } + nonParallelDurations[nonParallelTask.key] = Pair(start, end) + } + } + + val biggestNonParallelTaskEnd = nonParallelDurations.toList().maxByOrNull { it.second.second }!!.second.second + if (first > biggestNonParallelTaskEnd) { + nonParallelDurations[nonParallelDurations.size] = this + } + } + } + + return nonParallelDurations.toList().sumOf { (it.second.second - it.second.first) } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/ChartPoint.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/ChartPoint.kt new file mode 100644 index 00000000..c4de603a --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/ChartPoint.kt @@ -0,0 +1,36 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ChartPoint( + @Json(name = "value") + val value: Long, + @Json(name = "description") + val description: String +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/Dependency.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/Dependency.kt new file mode 100644 index 00000000..e9d44912 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/Dependency.kt @@ -0,0 +1,70 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import org.gradle.api.Project + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class Dependency( + @Json(name = "name") + val name: String, + @Json(name = "module_name") + val moduleName: String, + @Json(name = "module_group") + val moduleGroup: String, + @Json(name = "module_version") + val moduleVersion: String, + @Json(name = "size_by_kb") + val sizeByKb: Long +): java.io.Serializable { + + companion object { + + fun Project.getThirdPartyDependencies(): List { + return subprojects.flatMap { project -> + project.configurations.filter { + it.isCanBeResolved && it.name.contains("compileClassPath", ignoreCase = true) + }.flatMap { configuration -> + configuration.resolvedConfiguration.firstLevelModuleDependencies.filter { resolvedDependency -> + !resolvedDependency.moduleVersion.equals("unspecified", true) + }.map { it } + } + }.toSet() + .map { + Dependency( + name = it.name, + moduleName = it.moduleName, + moduleGroup = it.moduleGroup, + moduleVersion = it.moduleVersion, + sizeByKb = it.moduleArtifacts.sumOf { artifact -> artifact.file.length() / 1024L } + ) + } + } + + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/DependencyResolveInfo.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/DependencyResolveInfo.kt new file mode 100644 index 00000000..e0c2ca9a --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/DependencyResolveInfo.kt @@ -0,0 +1,46 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass + +@JsonClass(generateAdapter = true) +data class DependencyResolveInfo( + @Json(name = "path") + val path: String, + @Json(name = "started_at") + val startedAt: Long, + @Json(name = "finished_at") + var finishedAt: Long = 0L +) : java.io.Serializable { + + /** + * Returns the resolve duration in milliseconds. + */ + fun getDuration(): Long { + if (finishedAt < startedAt) return 0L + return finishedAt - startedAt + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/Module.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/Module.kt new file mode 100644 index 00000000..4d7422c1 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/Module.kt @@ -0,0 +1,46 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.gradle.api.Project + +@JsonClass(generateAdapter = true) +data class Module( + @Json(name = "path") + val path: String, + @Json(name = "absoluteDir") + val absoluteDir: String +): java.io.Serializable { + + companion object { + fun Project.toModule(): Module { + return Module( + path = path, + absoluteDir = projectDir.absolutePath + ) + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/ModuleDependency.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/ModuleDependency.kt new file mode 100644 index 00000000..37d8dcf8 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/ModuleDependency.kt @@ -0,0 +1,39 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import java.io.Serializable + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModuleDependency( + @Json(name = "path") + val path: String, + @Json(name = "configuration") + val configuration: String, + @Json(name = "dependency") + val dependency: String +): Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/ModulesDependencyGraph.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/ModulesDependencyGraph.kt new file mode 100644 index 00000000..43b61924 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/ModulesDependencyGraph.kt @@ -0,0 +1,35 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import java.io.Serializable + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModulesDependencyGraph( + @Json(name = "dependencies") + val dependencies: List +): Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/TaskInfo.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/TaskInfo.kt new file mode 100644 index 00000000..55641d99 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/TaskInfo.kt @@ -0,0 +1,79 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import org.gradle.tooling.Failure +import org.gradle.tooling.events.OperationDescriptor + +@JsonClass(generateAdapter = true) +data class TaskInfo( + @Json(name = "started_at") + val startedAt: Long, + @Json(name = "finished_at") + val finishedAt: Long, + @Json(name = "path") + val path: String, + @Json(name = "display_name") + val displayName: String, + @Json(name = "name") + val name: String, + @Json(name = "is_successful") + val isSuccessful: Boolean, + @Json(name = "failures") + val failures: List?, + @Json(name = "dependencies") + val dependencies: List?, + @Json(name = "is_incremental") + val isIncremental: Boolean, + @Json(name = "is_from_cache") + val isFromCache: Boolean, + @Json(name = "is_up_to_date") + val isUpToDate: Boolean, + @Json(name = "is_skipped") + val isSkipped: Boolean, + @Json(name = "execution_reasons") + val executionReasons: List? +) : java.io.Serializable { + + /** + * Returns the task execution duration in milliseconds. + */ + fun getDurationInMillis(): Long { + if (finishedAt < startedAt) return 0L + return finishedAt - startedAt + } + + /** + * Returns the task module name. + */ + fun getModule(): String { + val module = path.split(":") + return if (module.size > 2) module.toList() + .dropLast(1) + .joinToString(separator = ":") + else "no_module" + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/TimespanPoint.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/TimespanPoint.kt new file mode 100644 index 00000000..ecc0aae2 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/TimespanPoint.kt @@ -0,0 +1,48 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.extension.isNull +import io.github.janbarari.gradle.utils.DateTimeUtils + +@JsonClass(generateAdapter = true) +data class TimespanPoint( + @Json(name = "value") + val value: Long, + @Json(name = "from") + val from: Long, + @Json(name = "to") + val to: Long? = null +): io.github.janbarari.gradle.core.Triple(value, from, to) { + + fun getTimespanString(): String { + return if (to.isNull()) { + DateTimeUtils.format(from, "dd/MM") + } else { + DateTimeUtils.format(from, "dd/MM") + "-" + DateTimeUtils.format(to!!, "dd/MM") + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/BuildMetric.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/BuildMetric.kt new file mode 100644 index 00000000..d8def289 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/BuildMetric.kt @@ -0,0 +1,103 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class BuildMetric( + + @Json(name = "branch") + var branch: String, + + @Json(name = "requested_tasks") + var requestedTasks: List, + + @Json(name = "created_at") + var createdAt: Long, + + @Json(name = "git_head_commit_hash") + var gitHeadCommitHash: String, + + @Json(name = "initialization_process_metric") + var initializationProcessMetric: InitializationProcessMetric? = null, + + @Json(name = "configuration_process_metric") + var configurationProcessMetric: ConfigurationProcessMetric? = null, + + @Json(name = "execution_process_metric") + var executionProcessMetric: ExecutionProcessMetric? = null, + + @Json(name = "overall_build_process_metric") + var overallBuildProcessMetric: OverallBuildProcessMetric? = null, + + @Json(name = "modules_source_count_metric") + var modulesSourceCountMetric: ModulesSourceCountMetric? = null, + + @Json(name = "modules_method_count_metric") + var modulesMethodCountMetric: ModulesMethodCountMetric? = null, + + @Json(name = "cache_hit_metric") + var cacheHitMetric: CacheHitMetric? = null, + + @Json(name = "success_build_rate_metric") + var successBuildRateMetric: SuccessBuildRateMetric? = null, + + @Json(name = "dependency_resolve_process_metric") + var dependencyResolveProcessMetric: DependencyResolveProcessMetric? = null, + + @Json(name = "parallel_execution_rate_metric") + var parallelExecutionRateMetric: ParallelExecutionRateMetric? = null, + + @Json(name = "modules_execution_process_metric") + var modulesExecutionProcessMetric: ModulesExecutionProcessMetric? = null, + + @Json(name = "modules_dependency_graph_metric") + var modulesDependencyGraphMetric: ModulesDependencyGraphMetric? = null, + + @Json(name = "modules_build_heatmap_metric") + var modulesBuildHeatmap: ModulesBuildHeatmapMetric? = null, + + @Json(name = "dependency_details_metric") + var dependencyDetailsMetric: DependencyDetailsMetric? = null, + + @Json(name = "non_cacheable_tasks_metric") + var nonCacheableTasksMetric: NonCacheableTasksMetric? = null, + + @Json(name = "modules_source_size_metric") + var modulesSourceSizeMetric: ModulesSourceSizeMetric? = null, + + @Json(name = "modules_crash_count_metric") + var modulesCrashCountMetric: ModulesCrashCountMetric? = null, + +): java.io.Serializable { + + // Exclude from build metric json to avoid save in metric table. Regarding the metric + // size and usability, this metric should be saved in `single_metric` table. + @Transient + var modulesTimelineMetric: ModulesTimelineMetric? = null + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/CacheHitMetric.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/CacheHitMetric.kt new file mode 100644 index 00000000..8e840520 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/CacheHitMetric.kt @@ -0,0 +1,36 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class CacheHitMetric( + @Json(name = "rate") + val rate: Long, + @Json(name = "modules") + val modules: List +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ConfigurationProcessMetric.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ConfigurationProcessMetric.kt new file mode 100644 index 00000000..27d6df13 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ConfigurationProcessMetric.kt @@ -0,0 +1,36 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ConfigurationProcessMetric( + @Json(name = "median") + var median: Long = 0L, + @Json(name = "mean") + var mean: Long = 0L +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/DependencyDetailsMetric.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/DependencyDetailsMetric.kt new file mode 100644 index 00000000..06f287f8 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/DependencyDetailsMetric.kt @@ -0,0 +1,36 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.domain.model.Dependency + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class DependencyDetailsMetric( + @Json(name = "dependencies") + val dependencies: List +): java.io.Serializable + diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/DependencyResolveProcessMetric.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/DependencyResolveProcessMetric.kt new file mode 100644 index 00000000..15325400 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/DependencyResolveProcessMetric.kt @@ -0,0 +1,36 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class DependencyResolveProcessMetric( + @Json(name = "median") + var median: Long = 0L, + @Json(name = "mean") + var mean: Long = 0L +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ExecutionProcessMetric.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ExecutionProcessMetric.kt new file mode 100644 index 00000000..14e4b555 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ExecutionProcessMetric.kt @@ -0,0 +1,36 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ExecutionProcessMetric( + @Json(name = "median") + var median: Long = 0L, + @Json(name = "mean") + var mean: Long = 0L +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/InitializationProcessMetric.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/InitializationProcessMetric.kt new file mode 100644 index 00000000..07ca8830 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/InitializationProcessMetric.kt @@ -0,0 +1,36 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class InitializationProcessMetric( + @Json(name = "median") + var median: Long = 0L, + @Json(name = "mean") + var mean: Long = 0L +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModuleBuildHeatmap.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModuleBuildHeatmap.kt new file mode 100644 index 00000000..43693df8 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModuleBuildHeatmap.kt @@ -0,0 +1,36 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModuleBuildHeatmap( + @Json(name = "path") + val path: String, + @Json(name = "dependant_modules_count") + val dependantModulesCount: Int +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModuleCacheHit.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModuleCacheHit.kt new file mode 100644 index 00000000..d8cceb80 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModuleCacheHit.kt @@ -0,0 +1,36 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModuleCacheHit( + @Json(name = "path") + val path: String, + @Json(name = "rate") + var rate: Long +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModuleExecutionProcess.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModuleExecutionProcess.kt new file mode 100644 index 00000000..556318bb --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModuleExecutionProcess.kt @@ -0,0 +1,42 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModuleExecutionProcess( + @Json(name = "path") + val path: String, + @Json(name = "median") + val medianExecInMillis: Long, + @Json(name = "median_parallel") + val medianParallelExecInMillis: Long, + @Json(name = "parallel_rate") + val parallelRate: Float, + @Json(name = "coverage") + val coverageRate: Float +) : java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModuleMethodCount.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModuleMethodCount.kt new file mode 100644 index 00000000..e918ebd5 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModuleMethodCount.kt @@ -0,0 +1,36 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModuleMethodCount( + @Json(name = "path") + val path: String, + @Json(name = "value") + val value: Int +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModuleSourceCount.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModuleSourceCount.kt new file mode 100644 index 00000000..3bb6d281 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModuleSourceCount.kt @@ -0,0 +1,36 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModuleSourceCount( + @Json(name = "path") + val path: String, + @Json(name = "value") + val value: Int +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModuleTimeline.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModuleTimeline.kt new file mode 100644 index 00000000..50e1e246 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModuleTimeline.kt @@ -0,0 +1,52 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModuleTimeline( + @Json(name = "path") + val path: String, + @Json(name = "timelines") + val timelines: List +) { + + @ExcludeJacocoGenerated + @JsonClass(generateAdapter = true) + data class Timeline( + @Json(name = "path") + val path: String, + @Json(name = "start") + val start: Long, + @Json(name = "end") + val end: Long, + @Json(name = "is_cached") + val isCached: Boolean + ) + +} + diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesBuildHeatmapMetric.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesBuildHeatmapMetric.kt new file mode 100644 index 00000000..fee73086 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesBuildHeatmapMetric.kt @@ -0,0 +1,35 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModulesBuildHeatmapMetric( + @Json(name = "modules") + val modules: List +): java.io.Serializable + diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesCrashCountMetric.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesCrashCountMetric.kt new file mode 100644 index 00000000..95cf2565 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesCrashCountMetric.kt @@ -0,0 +1,45 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModulesCrashCountMetric( + @Json(name = "modules") + val modules: List +): java.io.Serializable { + + @ExcludeJacocoGenerated + @JsonClass(generateAdapter = true) + data class ModuleCrash( + @Json(name = "path") + val path: String, + @Json(name = "crash_count") + val totalCrashes: Long, + ): java.io.Serializable + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesDependencyGraphMetric.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesDependencyGraphMetric.kt new file mode 100644 index 00000000..453442f9 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesDependencyGraphMetric.kt @@ -0,0 +1,36 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.domain.model.ModuleDependency +import java.io.Serializable + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModulesDependencyGraphMetric( + @Json(name = "dependencies") + var dependencies: List +): Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesExecutionProcessMetric.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesExecutionProcessMetric.kt new file mode 100644 index 00000000..1a7e87c1 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesExecutionProcessMetric.kt @@ -0,0 +1,34 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModulesExecutionProcessMetric( + @Json(name = "modules") + var modules: List +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesMethodCountMetric.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesMethodCountMetric.kt new file mode 100644 index 00000000..a464674c --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesMethodCountMetric.kt @@ -0,0 +1,35 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModulesMethodCountMetric( + @Json(name = "modules") + val modules: List +): java.io.Serializable + diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesSourceCountMetric.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesSourceCountMetric.kt new file mode 100644 index 00000000..cbb9b913 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesSourceCountMetric.kt @@ -0,0 +1,35 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModulesSourceCountMetric( + @Json(name = "modules") + val modules: List +): java.io.Serializable + diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesSourceSizeMetric.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesSourceSizeMetric.kt new file mode 100644 index 00000000..51351c5c --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesSourceSizeMetric.kt @@ -0,0 +1,46 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModulesSourceSizeMetric( + @Json(name = "modules") + val modules: List +): java.io.Serializable { + + @ExcludeJacocoGenerated + @JsonClass(generateAdapter = true) + data class ModuleSourceSize( + @Json(name = "path") + val path: String, + @Json(name = "size_by_kb") + val sizeInKb: Long + ): java.io.Serializable + +} + diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesTimelineMetric.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesTimelineMetric.kt new file mode 100644 index 00000000..505b1aaa --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ModulesTimelineMetric.kt @@ -0,0 +1,40 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModulesTimelineMetric( + @Json(name = "start") + val start: Long, + @Json(name = "end") + val end: Long, + @Json(name = "modules") + val modules: List, + @Json(name = "created_at") + val createdAt: Long, +) diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/NonCacheableTasksMetric.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/NonCacheableTasksMetric.kt new file mode 100644 index 00000000..abe1063e --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/NonCacheableTasksMetric.kt @@ -0,0 +1,42 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import java.io.Serializable + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class NonCacheableTasksMetric( + val tasks: List +): Serializable { + + @ExcludeJacocoGenerated + @JsonClass(generateAdapter = true) + data class NonCacheableTask( + val path: String, + var avgExecutionDurationInMillis: Long + ): Serializable + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/OverallBuildProcessMetric.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/OverallBuildProcessMetric.kt new file mode 100644 index 00000000..fdc26d4d --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/OverallBuildProcessMetric.kt @@ -0,0 +1,36 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class OverallBuildProcessMetric( + @Json(name = "median") + var median: Long = 0L, + @Json(name = "mean") + var mean: Long = 0L +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ParallelExecutionRateMetric.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ParallelExecutionRateMetric.kt new file mode 100644 index 00000000..130c6295 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/ParallelExecutionRateMetric.kt @@ -0,0 +1,34 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ParallelExecutionRateMetric( + @Json(name = "median_rate") + var medianRate: Long = 0L +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/SuccessBuildRateMetric.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/SuccessBuildRateMetric.kt new file mode 100644 index 00000000..73da372c --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/metric/SuccessBuildRateMetric.kt @@ -0,0 +1,40 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.metric + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class SuccessBuildRateMetric( + @Json(name = "median_rate") + var medianRate: Float = 0f, + @Json(name = "mean_rate") + var meanRate: Float = 0f, + @Json(name = "successes") + var successes: Int = 0, + @Json(name = "fails") + var fails: Int = 0 +) : java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/os/HardwareInfo.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/os/HardwareInfo.kt new file mode 100644 index 00000000..98319192 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/os/HardwareInfo.kt @@ -0,0 +1,31 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.os + +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +data class HardwareInfo( + val availableMemory: Long, + val maximumMemoryCapacity: Long +) : java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/os/OsInfo.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/os/OsInfo.kt new file mode 100644 index 00000000..14d87385 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/os/OsInfo.kt @@ -0,0 +1,30 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.os + +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +data class OsInfo( + val name: String +) : java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/BuildStatusReport.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/BuildStatusReport.kt new file mode 100644 index 00000000..7fea9261 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/BuildStatusReport.kt @@ -0,0 +1,59 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import java.io.Serializable + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class BuildStatusReport( + @Json(name = "cumulative_overall_build_process_by_seconds") + val cumulativeOverallBuildProcessBySeconds: Long, + @Json(name = "avg_overall_build_process_by_seconds") + val avgOverallBuildProcessBySeconds: Long, + @Json(name = "total_build_process_count") + val totalBuildProcessCount: Int, + @Json(name = "total_project_modules_count") + val totalProjectModulesCount: Int, + @Json(name = "cumulative_parallel_execution_by_seconds") + val cumulativeParallelExecutionBySeconds: Long, + @Json(name = "avg_parallel_execution_rate") + val avgParallelExecutionRate: Float, + @Json(name = "total_succeed_build_count") + val totalSucceedBuildCount: Int, + @Json(name = "total_failed_build_count") + val totalFailedBuildCount: Int, + @Json(name = "avg_cache_hit_rate") + val avgCacheHitRate: Float, + @Json(name = "cumulative_dependency_resolve_by_seconds") + val cumulativeDependencyResolveBySeconds: Long, + @Json(name = "avg_initialization_process_by_millis") + val avgInitializationProcessByMillis: Long, + @Json(name = "avg_configuration_process_by_millis") + val avgConfigurationProcessByMillis: Long, + @Json(name = "avg_execution_process_by_seconds") + val avgExecutionProcessBySeconds: Long +): Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/CacheHitReport.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/CacheHitReport.kt new file mode 100644 index 00000000..18e2fa80 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/CacheHitReport.kt @@ -0,0 +1,43 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.domain.model.ChartPoint +import io.github.janbarari.gradle.analytics.domain.model.TimespanPoint +import java.io.Serializable + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class CacheHitReport( + @Json(name = "modules") + val modules: List, + @Json(name = "overall_mean_values") + val overallMeanValues: List, + @Json(name = "overall_rate") + val overallRate: Long, + @Json(name = "overall_diff_rate") + val overallDiffRate: Float? = null +): Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ConfigurationProcessReport.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ConfigurationProcessReport.kt new file mode 100644 index 00000000..f4ca4ec0 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ConfigurationProcessReport.kt @@ -0,0 +1,37 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.domain.model.TimespanPoint + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ConfigurationProcessReport( + @Json(name = "median_values") + val medianValues: List, + @Json(name = "mean_values") + val meanValues: List, +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/DependencyDetailsReport.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/DependencyDetailsReport.kt new file mode 100644 index 00000000..dd522e14 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/DependencyDetailsReport.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.domain.model.Dependency +import java.io.Serializable + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class DependencyDetailsReport( + @Json(name = "dependencies") + val dependencies: List, + @Json(name = "cumulative_dependencies_size_by_kb") + val cumulativeDependenciesSizeByKb: Long +): Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/DependencyResolveProcessReport.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/DependencyResolveProcessReport.kt new file mode 100644 index 00000000..bdeccebf --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/DependencyResolveProcessReport.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.domain.model.ChartPoint +import io.github.janbarari.gradle.analytics.domain.model.TimespanPoint + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class DependencyResolveProcessReport( + @Json(name = "median_values") + val medianValues: List, + @Json(name = "mean_values") + val meanValues: List, +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ExecutionProcessReport.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ExecutionProcessReport.kt new file mode 100644 index 00000000..b6dae584 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ExecutionProcessReport.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.domain.model.ChartPoint +import io.github.janbarari.gradle.analytics.domain.model.TimespanPoint + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ExecutionProcessReport( + @Json(name = "median_values") + val medianValues: List, + @Json(name = "mean_values") + val meanValues: List +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/InitializationProcessReport.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/InitializationProcessReport.kt new file mode 100644 index 00000000..e5dc4f90 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/InitializationProcessReport.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.domain.model.ChartPoint +import io.github.janbarari.gradle.analytics.domain.model.TimespanPoint + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class InitializationProcessReport( + @Json(name = "median_values") + val medianValues: List, + @Json(name = "mean_values") + val meanValues: List +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModuleBuildHeatmap.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModuleBuildHeatmap.kt new file mode 100644 index 00000000..1ff6c64c --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModuleBuildHeatmap.kt @@ -0,0 +1,40 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModuleBuildHeatmap( + @Json(name = "path") + val path: String, + @Json(name = "dependant_modules_count") + val dependantModulesCount: Int, + @Json(name = "avg_median_cache_hit") + val avgMedianCacheHit: Long, + @Json(name = "total_build_count") + val totalBuildCount: Int +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModuleCacheHit.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModuleCacheHit.kt new file mode 100644 index 00000000..c93c22b1 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModuleCacheHit.kt @@ -0,0 +1,42 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.domain.model.ChartPoint +import io.github.janbarari.gradle.analytics.domain.model.TimespanPoint + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModuleCacheHit( + @Json(name = "path") + val path: String, + @Json(name = "rate") + val rate: Long, + @Json(name = "diff_rate") + val diffRate: Float? = null, + @Json(name = "mean_values") + val meanValues: List +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModuleExecutionProcess.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModuleExecutionProcess.kt new file mode 100644 index 00000000..43472fbb --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModuleExecutionProcess.kt @@ -0,0 +1,47 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.domain.model.TimespanPoint + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModuleExecutionProcess( + @Json(name = "path") + val path: String, + @Json(name = "avg_median_duration") + val avgMedianExecInMillis: Long, + @Json(name = "avg_median_parallel_duration") + val avgMedianParallelExecInMillis: Long, + @Json(name = "avg_median_parallel_rate") + val avgMedianParallelRate: Float, + @Json(name = "avg_median_coverage") + val avgMedianCoverageRate: Float, + @Json(name = "avg_median_durations") + val avgMedianExecs: List, + @Json(name = "diff_rate") + val diffRate: Float? +) : java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModuleMethodCount.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModuleMethodCount.kt new file mode 100644 index 00000000..92f5658b --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModuleMethodCount.kt @@ -0,0 +1,40 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModuleMethodCount( + @Json(name = "path") + val path: String, + @Json(name = "value") + val value: Int, + @Json(name = "coverage") + val coverageRate: Float, + @Json(name = "diff_rate") + val diffRate: Float? = null +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModuleSourceCount.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModuleSourceCount.kt new file mode 100644 index 00000000..ed242a7b --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModuleSourceCount.kt @@ -0,0 +1,40 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModuleSourceCount( + @Json(name = "path") + val path: String, + @Json(name = "value") + val value: Int, + @Json(name = "coverage") + val coverageRate: Float, + @Json(name = "diff_rate") + val diffRate: Float? = null +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesBuildHeatmapReport.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesBuildHeatmapReport.kt new file mode 100644 index 00000000..16e3b568 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesBuildHeatmapReport.kt @@ -0,0 +1,35 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import java.io.Serializable + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModulesBuildHeatmapReport( + @Json(name = "modules") + val modules: List +): Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesCrashCountReport.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesCrashCountReport.kt new file mode 100644 index 00000000..ed2ae5c0 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesCrashCountReport.kt @@ -0,0 +1,36 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesCrashCountMetric +import java.io.Serializable + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModulesCrashCountReport( + @Json(name = "modules") + val modules: List +): Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesDependencyGraphReport.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesDependencyGraphReport.kt new file mode 100644 index 00000000..d3aaabcd --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesDependencyGraphReport.kt @@ -0,0 +1,36 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.domain.model.ModuleDependency +import java.io.Serializable + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModulesDependencyGraphReport( + @Json(name = "dependencies") + var dependencies: List +) : Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesExecutionProcessReport.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesExecutionProcessReport.kt new file mode 100644 index 00000000..a2878b80 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesExecutionProcessReport.kt @@ -0,0 +1,35 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.domain.model.ChartPoint + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModulesExecutionProcessReport( + @Json(name = "modules") + val modules: List +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesMethodCountReport.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesMethodCountReport.kt new file mode 100644 index 00000000..455f0b58 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesMethodCountReport.kt @@ -0,0 +1,39 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import java.io.Serializable + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModulesMethodCountReport( + @Json(name = "values") + val values: List, + @Json(name = "total_method_count") + val totalMethodCount: Int, + @Json(name = "total_diff_rate") + val totalDiffRate: Float? = null +): Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesSourceCountReport.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesSourceCountReport.kt new file mode 100644 index 00000000..a1c62520 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesSourceCountReport.kt @@ -0,0 +1,39 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import java.io.Serializable + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModulesSourceCountReport( + @Json(name = "values") + val values: List, + @Json(name = "total_source_count") + val totalSourceCount: Int, + @Json(name = "total_diff_rate") + val totalDiffRate: Float? = null +): Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesSourceSizeReport.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesSourceSizeReport.kt new file mode 100644 index 00000000..95a7cbc9 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesSourceSizeReport.kt @@ -0,0 +1,54 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import java.io.Serializable + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModulesSourceSizeReport( + @Json(name = "values") + val values: List, + @Json(name = "total_source_size_by_kb") + val totalSourceSizeInKb: Long, + @Json(name = "total_diff_rate") + val totalDiffRate: Float? = null +): Serializable { + + @ExcludeJacocoGenerated + @JsonClass(generateAdapter = true) + data class ModuleSourceSize( + @Json(name = "path") + val path: String, + @Json(name = "size_by_kb") + val sizeInKb: Long, + @Json(name = "coverage") + val coverageRate: Float, + @Json(name = "diff_rate") + val diffRate: Float? = null + ): Serializable + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesTimelineReport.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesTimelineReport.kt new file mode 100644 index 00000000..fa32f622 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ModulesTimelineReport.kt @@ -0,0 +1,42 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.domain.model.metric.ModuleTimeline +import java.io.Serializable + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ModulesTimelineReport( + @Json(name = "start") + val start: Long, + @Json(name = "end") + val end: Long, + @Json(name = "modules") + val modules: List, + @Json(name = "created_at") + val createdAt: Long +): Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/NonCacheableTasksReport.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/NonCacheableTasksReport.kt new file mode 100644 index 00000000..4938ddf7 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/NonCacheableTasksReport.kt @@ -0,0 +1,36 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.domain.model.metric.NonCacheableTasksMetric +import java.io.Serializable + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class NonCacheableTasksReport( + @Json(name = "tasks") + val tasks: List +): Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/OverallBuildProcessReport.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/OverallBuildProcessReport.kt new file mode 100644 index 00000000..4feae285 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/OverallBuildProcessReport.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.domain.model.ChartPoint +import io.github.janbarari.gradle.analytics.domain.model.TimespanPoint + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class OverallBuildProcessReport( + @Json(name = "median_values") + val medianValues: List, + @Json(name = "mean_values") + val meanValues: List +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ParallelExecutionRateReport.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ParallelExecutionRateReport.kt new file mode 100644 index 00000000..991e0008 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/ParallelExecutionRateReport.kt @@ -0,0 +1,35 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.domain.model.TimespanPoint + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class ParallelExecutionRateReport( + @Json(name = "median_values") + val medianValues: List +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/Report.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/Report.kt new file mode 100644 index 00000000..d9601982 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/Report.kt @@ -0,0 +1,105 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonAdapter +import com.squareup.moshi.JsonClass +import com.squareup.moshi.Moshi + +@JsonClass(generateAdapter = true) +data class Report( + + @Json(name = "branch") + val branch: String, + + @Json(name = "requested_tasks") + val requestedTasks: String, + + @Json(name = "initialization_process_report") + var initializationProcessReport: InitializationProcessReport? = null, + + @Json(name = "configuration_process_report") + var configurationProcessReport: ConfigurationProcessReport? = null, + + @Json(name = "execution_process_report") + var executionProcessReport: ExecutionProcessReport? = null, + + @Json(name = "overall_build_process_report") + var overallBuildProcessReport: OverallBuildProcessReport? = null, + + @Json(name = "modules_source_count_report") + var modulesSourceCountReport: ModulesSourceCountReport? = null, + + @Json(name = "modules_method_count_report") + var modulesMethodCountReport: ModulesMethodCountReport? = null, + + @Json(name = "cache_hit_report") + var cacheHitReport: CacheHitReport? = null, + + @Json(name = "success_build_rate_report") + var successBuildRateReport: SuccessBuildRateReport? = null, + + @Json(name = "dependency_resolve_process_report") + var dependencyResolveProcessReport: DependencyResolveProcessReport? = null, + + @Json(name = "parallel_execution_rate_report") + var parallelExecutionRateReport: ParallelExecutionRateReport? = null, + + @Json(name = "modules_execution_process_report") + var modulesExecutionProcessReport: ModulesExecutionProcessReport? = null, + + @Json(name = "modules_dependency_graph_report") + var modulesDependencyGraphReport: ModulesDependencyGraphReport? = null, + + @Json(name = "modules_timeline_report") + var modulesTimelineReport: ModulesTimelineReport? = null, + + @Json(name = "build_status_report") + var buildStatusReport: BuildStatusReport? = null, + + @Json(name = "modules_build_heatmap_report") + var modulesBuildHeatmapReport: ModulesBuildHeatmapReport? = null, + + @Json(name = "dependency_details_report") + var dependencyDetailsReport: DependencyDetailsReport? = null, + + @Json(name = "non_cacheable_tasks_report") + var nonCacheableTasksReport: NonCacheableTasksReport? = null, + + @Json(name = "modules_source_size_report") + var modulesSourceSizeReport: ModulesSourceSizeReport? = null, + + @Json(name = "modules_crash_count_report") + var modulesCrashCountReport: ModulesCrashCountReport? = null, + +) : java.io.Serializable { + + fun toJson(): String { + val moshi: Moshi = Moshi.Builder().build() + val jsonAdapter: JsonAdapter = ReportJsonAdapter(moshi) + return jsonAdapter.toJson(this) + } + +} + diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/SuccessBuildRateReport.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/SuccessBuildRateReport.kt new file mode 100644 index 00000000..6ec7ccf8 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/model/report/SuccessBuildRateReport.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.model.report + +import com.squareup.moshi.Json +import com.squareup.moshi.JsonClass +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.domain.model.ChartPoint +import io.github.janbarari.gradle.analytics.domain.model.TimespanPoint + +@ExcludeJacocoGenerated +@JsonClass(generateAdapter = true) +data class SuccessBuildRateReport( + @Json(name = "median_values") + val medianValues: List, + @Json(name = "mean_values") + val meanValues: List +): java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/repository/DatabaseRepository.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/repository/DatabaseRepository.kt new file mode 100644 index 00000000..c124a8c0 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/repository/DatabaseRepository.kt @@ -0,0 +1,48 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.repository + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric + +interface DatabaseRepository { + fun saveNewMetric(metric: BuildMetric): Long + fun saveTemporaryMetric(metric: BuildMetric): Long + + fun isDayMetricExists(): Boolean + + fun getDayMetric(): Pair + fun getMetrics(period: Pair): List + fun getTemporaryMetrics(): List + + fun updateDayMetric(number: Long, metric: BuildMetric): Boolean + + fun dropOutdatedTemporaryMetrics(): Boolean + fun dropMetrics(): Boolean + fun dropOutdatedMetrics() + + fun getSingleMetric(key: String, branch: String): String? + fun updateSingleMetric(key: String, branch: String, value: String): Boolean + fun saveSingleMetric(key: String, branch: String, value: String): Boolean + fun dropSingleMetric(key: String, branch: String): Boolean + fun dropSingleMetrics(): Boolean +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/usecase/GetMetricsUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/usecase/GetMetricsUseCase.kt new file mode 100644 index 00000000..bb0692b2 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/usecase/GetMetricsUseCase.kt @@ -0,0 +1,43 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.usecase + +import io.github.janbarari.gradle.core.UseCase +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository + +/** + * Gets the saved metrics from the database. + */ +class GetMetricsUseCase( + private val repo: DatabaseRepository +): UseCase, List>() { + + /** + * Get list of metrics from database by giving the start & end timestamp. + */ + override suspend fun execute(input: Pair): List { + return repo.getMetrics(input) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/usecase/GetModulesTimelineUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/usecase/GetModulesTimelineUseCase.kt new file mode 100644 index 00000000..25ef2e7c --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/usecase/GetModulesTimelineUseCase.kt @@ -0,0 +1,47 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.usecase + +import com.squareup.moshi.Moshi +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesTimelineMetric +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesTimelineMetricJsonAdapter +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.core.UseCase +import io.github.janbarari.gradle.extension.isNotNull + +class GetModulesTimelineUseCase( + val moshi: Moshi, + val repo: DatabaseRepository +): UseCase() { + + override suspend fun execute(branch: String): ModulesTimelineMetric? { + val result = repo.getSingleMetric( + key = "modulesExecTimeline", + branch = branch + ) + if (result.isNotNull()) + return ModulesTimelineMetricJsonAdapter(moshi).fromJson(result!!) + return null + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/usecase/SaveMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/usecase/SaveMetricUseCase.kt new file mode 100644 index 00000000..a3975253 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/usecase/SaveMetricUseCase.kt @@ -0,0 +1,126 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.usecase + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.analytics.metric.cachehit.update.UpdateCacheHitMetricStage +import io.github.janbarari.gradle.analytics.metric.cachehit.update.UpdateCacheHitMetricUseCase +import io.github.janbarari.gradle.analytics.metric.configurationprocess.update.UpdateConfigurationProcessMetricStage +import io.github.janbarari.gradle.analytics.metric.configurationprocess.update.UpdateConfigurationProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.dependencydetails.update.UpdateDependencyDetailsMetricStage +import io.github.janbarari.gradle.analytics.metric.dependencydetails.update.UpdateDependencyDetailsMetricUseCase +import io.github.janbarari.gradle.analytics.metric.dependencyresolveprocess.update.UpdateDependencyResolveProcessMetricStage +import io.github.janbarari.gradle.analytics.metric.dependencyresolveprocess.update.UpdateDependencyResolveProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.executionprocess.update.UpdateExecutionProcessMetricStage +import io.github.janbarari.gradle.analytics.metric.executionprocess.update.UpdateExecutionProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.initializationprocess.update.UpdateInitializationProcessMetricStage +import io.github.janbarari.gradle.analytics.metric.initializationprocess.update.UpdateInitializationProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulesbuildheatmap.update.UpdateModulesBuildHeatmapMetricStage +import io.github.janbarari.gradle.analytics.metric.modulesbuildheatmap.update.UpdateModulesBuildHeatmapMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulescrashcount.update.UpdateModulesCrashCountMetricStage +import io.github.janbarari.gradle.analytics.metric.modulescrashcount.update.UpdateModulesCrashCountMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulesdependencygraph.update.UpdateModulesDependencyGraphMetricStage +import io.github.janbarari.gradle.analytics.metric.modulesdependencygraph.update.UpdateModulesDependencyGraphMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulesexecutionprocess.update.UpdateModulesExecutionProcessMetricStage +import io.github.janbarari.gradle.analytics.metric.modulesexecutionprocess.update.UpdateModulesExecutionProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulesmethodcount.update.UpdateModulesMethodCountMetricStage +import io.github.janbarari.gradle.analytics.metric.modulesmethodcount.update.UpdateModulesMethodCountMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulesourcecount.update.UpdateModulesSourceCountMetricStage +import io.github.janbarari.gradle.analytics.metric.modulesourcecount.update.UpdateModulesSourceCountMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulessourcesize.update.UpdateModulesSourceSizeMetricStage +import io.github.janbarari.gradle.analytics.metric.modulessourcesize.update.UpdateModulesSourceSizeMetricUseCase +import io.github.janbarari.gradle.analytics.metric.noncacheabletasks.update.UpdateNonCacheableTasksMetricStage +import io.github.janbarari.gradle.analytics.metric.noncacheabletasks.update.UpdateNonCacheableTasksMetricUseCase +import io.github.janbarari.gradle.analytics.metric.overallbuildprocess.update.UpdateOverallBuildProcessMetricStage +import io.github.janbarari.gradle.analytics.metric.overallbuildprocess.update.UpdateOverallBuildProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.paralleexecutionrate.update.UpdateParallelExecutionRateMetricStage +import io.github.janbarari.gradle.analytics.metric.paralleexecutionrate.update.UpdateParallelExecutionRateMetricUseCase +import io.github.janbarari.gradle.analytics.metric.successbuildrate.update.UpdateSuccessBuildRateMetricStage +import io.github.janbarari.gradle.analytics.metric.successbuildrate.update.UpdateSuccessBuildRateMetricUseCase +import io.github.janbarari.gradle.core.UseCase + +/** + * Saves daily build metrics. + */ +class SaveMetricUseCase( + private val repo: DatabaseRepository, + private val updateInitializationProcessMetricUseCase: UpdateInitializationProcessMetricUseCase, + private val updateConfigurationProcessMetricUseCase: UpdateConfigurationProcessMetricUseCase, + private val updateExecutionProcessMetricUseCase: UpdateExecutionProcessMetricUseCase, + private val updateOverallBuildProcessMetricUseCase: UpdateOverallBuildProcessMetricUseCase, + private val updateModulesSourceCountMetricUseCase: UpdateModulesSourceCountMetricUseCase, + private val updateModulesMethodCountMetricUseCase: UpdateModulesMethodCountMetricUseCase, + private val updateCacheHitMetricUseCase: UpdateCacheHitMetricUseCase, + private val updateSuccessBuildRateMetricUseCase: UpdateSuccessBuildRateMetricUseCase, + private val updateDependencyResolveProcessMetricUseCase: UpdateDependencyResolveProcessMetricUseCase, + private val updateParallelExecutionRateMetricUseCase: UpdateParallelExecutionRateMetricUseCase, + private val updateModulesExecutionProcessMetricUseCase: UpdateModulesExecutionProcessMetricUseCase, + private val updateModulesDependencyGraphMetricUseCase: UpdateModulesDependencyGraphMetricUseCase, + private val updateModulesBuildHeatmapMetricUseCase: UpdateModulesBuildHeatmapMetricUseCase, + private val updateDependencyDetailsMetricUseCase: UpdateDependencyDetailsMetricUseCase, + private val updateNonCacheableTasksMetricUseCase: UpdateNonCacheableTasksMetricUseCase, + private val updateModulesSourceSizeMetricUseCase: UpdateModulesSourceSizeMetricUseCase, + private val updateModulesCrashCountMetricUseCase: UpdateModulesCrashCountMetricUseCase, +) : UseCase() { + + /** + * Upsert the daily metric in the database. + * + * @param input BuildMetric of the build. + */ + override suspend fun execute(input: BuildMetric): Long { + if (!repo.isDayMetricExists()) return repo.saveNewMetric(input) + + val pipeline = UpdateMetricPipeline(UpdateInitializationProcessMetricStage(updateInitializationProcessMetricUseCase)) + .addStage(UpdateConfigurationProcessMetricStage(updateConfigurationProcessMetricUseCase)) + .addStage(UpdateExecutionProcessMetricStage(updateExecutionProcessMetricUseCase)) + .addStage(UpdateOverallBuildProcessMetricStage(updateOverallBuildProcessMetricUseCase)) + .addStage(UpdateModulesSourceCountMetricStage(updateModulesSourceCountMetricUseCase)) + .addStage(UpdateModulesMethodCountMetricStage(updateModulesMethodCountMetricUseCase)) + .addStage(UpdateCacheHitMetricStage(updateCacheHitMetricUseCase)) + .addStage(UpdateSuccessBuildRateMetricStage(updateSuccessBuildRateMetricUseCase)) + .addStage(UpdateDependencyResolveProcessMetricStage(updateDependencyResolveProcessMetricUseCase)) + .addStage(UpdateParallelExecutionRateMetricStage(updateParallelExecutionRateMetricUseCase)) + .addStage(UpdateModulesExecutionProcessMetricStage(updateModulesExecutionProcessMetricUseCase)) + .addStage(UpdateModulesDependencyGraphMetricStage(updateModulesDependencyGraphMetricUseCase)) + .addStage(UpdateModulesBuildHeatmapMetricStage(updateModulesBuildHeatmapMetricUseCase)) + .addStage(UpdateDependencyDetailsMetricStage(updateDependencyDetailsMetricUseCase)) + .addStage(UpdateNonCacheableTasksMetricStage(updateNonCacheableTasksMetricUseCase)) + .addStage(UpdateModulesSourceSizeMetricStage(updateModulesSourceSizeMetricUseCase)) + .addStage(UpdateModulesCrashCountMetricStage(updateModulesCrashCountMetricUseCase)) + .execute( + BuildMetric( + branch = input.branch, + requestedTasks = input.requestedTasks, + createdAt = input.createdAt, + gitHeadCommitHash = input.gitHeadCommitHash + ) + ) + + val dayMetricNumber = repo.getDayMetric().second + repo.updateDayMetric(dayMetricNumber, pipeline) + return dayMetricNumber + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/usecase/SaveTemporaryMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/usecase/SaveTemporaryMetricUseCase.kt new file mode 100644 index 00000000..57ec6cb7 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/usecase/SaveTemporaryMetricUseCase.kt @@ -0,0 +1,40 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.usecase + +import io.github.janbarari.gradle.core.UseCase +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository + +/** + * Saves day build metrics. + * It's temporary and only valid for a day. to measure a valid result from all + * build metrics of the day. + */ +class SaveTemporaryMetricUseCase(private val repo: DatabaseRepository): UseCase() { + + override suspend fun execute(input: BuildMetric): Long { + return repo.saveTemporaryMetric(input) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/usecase/UpdateMetricPipeline.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/usecase/UpdateMetricPipeline.kt new file mode 100644 index 00000000..9ec01c5f --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/usecase/UpdateMetricPipeline.kt @@ -0,0 +1,29 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.usecase + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Pipeline +import io.github.janbarari.gradle.core.Stage + +class UpdateMetricPipeline(firstStage: Stage): Pipeline(firstStage) diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/usecase/UpsertModulesTimelineUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/usecase/UpsertModulesTimelineUseCase.kt new file mode 100644 index 00000000..62937839 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/domain/usecase/UpsertModulesTimelineUseCase.kt @@ -0,0 +1,57 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.domain.usecase + +import com.squareup.moshi.Moshi +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesTimelineMetric +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesTimelineMetricJsonAdapter +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.core.UseCase +import io.github.janbarari.gradle.extension.isNotNull + +class UpsertModulesTimelineUseCase( + private val moshi: Moshi, + private val repo: DatabaseRepository +): UseCase, Boolean>() { + + override suspend fun execute(input: Pair): Boolean { + val branch = input.first + val model = input.second + + if (repo.getSingleMetric(key = "modulesExecTimeline", branch = branch).isNotNull()) { + return repo.updateSingleMetric( + key = "modulesExecTimeline", + branch = branch, + value = ModulesTimelineMetricJsonAdapter(moshi).toJson(model) + ) + } + + return repo.saveSingleMetric( + key = "modulesExecTimeline", + branch = branch, + value = ModulesTimelineMetricJsonAdapter(moshi).toJson(model) + ) + + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/buildstatus/render/CreateBuildStatusReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/buildstatus/render/CreateBuildStatusReportStage.kt new file mode 100644 index 00000000..7e90ac14 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/buildstatus/render/CreateBuildStatusReportStage.kt @@ -0,0 +1,151 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.buildstatus.render + +import io.github.janbarari.gradle.analytics.domain.model.Module +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.report.BuildStatusReport +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.millisToSeconds +import io.github.janbarari.gradle.utils.MathUtils + +class CreateBuildStatusReportStage( + private val modules: List, + private val metrics: List +) : Stage { + + override suspend fun process(input: Report): Report { + return input.apply { + buildStatusReport = BuildStatusReport( + cumulativeOverallBuildProcessBySeconds = getCumulativeOverallBuildProcessInSeconds(), + avgOverallBuildProcessBySeconds = getAvgOverallBuildProcessInSeconds(), + totalBuildProcessCount = metrics.size, + totalProjectModulesCount = modules.size, + cumulativeDependencyResolveBySeconds = getCumulativeDependencyResolveInSeconds(), + cumulativeParallelExecutionBySeconds = getCumulativeParallelExecutionInSeconds(), + avgParallelExecutionRate = getAvgParallelExecutionRate(), + totalFailedBuildCount = getTotalFailedBuildCount(), + totalSucceedBuildCount = getTotalSuccessBuildCount(), + avgCacheHitRate = getAvgCacheHitRate(), + avgInitializationProcessByMillis = getAvgInitializationProcessInMillis(), + avgConfigurationProcessByMillis = getAvgConfigurationProcessInMillis(), + avgExecutionProcessBySeconds = getAvgExecutionProcessInSeconds() + ) + } + } + + fun getCumulativeOverallBuildProcessInSeconds(): Long { + return metrics + .filter { it.overallBuildProcessMetric.isNotNull() } + .sumOf { metric -> + metric.overallBuildProcessMetric!!.median.millisToSeconds() + } + } + + fun getAvgOverallBuildProcessInSeconds(): Long { + return MathUtils.longMedian( + metrics.filter { it.overallBuildProcessMetric.isNotNull() } + .map { metric -> + metric.overallBuildProcessMetric!!.median.millisToSeconds() + } + ) + } + + fun getCumulativeDependencyResolveInSeconds(): Long { + return metrics + .filter { it.dependencyResolveProcessMetric.isNotNull() } + .sumOf { metric -> + metric.dependencyResolveProcessMetric!!.median.millisToSeconds() + } + } + + fun getCumulativeParallelExecutionInSeconds(): Long { + return metrics + .filter { it.executionProcessMetric.isNotNull() && it.parallelExecutionRateMetric.isNotNull() } + .sumOf { metric -> + MathUtils.sumWithPercentage( + metric.executionProcessMetric!!.median.millisToSeconds(), + metric.parallelExecutionRateMetric!!.medianRate.toInt() + ) + } + } + + fun getAvgParallelExecutionRate(): Float { + return MathUtils.floatMedian( + metrics.filter { it.parallelExecutionRateMetric.isNotNull() } + .map { metric -> + metric.parallelExecutionRateMetric!!.medianRate.toFloat() + } + ) + } + + fun getTotalFailedBuildCount(): Int { + return metrics + .filter { it.successBuildRateMetric.isNotNull() } + .sumOf { it.successBuildRateMetric!!.fails } + } + + fun getTotalSuccessBuildCount(): Int { + return metrics + .filter { it.successBuildRateMetric.isNotNull() } + .sumOf { it.successBuildRateMetric!!.successes } + } + + fun getAvgCacheHitRate(): Float { + return MathUtils.floatMedian( + metrics + .filter { it.cacheHitMetric.isNotNull() } + .map { it.cacheHitMetric!!.rate.toFloat() } + ) + } + + fun getAvgInitializationProcessInMillis(): Long { + return MathUtils.longMedian( + metrics.filter { it.initializationProcessMetric.isNotNull() } + .map { metric -> + metric.initializationProcessMetric!!.median + } + ) + } + + fun getAvgConfigurationProcessInMillis(): Long { + return MathUtils.longMedian( + metrics.filter { it.configurationProcessMetric.isNotNull() } + .map { metric -> + metric.configurationProcessMetric!!.median + } + ) + } + + fun getAvgExecutionProcessInSeconds(): Long { + return MathUtils.longMedian( + metrics.filter { it.executionProcessMetric.isNotNull() } + .map { metric -> + metric.executionProcessMetric!!.median.millisToSeconds() + } + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/buildstatus/render/RenderBuildStatusReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/buildstatus/render/RenderBuildStatusReportStage.kt new file mode 100644 index 00000000..39ffdeae --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/buildstatus/render/RenderBuildStatusReportStage.kt @@ -0,0 +1,84 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.buildstatus.render + +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNull +import io.github.janbarari.gradle.extension.round +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.DateTimeUtils +import io.github.janbarari.gradle.utils.HtmlUtils + +class RenderBuildStatusReportStage( + private val report: Report +): Stage { + + companion object { + private const val BUILD_STATUS_METRIC_TEMPLATE_ID = "%build-status-metric%" + private const val BUILD_STATUS_METRIC_TEMPLATE_FILENAME = "build-status-metric-template" + } + + override suspend fun process(input: String): String { + if (report.buildStatusReport.isNull()) + return input.replace(BUILD_STATUS_METRIC_TEMPLATE_ID, getEmptyRender()) + + return input.replace(BUILD_STATUS_METRIC_TEMPLATE_ID, getMetricRender()) + } + + fun getEmptyRender(): String { + return HtmlUtils.renderMessage("Build Status is not available!") + } + + fun getMetricRender(): String { + var renderedTemplate = HtmlUtils.getTemplate(BUILD_STATUS_METRIC_TEMPLATE_FILENAME) + report.buildStatusReport.whenNotNull { + renderedTemplate = renderedTemplate + .replace("%cumulative-build-process-duration%", + DateTimeUtils.convertSecondsToHumanReadableTime(cumulativeOverallBuildProcessBySeconds) + ) + .replace("%avg-build-process-duration%", + DateTimeUtils.convertSecondsToHumanReadableTime(avgOverallBuildProcessBySeconds) + ) + .replace("%total-build-process-count%", totalBuildProcessCount.toString()) + .replace("%total-modules-count%", totalProjectModulesCount.toString()) + .replace("%cumulative-parallel-exec-duration%", + DateTimeUtils.convertSecondsToHumanReadableTime(cumulativeParallelExecutionBySeconds) + ) + .replace("%avg-parallel-exec-rate%", "${avgParallelExecutionRate.round()}%") + .replace("%total-succeed-build-count%", "$totalSucceedBuildCount") + .replace("%total-failed-build-count%", "$totalFailedBuildCount") + .replace("%avg-cache-hit-rate%", "${avgCacheHitRate.round()}%") + .replace("%cumulative-dependency-resolve-duration%", + DateTimeUtils.convertSecondsToHumanReadableTime(cumulativeDependencyResolveBySeconds) + ) + .replace("%avg-initialization-process-duration%", "${avgInitializationProcessByMillis}ms") + .replace("%avg-configuration-process-duration%", "${avgConfigurationProcessByMillis}ms") + .replace("%avg-execution-process-duration%", "${avgExecutionProcessBySeconds}s") + } + return renderedTemplate + } + + + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/cachehit/create/CreateCacheHitMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/cachehit/create/CreateCacheHitMetricStage.kt new file mode 100644 index 00000000..578054fd --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/cachehit/create/CreateCacheHitMetricStage.kt @@ -0,0 +1,42 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.cachehit.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class CreateCacheHitMetricStage( + private val buildInfo: BuildInfo, + private val createCacheHitMetricUseCase: CreateCacheHitMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + if (buildInfo.isSuccessful) { + cacheHitMetric = createCacheHitMetricUseCase.execute(buildInfo) + } + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/cachehit/create/CreateCacheHitMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/cachehit/create/CreateCacheHitMetricUseCase.kt new file mode 100644 index 00000000..2a42c91c --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/cachehit/create/CreateCacheHitMetricUseCase.kt @@ -0,0 +1,72 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.cachehit.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.metric.CacheHitMetric +import io.github.janbarari.gradle.analytics.domain.model.metric.ModuleCacheHit +import io.github.janbarari.gradle.analytics.domain.model.Module +import io.github.janbarari.gradle.core.UseCase +import io.github.janbarari.gradle.extension.toPercentageOf +import io.github.janbarari.gradle.extension.whenEach + +class CreateCacheHitMetricUseCase( + private val modules: List +): UseCase() { + + override suspend fun execute(input: BuildInfo): CacheHitMetric { + var cachedTasksCount = 0 + input.executedTasks.whenEach { + if (!isSkipped) { + if (isUpToDate || isFromCache) { + cachedTasksCount++ + } + } + } + val overallCacheHitRate = cachedTasksCount.toPercentageOf(input.executedTasks.filter { it.isSkipped.not() }.size) + + val modulesCacheHit = mutableListOf() + modules.whenEach { + var moduleCachedTasksCount = 0 + var moduleTasksCount = 0 + input.executedTasks.filter { it.path.startsWith(path) } + .whenEach { + moduleTasksCount++ + if (!isSkipped) { + if (isUpToDate || isFromCache) { + moduleCachedTasksCount++ + } + } + } + val moduleCacheHitRate = moduleCachedTasksCount.toPercentageOf(moduleTasksCount) + modulesCacheHit.add( + ModuleCacheHit( + path = path, + rate = moduleCacheHitRate.toLong() + ) + ) + } + return CacheHitMetric(overallCacheHitRate.toLong(), modulesCacheHit) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/cachehit/report/CreateCacheHitReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/cachehit/report/CreateCacheHitReportStage.kt new file mode 100644 index 00000000..0d8c921a --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/cachehit/report/CreateCacheHitReportStage.kt @@ -0,0 +1,162 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.cachehit.report + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.report.CacheHitReport +import io.github.janbarari.gradle.analytics.domain.model.report.ModuleCacheHit +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.analytics.domain.model.TimespanPoint +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.diffPercentageOf +import io.github.janbarari.gradle.extension.hasMultipleItems +import io.github.janbarari.gradle.extension.hasSingleItem +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.minimize +import io.github.janbarari.gradle.extension.whenEach + +class CreateCacheHitReportStage( + private val metrics: List +) : Stage { + + override suspend fun process(input: Report): Report { + val metrics = metrics.filter { + it.cacheHitMetric.isNotNull() + } + + if (metrics.hasSingleItem()) { + return input.apply { + cacheHitReport = generateSingleItemReport(metrics.single()) + } + } + + if (metrics.hasMultipleItems()) { + return input.apply { + cacheHitReport = generateMultipleItemsReport(metrics) + } + } + + return input + } + + private fun generateSingleItemReport(metric: BuildMetric): CacheHitReport { + val modules = mutableListOf() + val overallHit = metric.cacheHitMetric!!.rate + + val overallHitTimespanChartPoint = TimespanPoint( + value = overallHit, + from = metric.createdAt + ) + + val overallValues = listOf( + overallHitTimespanChartPoint + ) + + metric.cacheHitMetric!!.modules.whenEach { + val values = mutableListOf( + TimespanPoint( + value = rate, from = metric.createdAt + ) + ) + modules.add( + ModuleCacheHit( + path = path, + rate = rate, + diffRate = null, + meanValues = values + ) + ) + } + + return CacheHitReport( + modules = modules.sortedByDescending { it.rate }, + overallRate = overallHit, + overallDiffRate = null, + overallMeanValues = overallValues + ) + } + + private fun generateMultipleItemsReport(metrics: List): CacheHitReport { + val firstCacheHitRatio = metrics.first().cacheHitMetric!!.rate + val lastCacheHitRatio = metrics.last().cacheHitMetric!!.rate + + val overallDiffRatio = firstCacheHitRatio.diffPercentageOf(lastCacheHitRatio) + + val overallHit = metrics.last().cacheHitMetric!!.rate + + val overallValuesTimestampChartPoints = mutableListOf() + metrics.whenEach { + overallValuesTimestampChartPoints.add( + TimespanPoint( + value = cacheHitMetric!!.rate, from = createdAt + ) + ) + } + val overallMeanValues = overallValuesTimestampChartPoints.minimize(12) + + val modules = mutableListOf() + metrics.last().cacheHitMetric!!.modules.whenEach { + modules.add( + ModuleCacheHit( + path = path, + rate = rate, + diffRate = calculateModuleCacheHitDiffRatio(metrics, path, rate), + meanValues = getModuleChartPoints(path) + ) + ) + } + + return CacheHitReport( + modules = modules, + overallMeanValues = overallMeanValues, + overallRate = overallHit, + overallDiffRate = overallDiffRatio + ) + } + + private fun calculateModuleCacheHitDiffRatio(metrics: List, path: String, value: Long): Float? { + return metrics.first().cacheHitMetric!! + .modules.find { it.path == path }?.rate?.diffPercentageOf(value) + } + + private fun getModuleChartPoints(path: String): List { + val timestampChartPoints = mutableListOf() + metrics + .filter { + it.cacheHitMetric.isNotNull() + }.whenEach { + cacheHitMetric!!.modules + .filter { it.path == path } + .whenEach { + timestampChartPoints.add( + TimespanPoint( + value = rate, + from = createdAt + ) + ) + } + } + + return timestampChartPoints.minimize(12) + } +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/cachehit/report/RenderCacheHitReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/cachehit/report/RenderCacheHitReportStage.kt new file mode 100644 index 00000000..ae847748 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/cachehit/report/RenderCacheHitReportStage.kt @@ -0,0 +1,160 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.cachehit.report + +import io.github.janbarari.gradle.analytics.domain.model.report.ModuleCacheHit +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNull +import io.github.janbarari.gradle.extension.mapToChartPoints +import io.github.janbarari.gradle.extension.toArrayString +import io.github.janbarari.gradle.extension.toIntList +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.HtmlUtils + +class RenderCacheHitReportStage( + private val report: Report +) : Stage { + + companion object { + private const val CACHE_HIT_METRIC_TEMPLATE_ID = "%cache-hit-metric%" + private const val CACHE_HIT_METRIC_TEMPLATE_FILE_NAME = "cache-hit-metric-template" + } + + override suspend fun process(input: String): String { + if (report.cacheHitReport.isNull()) + return input.replace(CACHE_HIT_METRIC_TEMPLATE_ID, getEmptyRender()) + + return input.replace(CACHE_HIT_METRIC_TEMPLATE_ID, getMetricRender()) + } + + fun getEmptyRender(): String { + return HtmlUtils.renderMessage("Cache Hit is not available!") + } + + fun getMetricRender(): String { + var renderedTemplate = HtmlUtils.getTemplate(CACHE_HIT_METRIC_TEMPLATE_FILE_NAME) + report.cacheHitReport.whenNotNull { + val tableData = buildString { + modules.forEachIndexed { index, it -> + var diffRatioRender = "-" + it.diffRate.whenNotNull { + diffRatioRender = if (this > 0) + "+${this}%" + else if (this < 0) + "-${this}%" + else + "Equals" + } + append(""" + + ${index + 1} + ${it.path} + ${it.rate}% + $diffRatioRender + + """.trimIndent()) + } + } + + val overallCacheHit = "$overallRate%" + var overallDiffRatioRender = "-" + overallDiffRate.whenNotNull { + overallDiffRatioRender = if (this > 0) + "+${this}%" + else if (this < 0) + "-${this}%" + else + "Equals" + } + + var bestChartValues = "[]" + var worstChartValues = "[]" + var bwLabels = "[]" + if (modules.isNotEmpty()) { + bestChartValues = modules + .first { it.path == getBestModulePath(modules) } + .meanValues + .map { it.value } + .toIntList() + .toString() + + worstChartValues = modules + .first { it.path == getWorstModulePath(modules) } + .meanValues + .map { it.value } + .toIntList() + .toString() + + bwLabels = modules + .first { it.path == getWorstModulePath(modules) } + .meanValues + .mapToChartPoints() + .map { it.description } + .toArrayString() + } + + renderedTemplate = renderedTemplate + .replace("%chart-values%", getOverallChartValues().toString()) + .replace("%chart-labels%", getOverallChartLabels().toArrayString()) + .replace("%table-data%", tableData) + .replace("%overall-cache-hit%", overallCacheHit) + .replace("%overall-diff-rate%", overallDiffRatioRender) + .replace("%best-values%", bestChartValues) + .replace("%worst-values%", worstChartValues) + .replace("%bw-labels%", bwLabels) + .replace("%worst-module-name%", "\"${getWorstModulePath(modules)}\"") + .replace("%best-module-name%", "\"${getBestModulePath(modules)}\"") + } + return renderedTemplate + } + + fun getBestModulePath(modules: List): String? { + if (modules.isEmpty()) return null + return modules.sortedByDescending { module -> + module.meanValues.sumOf { it.value } + }.first().path + } + + fun getWorstModulePath(modules: List): String? { + if (modules.isEmpty()) return null + return modules.sortedByDescending { module -> + module.meanValues.sumOf { it.value } + }.last().path + } + + fun getOverallChartValues(): List { + return report.cacheHitReport!! + .overallMeanValues + .map { it.value } + .toIntList() + } + + fun getOverallChartLabels(): List { + return report.cacheHitReport!! + .overallMeanValues + .mapToChartPoints() + .map { it.description } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/cachehit/update/UpdateCacheHitMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/cachehit/update/UpdateCacheHitMetricStage.kt new file mode 100644 index 00000000..5e942e61 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/cachehit/update/UpdateCacheHitMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.cachehit.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class UpdateCacheHitMetricStage( + private val updateCacheHitMetricUseCase: UpdateCacheHitMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + cacheHitMetric = updateCacheHitMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/cachehit/update/UpdateCacheHitMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/cachehit/update/UpdateCacheHitMetricUseCase.kt new file mode 100644 index 00000000..d69d90c8 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/cachehit/update/UpdateCacheHitMetricUseCase.kt @@ -0,0 +1,66 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.cachehit.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.metric.CacheHitMetric +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.core.UseCaseNoInput +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.modify +import io.github.janbarari.gradle.utils.MathUtils + +class UpdateCacheHitMetricUseCase( + private val repo: DatabaseRepository +) : UseCaseNoInput() { + + override suspend fun execute(): CacheHitMetric? { + val temporaryMetrics = repo.getTemporaryMetrics() + + val hitRates = temporaryMetrics.filter { it.cacheHitMetric.isNotNull() } + .map { it.cacheHitMetric!!.rate } + + val modules = temporaryMetrics.last().cacheHitMetric?.modules?.modify { + rate = getModuleMeanCacheHit(path, temporaryMetrics) + } ?: return null + + return CacheHitMetric( + rate = MathUtils.longMean(hitRates), + modules = modules + ) + } + + private fun getModuleMeanCacheHit(path: String, metrics: List): Long { + val hitRates = metrics + .filter { + it.cacheHitMetric.isNotNull() + && it.cacheHitMetric!!.modules.find { module -> module.path == path }.isNotNull() + } + .map { + it.cacheHitMetric!!.modules.find { module -> module.path == path }!!.rate + } + + return MathUtils.longMean(hitRates) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/configurationprocess/create/CreateConfigurationProcessMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/configurationprocess/create/CreateConfigurationProcessMetricStage.kt new file mode 100644 index 00000000..ff665ac2 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/configurationprocess/create/CreateConfigurationProcessMetricStage.kt @@ -0,0 +1,42 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.configurationprocess.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class CreateConfigurationProcessMetricStage( + private val buildInfo: BuildInfo, + private val createConfigurationProcessMetricUseCase: CreateConfigurationProcessMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + if (buildInfo.isSuccessful) { + configurationProcessMetric = createConfigurationProcessMetricUseCase.execute(buildInfo) + } + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/configurationprocess/create/CreateConfigurationProcessMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/configurationprocess/create/CreateConfigurationProcessMetricUseCase.kt new file mode 100644 index 00000000..e4e0968b --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/configurationprocess/create/CreateConfigurationProcessMetricUseCase.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.configurationprocess.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.metric.ConfigurationProcessMetric +import io.github.janbarari.gradle.core.UseCase + +class CreateConfigurationProcessMetricUseCase: UseCase() { + + override suspend fun execute(input: BuildInfo): ConfigurationProcessMetric { + return ConfigurationProcessMetric( + median = input.getConfigurationDuration().toMillis(), + mean = input.getConfigurationDuration().toMillis() + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/configurationprocess/report/CreateConfigurationProcessReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/configurationprocess/report/CreateConfigurationProcessReportStage.kt new file mode 100644 index 00000000..62f59a00 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/configurationprocess/report/CreateConfigurationProcessReportStage.kt @@ -0,0 +1,68 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.configurationprocess.report + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.report.ConfigurationProcessReport +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isBiggerEquals +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.mapToConfigurationMeanTimespanChartPoints +import io.github.janbarari.gradle.extension.mapToConfigurationMedianTimespanChartPoints +import io.github.janbarari.gradle.extension.whenEmpty + +class CreateConfigurationProcessReportStage( + private val metrics: List +) : Stage { + + companion object { + private const val SKIP_THRESHOLD_IN_MS = 50L + } + + override suspend fun process(input: Report): Report { + val medianChartPoints = metrics.filter { metric -> + metric.configurationProcessMetric.isNotNull() && + metric.configurationProcessMetric?.median?.isBiggerEquals(SKIP_THRESHOLD_IN_MS) ?: false + }.mapToConfigurationMedianTimespanChartPoints() + .whenEmpty { + return input + } + + val meanChartPoints = metrics.filter { metric -> + metric.configurationProcessMetric.isNotNull() && + metric.configurationProcessMetric?.mean?.isBiggerEquals(SKIP_THRESHOLD_IN_MS) ?: false + }.mapToConfigurationMeanTimespanChartPoints() + .whenEmpty { + return input + } + + return input.apply { + configurationProcessReport = ConfigurationProcessReport( + medianValues = medianChartPoints, + meanValues = meanChartPoints, + ) + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/configurationprocess/report/RenderConfigurationProcessReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/configurationprocess/report/RenderConfigurationProcessReportStage.kt new file mode 100644 index 00000000..1818c251 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/configurationprocess/report/RenderConfigurationProcessReportStage.kt @@ -0,0 +1,102 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.configurationprocess.report + +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNull +import io.github.janbarari.gradle.extension.mapToChartPoints +import io.github.janbarari.gradle.extension.maxValue +import io.github.janbarari.gradle.extension.minValue +import io.github.janbarari.gradle.extension.minimize +import io.github.janbarari.gradle.extension.toArrayString +import io.github.janbarari.gradle.extension.toIntList +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.HtmlUtils +import io.github.janbarari.gradle.utils.MathUtils + +/** + * Generates html result for [io.github.janbarari.gradle.analytics.domain.model.report.ConfigurationProcessReport]. + */ +class RenderConfigurationProcessReportStage( + private val report: Report +) : Stage { + + companion object { + private const val CHART_MAX_COLUMNS = 12 + private const val CHART_SUGGESTED_MIN_MAX_PERCENTAGE = 30 + private const val CONFIGURATION_METRIC_TEMPLATE_ID = "%configuration-process-metric%" + private const val CONFIGURATION_METRIC_TEMPLATE_FILE_NAME = "configuration-process-metric-template" + } + + override suspend fun process(input: String): String { + if (report.configurationProcessReport.isNull()) { + return input.replace(CONFIGURATION_METRIC_TEMPLATE_ID, getEmptyRender()) + } + + return input.replace(CONFIGURATION_METRIC_TEMPLATE_ID, getMetricRender()) + } + + fun getMetricRender(): String { + var renderedTemplate = HtmlUtils.getTemplate(CONFIGURATION_METRIC_TEMPLATE_FILE_NAME) + report.configurationProcessReport.whenNotNull { + val medianChartValues = medianValues + .minimize(CHART_MAX_COLUMNS) + .mapToChartPoints() + .map { it.value } + .toIntList() + .toString() + + val meanChartValues = meanValues + .minimize(CHART_MAX_COLUMNS) + .mapToChartPoints() + .map { it.value } + .toIntList() + .toString() + + val chartLabels = medianValues + .minimize(CHART_MAX_COLUMNS) + .mapToChartPoints() + .map { it.description } + .toArrayString() + + val maximumValue = Math.max(medianValues.maxValue(), meanValues.maxValue()) + val minimumValue = Math.min(medianValues.minValue(), meanValues.minValue()) + val chartSuggestedMaxValue = MathUtils.sumWithPercentage(maximumValue, CHART_SUGGESTED_MIN_MAX_PERCENTAGE) + val chartSuggestedMinValue = MathUtils.deductWithPercentage(minimumValue, CHART_SUGGESTED_MIN_MAX_PERCENTAGE) + + renderedTemplate = renderedTemplate + .replace("%suggested-max-value%", chartSuggestedMaxValue.toString()) + .replace("%suggested-min-value%", chartSuggestedMinValue.toString()) + .replace("%chart-median-values%", medianChartValues) + .replace("%chart-mean-values%", meanChartValues) + .replace("%chart-labels%", chartLabels) + } + return renderedTemplate + } + + fun getEmptyRender(): String { + return HtmlUtils.renderMessage("Configuration Process is not available!") + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/configurationprocess/update/UpdateConfigurationProcessMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/configurationprocess/update/UpdateConfigurationProcessMetricStage.kt new file mode 100644 index 00000000..834ec99f --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/configurationprocess/update/UpdateConfigurationProcessMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.configurationprocess.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class UpdateConfigurationProcessMetricStage( + private val updateConfigurationProcessMetricUseCase: UpdateConfigurationProcessMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + configurationProcessMetric = updateConfigurationProcessMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/configurationprocess/update/UpdateConfigurationProcessMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/configurationprocess/update/UpdateConfigurationProcessMetricUseCase.kt new file mode 100644 index 00000000..6bb6257b --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/configurationprocess/update/UpdateConfigurationProcessMetricUseCase.kt @@ -0,0 +1,66 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.configurationprocess.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.ConfigurationProcessMetric +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.core.UseCaseNoInput +import io.github.janbarari.gradle.extension.isBiggerEquals +import io.github.janbarari.gradle.extension.whenEach +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.extension.whenTrue +import io.github.janbarari.gradle.utils.MathUtils + +/** + * Generates a new metric with Median mathematics based on temporary metrics. + */ +class UpdateConfigurationProcessMetricUseCase( + private val repo: DatabaseRepository +) : UseCaseNoInput() { + + companion object { + private const val SKIP_THRESHOLD_IN_MS = 50L + } + + override suspend fun execute(): ConfigurationProcessMetric { + val medianValues = mutableListOf() + val meanValues = mutableListOf() + repo.getTemporaryMetrics().whenEach { + configurationProcessMetric.whenNotNull { + // In order to have accurate metric, don't add metric value in Median dataset if it's under 50 milliseconds. + median.isBiggerEquals(SKIP_THRESHOLD_IN_MS).whenTrue { + medianValues.add(median) + } + mean.isBiggerEquals(SKIP_THRESHOLD_IN_MS).whenTrue { + meanValues.add(mean) + } + } + } + + return ConfigurationProcessMetric( + median = MathUtils.longMedian(medianValues), + mean = MathUtils.longMean(meanValues) + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencydetails/create/CreateDependencyDetailsMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencydetails/create/CreateDependencyDetailsMetricStage.kt new file mode 100644 index 00000000..10c131f3 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencydetails/create/CreateDependencyDetailsMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.dependencydetails.create + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class CreateDependencyDetailsMetricStage( + private val createDependencyDetailsMetricUseCase: CreateDependencyDetailsMetricUseCase +) : Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + dependencyDetailsMetric = createDependencyDetailsMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencydetails/create/CreateDependencyDetailsMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencydetails/create/CreateDependencyDetailsMetricUseCase.kt new file mode 100644 index 00000000..da65467d --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencydetails/create/CreateDependencyDetailsMetricUseCase.kt @@ -0,0 +1,39 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.dependencydetails.create + +import io.github.janbarari.gradle.analytics.domain.model.Dependency +import io.github.janbarari.gradle.analytics.domain.model.metric.DependencyDetailsMetric +import io.github.janbarari.gradle.core.UseCaseNoInput + +class CreateDependencyDetailsMetricUseCase( + private val thirdPartyDependencies: List +) : UseCaseNoInput() { + + override suspend fun execute(): DependencyDetailsMetric { + return DependencyDetailsMetric( + dependencies = thirdPartyDependencies + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencydetails/render/CreateDependencyDetailsReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencydetails/render/CreateDependencyDetailsReportStage.kt new file mode 100644 index 00000000..039d4258 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencydetails/render/CreateDependencyDetailsReportStage.kt @@ -0,0 +1,46 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.dependencydetails.render + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.report.DependencyDetailsReport +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.whenNotNull + +class CreateDependencyDetailsReportStage( + private val metrics: List +): Stage { + + override suspend fun process(input: Report): Report { + return input.apply { + metrics.last().dependencyDetailsMetric.whenNotNull { + dependencyDetailsReport = DependencyDetailsReport( + dependencies = dependencies, + cumulativeDependenciesSizeByKb = dependencies.sumOf { it.sizeByKb } + ) + } + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencydetails/render/RenderDependencyDetailsReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencydetails/render/RenderDependencyDetailsReportStage.kt new file mode 100644 index 00000000..e1efd632 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencydetails/render/RenderDependencyDetailsReportStage.kt @@ -0,0 +1,91 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.dependencydetails.render + +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNull +import io.github.janbarari.gradle.extension.toArrayString +import io.github.janbarari.gradle.extension.whenEach +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.HtmlUtils + +class RenderDependencyDetailsReportStage( + private val report: Report +) : Stage { + + companion object { + private const val DEPENDENCY_DETAILS_METRIC_TEMPLATE_ID = "%dependency-details-metric%" + private const val DEPENDENCY_DETAILS_METRIC_TEMPLATE_FILE_NAME = "dependency-details-metric-template" + } + + override suspend fun process(input: String): String { + if (report.dependencyDetailsReport.isNull()) return input.replace( + DEPENDENCY_DETAILS_METRIC_TEMPLATE_ID, getEmptyRender() + ) + + return input.replace(DEPENDENCY_DETAILS_METRIC_TEMPLATE_ID, getMetricRender()) + } + + fun getEmptyRender(): String { + return HtmlUtils.renderMessage("Dependency Details is not available!") + } + + fun getMetricRender(): String { + var renderedTemplate = HtmlUtils.getTemplate(DEPENDENCY_DETAILS_METRIC_TEMPLATE_FILE_NAME) + report.dependencyDetailsReport.whenNotNull { + val chartLabels = mutableListOf() + val chartDataset = mutableListOf() + dependencies.whenEach { + chartLabels.add("$moduleGroup:$moduleName") + chartDataset.add(sizeByKb) + } + + val tableDataset = buildString { + dependencies + .filter { it.sizeByKb > 0 } + .sortedByDescending { it.sizeByKb } + .forEachIndexed { index, dependency -> + append("") + append("${index + 1}") + append("${dependency.name}") + append("${dependency.sizeByKb}kb") + append("") + } + } + + renderedTemplate = if (chartDataset.sum() > 1000) { + renderedTemplate.replace("%cumulative-dependencies-size%", "%sMb".format((chartDataset.sum() / 1024))) + } else { + renderedTemplate.replace("%cumulative-dependencies-size%", "%skb".format(chartDataset.sum())) + } + + renderedTemplate = renderedTemplate + .replace("%table-dataset%", tableDataset) + .replace("%chart-labels%", chartLabels.toArrayString()) + .replace("%chart-dataset%", chartDataset.toString()) + } + return renderedTemplate + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencydetails/update/UpdateDependencyDetailsMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencydetails/update/UpdateDependencyDetailsMetricStage.kt new file mode 100644 index 00000000..c8d4f725 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencydetails/update/UpdateDependencyDetailsMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.dependencydetails.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class UpdateDependencyDetailsMetricStage( + private val updateDependencyDetailsMetricUseCase: UpdateDependencyDetailsMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + dependencyDetailsMetric = updateDependencyDetailsMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencydetails/update/UpdateDependencyDetailsMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencydetails/update/UpdateDependencyDetailsMetricUseCase.kt new file mode 100644 index 00000000..dc409475 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencydetails/update/UpdateDependencyDetailsMetricUseCase.kt @@ -0,0 +1,37 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.dependencydetails.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.DependencyDetailsMetric +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.core.UseCaseNoInput + +class UpdateDependencyDetailsMetricUseCase( + private val repo: DatabaseRepository +): UseCaseNoInput() { + + override suspend fun execute(): DependencyDetailsMetric { + return repo.getTemporaryMetrics().last().dependencyDetailsMetric!! + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencyresolveprocess/create/CreateDependencyResolveProcessMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencyresolveprocess/create/CreateDependencyResolveProcessMetricStage.kt new file mode 100644 index 00000000..b1fac293 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencyresolveprocess/create/CreateDependencyResolveProcessMetricStage.kt @@ -0,0 +1,43 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.dependencyresolveprocess.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class CreateDependencyResolveProcessMetricStage( + private val buildInfo: BuildInfo, + private val createDependencyResolveProcessMetricUseCase: CreateDependencyResolveProcessMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + if (buildInfo.isSuccessful) { + dependencyResolveProcessMetric = createDependencyResolveProcessMetricUseCase.execute(buildInfo) + } + } + } + +} + diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencyresolveprocess/create/CreateDependencyResolveProcessMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencyresolveprocess/create/CreateDependencyResolveProcessMetricUseCase.kt new file mode 100644 index 00000000..cd37cdd5 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencyresolveprocess/create/CreateDependencyResolveProcessMetricUseCase.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.dependencyresolveprocess.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.metric.DependencyResolveProcessMetric +import io.github.janbarari.gradle.core.UseCase + +class CreateDependencyResolveProcessMetricUseCase: UseCase() { + + override suspend fun execute(input: BuildInfo): DependencyResolveProcessMetric { + return DependencyResolveProcessMetric( + median = input.getTotalDependenciesResolveDuration().toMillis(), + mean = input.getTotalDependenciesResolveDuration().toMillis() + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencyresolveprocess/report/CreateDependencyResolveProcessReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencyresolveprocess/report/CreateDependencyResolveProcessReportStage.kt new file mode 100644 index 00000000..379a984a --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencyresolveprocess/report/CreateDependencyResolveProcessReportStage.kt @@ -0,0 +1,68 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.dependencyresolveprocess.report + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.report.DependencyResolveProcessReport +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isBiggerEquals +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.mapToDependencyResolveMeanTimespanChartPoints +import io.github.janbarari.gradle.extension.mapToDependencyResolveMedianTimespanChartPoints +import io.github.janbarari.gradle.extension.whenEmpty + +class CreateDependencyResolveProcessReportStage( + private val metrics: List +) : Stage { + + companion object { + private const val SKIP_THRESHOLD_IN_MS = 50L + } + + override suspend fun process(input: Report): Report { + val medianChartPoints = metrics.filter { metric -> + metric.dependencyResolveProcessMetric.isNotNull() && + metric.dependencyResolveProcessMetric?.median?.isBiggerEquals(SKIP_THRESHOLD_IN_MS) ?: false + }.mapToDependencyResolveMedianTimespanChartPoints() + .whenEmpty { + return input + } + + val meanChartPoints = metrics.filter { metric -> + metric.dependencyResolveProcessMetric.isNotNull() && + metric.dependencyResolveProcessMetric?.mean?.isBiggerEquals(SKIP_THRESHOLD_IN_MS) ?: false + }.mapToDependencyResolveMeanTimespanChartPoints() + .whenEmpty { + return input + } + + return input.apply { + dependencyResolveProcessReport = DependencyResolveProcessReport( + medianValues = medianChartPoints, + meanValues = meanChartPoints, + ) + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencyresolveprocess/report/RenderDependencyResolveProcessReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencyresolveprocess/report/RenderDependencyResolveProcessReportStage.kt new file mode 100644 index 00000000..5bbbb74a --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencyresolveprocess/report/RenderDependencyResolveProcessReportStage.kt @@ -0,0 +1,102 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.dependencyresolveprocess.report + +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNull +import io.github.janbarari.gradle.extension.mapToChartPoints +import io.github.janbarari.gradle.extension.maxValue +import io.github.janbarari.gradle.extension.minValue +import io.github.janbarari.gradle.extension.minimize +import io.github.janbarari.gradle.extension.toArrayString +import io.github.janbarari.gradle.extension.toIntList +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.HtmlUtils +import io.github.janbarari.gradle.utils.MathUtils + +/** + * Generates html result for [io.github.janbarari.gradle.analytics.domain.model.report.DependencyResolveProcessReport]. + */ +class RenderDependencyResolveProcessReportStage( + private val report: Report +) : Stage { + + companion object { + private const val CHART_MAX_COLUMNS = 12 + private const val CHART_SUGGESTED_MIN_MAX_PERCENTAGE = 30 + private const val DEPENDENCY_RESOLVE_METRIC_TEMPLATE_ID = "%dependency-resolve-process-metric%" + private const val DEPENDENCY_RESOLVE_METRIC_TEMPLATE_FILE_NAME = "dependency-resolve-process-metric-template" + } + + override suspend fun process(input: String): String { + if (report.dependencyResolveProcessReport.isNull()) { + return input.replace(DEPENDENCY_RESOLVE_METRIC_TEMPLATE_ID, getEmptyRender()) + } + + return input.replace(DEPENDENCY_RESOLVE_METRIC_TEMPLATE_ID, getMetricRender()) + } + + fun getMetricRender(): String { + var renderedTemplate = HtmlUtils.getTemplate(DEPENDENCY_RESOLVE_METRIC_TEMPLATE_FILE_NAME) + report.dependencyResolveProcessReport.whenNotNull { + val medianChartValues = medianValues + .minimize(CHART_MAX_COLUMNS) + .mapToChartPoints() + .map { it.value } + .toIntList() + .toString() + + val meanChartValues = meanValues + .minimize(CHART_MAX_COLUMNS) + .mapToChartPoints() + .map { it.value } + .toIntList() + .toString() + + val chartLabels = medianValues + .minimize(CHART_MAX_COLUMNS) + .mapToChartPoints() + .map { it.description } + .toArrayString() + + val maximumValue = Math.max(medianValues.maxValue(), meanValues.maxValue()) + val minimumValue = Math.min(medianValues.minValue(), meanValues.minValue()) + val chartSuggestedMaxValue = MathUtils.sumWithPercentage(maximumValue, CHART_SUGGESTED_MIN_MAX_PERCENTAGE) + val chartSuggestedMinValue = MathUtils.deductWithPercentage(minimumValue, CHART_SUGGESTED_MIN_MAX_PERCENTAGE) + + renderedTemplate = renderedTemplate + .replace("%suggested-max-value%", chartSuggestedMaxValue.toString()) + .replace("%suggested-min-value%", chartSuggestedMinValue.toString()) + .replace("%chart-median-values%", medianChartValues) + .replace("%chart-mean-values%", meanChartValues) + .replace("%chart-labels%", chartLabels) + } + return renderedTemplate + } + + fun getEmptyRender(): String { + return HtmlUtils.renderMessage("Dependency Resolve Process is not available!") + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencyresolveprocess/update/UpdateDependencyResolveProcessMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencyresolveprocess/update/UpdateDependencyResolveProcessMetricStage.kt new file mode 100644 index 00000000..a975e44f --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencyresolveprocess/update/UpdateDependencyResolveProcessMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.dependencyresolveprocess.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class UpdateDependencyResolveProcessMetricStage( + private val updateDependencyResolveProcessMetricUseCase: UpdateDependencyResolveProcessMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + dependencyResolveProcessMetric = updateDependencyResolveProcessMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencyresolveprocess/update/UpdateDependencyResolveProcessMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencyresolveprocess/update/UpdateDependencyResolveProcessMetricUseCase.kt new file mode 100644 index 00000000..3d227899 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/dependencyresolveprocess/update/UpdateDependencyResolveProcessMetricUseCase.kt @@ -0,0 +1,66 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.dependencyresolveprocess.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.DependencyResolveProcessMetric +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.core.UseCaseNoInput +import io.github.janbarari.gradle.extension.isBiggerEquals +import io.github.janbarari.gradle.extension.whenEach +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.extension.whenTrue +import io.github.janbarari.gradle.utils.MathUtils + +/** + * Generates a new metric with Median mathematics based on temporary metrics. + */ +class UpdateDependencyResolveProcessMetricUseCase( + private val repo: DatabaseRepository +): UseCaseNoInput() { + + companion object { + private const val SKIP_THRESHOLD_IN_MS = 50L + } + + override suspend fun execute(): DependencyResolveProcessMetric { + val medianValues = mutableListOf() + val meanValues = mutableListOf() + repo.getTemporaryMetrics().whenEach { + dependencyResolveProcessMetric.whenNotNull { + // In order to have accurate metric, don't add metric value in Median dataset if it's under 50 milliseconds. + median.isBiggerEquals(SKIP_THRESHOLD_IN_MS).whenTrue { + medianValues.add(median) + } + mean.isBiggerEquals(SKIP_THRESHOLD_IN_MS).whenTrue { + meanValues.add(mean) + } + } + } + + return DependencyResolveProcessMetric( + median = MathUtils.longMedian(medianValues), + mean = MathUtils.longMean(meanValues) + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/executionprocess/create/CreateExecutionProcessMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/executionprocess/create/CreateExecutionProcessMetricStage.kt new file mode 100644 index 00000000..5d0adb6b --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/executionprocess/create/CreateExecutionProcessMetricStage.kt @@ -0,0 +1,42 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.executionprocess.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class CreateExecutionProcessMetricStage( + private val buildInfo: BuildInfo, + private val createExecutionProcessMetricUseCase: CreateExecutionProcessMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + if (buildInfo.isSuccessful) { + executionProcessMetric = createExecutionProcessMetricUseCase.execute(buildInfo) + } + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/executionprocess/create/CreateExecutionProcessMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/executionprocess/create/CreateExecutionProcessMetricUseCase.kt new file mode 100644 index 00000000..ca6424cb --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/executionprocess/create/CreateExecutionProcessMetricUseCase.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.executionprocess.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.metric.ExecutionProcessMetric +import io.github.janbarari.gradle.core.UseCase + +class CreateExecutionProcessMetricUseCase: UseCase() { + + override suspend fun execute(input: BuildInfo): ExecutionProcessMetric { + return ExecutionProcessMetric( + median = input.getExecutionDuration().toMillis(), + mean = input.getExecutionDuration().toMillis() + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/executionprocess/report/CreateExecutionProcessReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/executionprocess/report/CreateExecutionProcessReportStage.kt new file mode 100644 index 00000000..f742baca --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/executionprocess/report/CreateExecutionProcessReportStage.kt @@ -0,0 +1,67 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.executionprocess.report + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.report.ExecutionProcessReport +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isBiggerEquals +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.mapToExecutionMeanTimespanChartPoints +import io.github.janbarari.gradle.extension.mapToExecutionMedianTimespanChartPoints +import io.github.janbarari.gradle.extension.whenEmpty + +class CreateExecutionProcessReportStage( + private val metrics: List +) : Stage { + + companion object { + private const val SKIP_THRESHOLD_IN_MS = 50L + } + + override suspend fun process(input: Report): Report { + val medianChartPoints = metrics.filter { metric -> + metric.executionProcessMetric.isNotNull() && + metric.executionProcessMetric?.median?.isBiggerEquals(SKIP_THRESHOLD_IN_MS) ?: false + }.mapToExecutionMedianTimespanChartPoints() + .whenEmpty { + return input + } + + val meanChartPoints = metrics.filter { metric -> + metric.executionProcessMetric.isNotNull() && + metric.executionProcessMetric?.mean?.isBiggerEquals(SKIP_THRESHOLD_IN_MS) ?: false + }.mapToExecutionMeanTimespanChartPoints() + .whenEmpty { + return input + } + + return input.apply { + executionProcessReport = ExecutionProcessReport( + medianValues = medianChartPoints, + meanValues = meanChartPoints, + ) + } + } +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/executionprocess/report/RenderExecutionProcessReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/executionprocess/report/RenderExecutionProcessReportStage.kt new file mode 100644 index 00000000..a251ee3c --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/executionprocess/report/RenderExecutionProcessReportStage.kt @@ -0,0 +1,98 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.executionprocess.report + +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNull +import io.github.janbarari.gradle.extension.mapToChartPoints +import io.github.janbarari.gradle.extension.maxValue +import io.github.janbarari.gradle.extension.minValue +import io.github.janbarari.gradle.extension.minimize +import io.github.janbarari.gradle.extension.toArrayString +import io.github.janbarari.gradle.extension.toIntList +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.HtmlUtils +import io.github.janbarari.gradle.utils.MathUtils + +class RenderExecutionProcessReportStage( + private val report: Report +) : Stage { + + companion object { + private const val CHART_MAX_COLUMNS = 12 + private const val CHART_SUGGESTED_MIN_MAX_PERCENTAGE = 30 + private const val EXECUTION_METRIC_TEMPLATE_ID = "%execution-process-metric%" + private const val EXECUTION_METRIC_TEMPLATE_FILE_NAME = "execution-process-metric-template" + } + + override suspend fun process(input: String): String { + if (report.executionProcessReport.isNull()) + return input.replace(EXECUTION_METRIC_TEMPLATE_ID, getEmptyRender()) + + return input.replace(EXECUTION_METRIC_TEMPLATE_ID, getMetricRender()) + } + + fun getEmptyRender(): String { + return HtmlUtils.renderMessage("Execution Process is not available!") + } + + fun getMetricRender(): String { + var renderedTemplate = HtmlUtils.getTemplate(EXECUTION_METRIC_TEMPLATE_FILE_NAME) + report.executionProcessReport.whenNotNull { + val medianChartValues = medianValues + .minimize(CHART_MAX_COLUMNS) + .mapToChartPoints() + .map { it.value } + .toIntList() + .toString() + + val meanChartValues = meanValues + .minimize(CHART_MAX_COLUMNS) + .mapToChartPoints() + .map { it.value } + .toIntList() + .toString() + + val chartLabels = medianValues + .minimize(CHART_MAX_COLUMNS) + .mapToChartPoints() + .map { it.description } + .toArrayString() + + val maximumValue = Math.max(medianValues.maxValue(), meanValues.maxValue()) + val minimumValue = Math.min(medianValues.minValue(), meanValues.minValue()) + val chartSuggestedMaxValue = MathUtils.sumWithPercentage(maximumValue, CHART_SUGGESTED_MIN_MAX_PERCENTAGE) + val chartSuggestedMinValue = MathUtils.deductWithPercentage(minimumValue, CHART_SUGGESTED_MIN_MAX_PERCENTAGE) + + renderedTemplate = renderedTemplate + .replace("%suggested-max-value%", chartSuggestedMaxValue.toString()) + .replace("%suggested-min-value%", chartSuggestedMinValue.toString()) + .replace("%chart-median-values%", medianChartValues) + .replace("%chart-mean-values%", meanChartValues) + .replace("%chart-labels%", chartLabels) + } + return renderedTemplate + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/executionprocess/update/UpdateExecutionProcessMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/executionprocess/update/UpdateExecutionProcessMetricStage.kt new file mode 100644 index 00000000..1b5dddf7 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/executionprocess/update/UpdateExecutionProcessMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.executionprocess.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class UpdateExecutionProcessMetricStage( + private val updateExecutionProcessMetricUseCase: UpdateExecutionProcessMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + executionProcessMetric = updateExecutionProcessMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/executionprocess/update/UpdateExecutionProcessMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/executionprocess/update/UpdateExecutionProcessMetricUseCase.kt new file mode 100644 index 00000000..a7700d43 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/executionprocess/update/UpdateExecutionProcessMetricUseCase.kt @@ -0,0 +1,63 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.executionprocess.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.ExecutionProcessMetric +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.core.UseCaseNoInput +import io.github.janbarari.gradle.extension.isBiggerEquals +import io.github.janbarari.gradle.extension.whenEach +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.extension.whenTrue +import io.github.janbarari.gradle.utils.MathUtils + +class UpdateExecutionProcessMetricUseCase( + private val repo: DatabaseRepository +): UseCaseNoInput() { + + companion object { + private const val SKIP_THRESHOLD_IN_MS = 50L + } + + override suspend fun execute(): ExecutionProcessMetric { + val medianValues = mutableListOf() + val meanValues = mutableListOf() + repo.getTemporaryMetrics().whenEach { + executionProcessMetric.whenNotNull { + // In order to have accurate metric, don't add metric value in Median dataset if it's under 50 milliseconds. + median.isBiggerEquals(SKIP_THRESHOLD_IN_MS).whenTrue { + medianValues.add(median) + } + mean.isBiggerEquals(SKIP_THRESHOLD_IN_MS).whenTrue { + meanValues.add(mean) + } + } + } + + return ExecutionProcessMetric( + median = MathUtils.longMedian(medianValues), + mean = MathUtils.longMean(meanValues) + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/initializationprocess/create/CreateInitializationProcessMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/initializationprocess/create/CreateInitializationProcessMetricStage.kt new file mode 100644 index 00000000..b7005a2c --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/initializationprocess/create/CreateInitializationProcessMetricStage.kt @@ -0,0 +1,42 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.initializationprocess.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class CreateInitializationProcessMetricStage( + private val buildInfo: BuildInfo, + private val createInitializationProcessMetricUseCase: CreateInitializationProcessMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + if (buildInfo.isSuccessful) { + initializationProcessMetric = createInitializationProcessMetricUseCase.execute(buildInfo) + } + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/initializationprocess/create/CreateInitializationProcessMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/initializationprocess/create/CreateInitializationProcessMetricUseCase.kt new file mode 100644 index 00000000..d08fe890 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/initializationprocess/create/CreateInitializationProcessMetricUseCase.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.initializationprocess.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.core.UseCase +import io.github.janbarari.gradle.analytics.domain.model.metric.InitializationProcessMetric + +class CreateInitializationProcessMetricUseCase: UseCase() { + + override suspend fun execute(input: BuildInfo): InitializationProcessMetric { + return InitializationProcessMetric( + median = input.getInitializationDuration().toMillis(), + mean = input.getInitializationDuration().toMillis() + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/initializationprocess/report/CreateInitializationProcessReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/initializationprocess/report/CreateInitializationProcessReportStage.kt new file mode 100644 index 00000000..a08ba3de --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/initializationprocess/report/CreateInitializationProcessReportStage.kt @@ -0,0 +1,68 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.initializationprocess.report + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.report.InitializationProcessReport +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isBiggerEquals +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.mapToInitializationMeanTimespanChartPoints +import io.github.janbarari.gradle.extension.mapToInitializationMedianTimespanChartPoints +import io.github.janbarari.gradle.extension.whenEmpty + +class CreateInitializationProcessReportStage( + private val metrics: List +) : Stage { + + companion object { + private const val SKIP_THRESHOLD_IN_MS = 50L + } + + override suspend fun process(input: Report): Report { + val medianChartPoints = metrics.filter { metric -> + metric.initializationProcessMetric.isNotNull() && + metric.initializationProcessMetric?.median?.isBiggerEquals(SKIP_THRESHOLD_IN_MS) ?: false + }.mapToInitializationMedianTimespanChartPoints() + .whenEmpty { + return input + } + + val meanChartPoints = metrics.filter { metric -> + metric.initializationProcessMetric.isNotNull() && + metric.initializationProcessMetric?.mean?.isBiggerEquals(SKIP_THRESHOLD_IN_MS) ?: false + }.mapToInitializationMeanTimespanChartPoints() + .whenEmpty { + return input + } + + return input.apply { + initializationProcessReport = InitializationProcessReport( + medianValues = medianChartPoints, + meanValues = meanChartPoints, + ) + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/initializationprocess/report/RenderInitializationProcessReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/initializationprocess/report/RenderInitializationProcessReportStage.kt new file mode 100644 index 00000000..02bee91b --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/initializationprocess/report/RenderInitializationProcessReportStage.kt @@ -0,0 +1,98 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.initializationprocess.report + +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNull +import io.github.janbarari.gradle.extension.mapToChartPoints +import io.github.janbarari.gradle.extension.maxValue +import io.github.janbarari.gradle.extension.minValue +import io.github.janbarari.gradle.extension.minimize +import io.github.janbarari.gradle.extension.toArrayString +import io.github.janbarari.gradle.extension.toIntList +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.HtmlUtils +import io.github.janbarari.gradle.utils.MathUtils + +class RenderInitializationProcessReportStage( + private val report: Report +) : Stage { + + companion object { + private const val CHART_MAX_COLUMNS = 12 + private const val CHART_SUGGESTED_MIN_MAX_PERCENTAGE = 30 + private const val INITIALIZATION_METRIC_TEMPLATE_ID = "%initialization-process-metric%" + private const val INITIALIZATION_METRIC_TEMPLATE_FILE_NAME = "initialization-process-metric-template" + } + + override suspend fun process(input: String): String { + if (report.initializationProcessReport.isNull()) + return input.replace(INITIALIZATION_METRIC_TEMPLATE_ID, getEmptyRender()) + + return input.replace(INITIALIZATION_METRIC_TEMPLATE_ID, getMetricRender()) + } + + fun getEmptyRender(): String { + return HtmlUtils.renderMessage("Initialization Process is not available!") + } + + fun getMetricRender(): String { + var renderedTemplate = HtmlUtils.getTemplate(INITIALIZATION_METRIC_TEMPLATE_FILE_NAME) + report.initializationProcessReport.whenNotNull { + val medianChartValues = medianValues + .minimize(CHART_MAX_COLUMNS) + .mapToChartPoints() + .map { it.value } + .toIntList() + .toString() + + val meanChartValues = meanValues + .minimize(CHART_MAX_COLUMNS) + .mapToChartPoints() + .map { it.value } + .toIntList() + .toString() + + val chartLabels = medianValues + .minimize(CHART_MAX_COLUMNS) + .mapToChartPoints() + .map { it.description } + .toArrayString() + + val maximumValue = Math.max(medianValues.maxValue(), meanValues.maxValue()) + val minimumValue = Math.min(medianValues.minValue(), meanValues.minValue()) + val chartSuggestedMaxValue = MathUtils.sumWithPercentage(maximumValue, CHART_SUGGESTED_MIN_MAX_PERCENTAGE) + val chartSuggestedMinValue = MathUtils.deductWithPercentage(minimumValue, CHART_SUGGESTED_MIN_MAX_PERCENTAGE) + + renderedTemplate = renderedTemplate + .replace("%suggested-max-value%", chartSuggestedMaxValue.toString()) + .replace("%suggested-min-value%", chartSuggestedMinValue.toString()) + .replace("%chart-median-values%", medianChartValues) + .replace("%chart-mean-values%", meanChartValues) + .replace("%chart-labels%", chartLabels) + } + return renderedTemplate + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/initializationprocess/update/UpdateInitializationProcessMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/initializationprocess/update/UpdateInitializationProcessMetricStage.kt new file mode 100644 index 00000000..e2967ce1 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/initializationprocess/update/UpdateInitializationProcessMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.initializationprocess.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class UpdateInitializationProcessMetricStage( + private val updateInitializationProcessMetricUseCase: UpdateInitializationProcessMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + initializationProcessMetric = updateInitializationProcessMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/initializationprocess/update/UpdateInitializationProcessMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/initializationprocess/update/UpdateInitializationProcessMetricUseCase.kt new file mode 100644 index 00000000..ac5591b6 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/initializationprocess/update/UpdateInitializationProcessMetricUseCase.kt @@ -0,0 +1,63 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.initializationprocess.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.InitializationProcessMetric +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.core.UseCaseNoInput +import io.github.janbarari.gradle.extension.isBiggerEquals +import io.github.janbarari.gradle.extension.whenEach +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.extension.whenTrue +import io.github.janbarari.gradle.utils.MathUtils + +class UpdateInitializationProcessMetricUseCase( + private val repo: DatabaseRepository +) : UseCaseNoInput() { + + companion object { + private const val SKIP_THRESHOLD_IN_MS = 50L + } + + override suspend fun execute(): InitializationProcessMetric { + val medianValues = mutableListOf() + val meanValues = mutableListOf() + repo.getTemporaryMetrics().whenEach { + initializationProcessMetric.whenNotNull { + // In order to have accurate metric, don't add metric value in Median dataset if it's under 50 milliseconds. + median.isBiggerEquals(SKIP_THRESHOLD_IN_MS).whenTrue { + medianValues.add(median) + } + mean.isBiggerEquals(SKIP_THRESHOLD_IN_MS).whenTrue { + meanValues.add(mean) + } + } + } + + return InitializationProcessMetric( + median = MathUtils.longMedian(medianValues), + mean = MathUtils.longMean(meanValues) + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesbuildheatmap/create/CreateModulesBuildHeatmapMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesbuildheatmap/create/CreateModulesBuildHeatmapMetricStage.kt new file mode 100644 index 00000000..d633c12f --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesbuildheatmap/create/CreateModulesBuildHeatmapMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesbuildheatmap.create + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class CreateModulesBuildHeatmapMetricStage( + private val createModulesBuildHeatmapMetricUseCase: CreateModulesBuildHeatmapMetricUseCase +) : Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + modulesBuildHeatmap = createModulesBuildHeatmapMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesbuildheatmap/create/CreateModulesBuildHeatmapMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesbuildheatmap/create/CreateModulesBuildHeatmapMetricUseCase.kt new file mode 100644 index 00000000..64876174 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesbuildheatmap/create/CreateModulesBuildHeatmapMetricUseCase.kt @@ -0,0 +1,55 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesbuildheatmap.create + +import io.github.janbarari.gradle.analytics.domain.model.Module +import io.github.janbarari.gradle.analytics.domain.model.ModulesDependencyGraph +import io.github.janbarari.gradle.analytics.domain.model.metric.ModuleBuildHeatmap +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesBuildHeatmapMetric +import io.github.janbarari.gradle.core.UseCase +import io.github.janbarari.gradle.core.UseCaseNoInput + +class CreateModulesBuildHeatmapMetricUseCase( + private val modules: List, + private val modulesDependencyGraph: ModulesDependencyGraph +): UseCaseNoInput() { + + override suspend fun execute(): ModulesBuildHeatmapMetric { + val result = mutableListOf() + + modules.forEach { module -> + val dependantModulesCount = modulesDependencyGraph.dependencies.filter { it.dependency == module.path }.size + result.add( + ModuleBuildHeatmap( + path = module.path, + dependantModulesCount = dependantModulesCount + ) + ) + } + + return ModulesBuildHeatmapMetric( + modules = result + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesbuildheatmap/render/CreateModulesBuildHeatmapReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesbuildheatmap/render/CreateModulesBuildHeatmapReportStage.kt new file mode 100644 index 00000000..42810780 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesbuildheatmap/render/CreateModulesBuildHeatmapReportStage.kt @@ -0,0 +1,82 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesbuildheatmap.render + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.report.ModuleBuildHeatmap +import io.github.janbarari.gradle.analytics.domain.model.report.ModulesBuildHeatmapReport +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.whenEach +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.MathUtils + +class CreateModulesBuildHeatmapReportStage( + private val metrics: List +) : Stage { + + override suspend fun process(input: Report): Report { + val temp = mutableListOf() + + metrics.last().modulesBuildHeatmap.whenNotNull { + modules.forEach { module -> + temp.add( + ModuleBuildHeatmap( + path = module.path, + dependantModulesCount = module.dependantModulesCount, + avgMedianCacheHit = getAvgMeanCacheHit(module.path), + totalBuildCount = getModuleTotalBuildCount(module.path) + ) + ) + } + } + + return input.apply { + modulesBuildHeatmapReport = ModulesBuildHeatmapReport( + modules = temp + ) + } + } + + fun getAvgMeanCacheHit(path: String): Long { + val hits = mutableListOf() + metrics.filter { + it.cacheHitMetric.isNotNull() + }.whenEach { + cacheHitMetric!!.modules.filter { moduleCacheHit -> + moduleCacheHit.path == path + }.whenEach { + hits.add(rate) + } + } + return MathUtils.longMean(hits) + } + + fun getModuleTotalBuildCount(path: String): Int { + return metrics.filter { + it.modulesBuildHeatmap.isNotNull() && it.modulesBuildHeatmap!!.modules.any { module -> module.path == path } + }.size + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesbuildheatmap/render/RenderModulesBuildHeatmapReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesbuildheatmap/render/RenderModulesBuildHeatmapReportStage.kt new file mode 100644 index 00000000..22644218 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesbuildheatmap/render/RenderModulesBuildHeatmapReportStage.kt @@ -0,0 +1,86 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesbuildheatmap.render + +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNull +import io.github.janbarari.gradle.extension.toArrayString +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.HtmlUtils +import io.github.janbarari.gradle.utils.MathUtils + +class RenderModulesBuildHeatmapReportStage( + private val report: Report +) : Stage { + + companion object { + private const val MODULES_BUILD_HEATMAP_TEMPLATE_ID = "%modules-build-heatmap-metric%" + private const val MODULES_BUILD_HEATMAP_TEMPLATE_FILE_NAME = "modules-build-heatmap-template" + } + + override suspend fun process(input: String): String { + if (report.modulesBuildHeatmapReport.isNull()) + return input.replace(MODULES_BUILD_HEATMAP_TEMPLATE_ID, getEmptyRender()) + + return input.replace(MODULES_BUILD_HEATMAP_TEMPLATE_ID, getMetricRender()) + } + + fun getEmptyRender(): String { + return HtmlUtils.renderMessage("Modules Build Heatmap is not available!") + } + + fun getMetricRender(): String { + var renderedTemplate = HtmlUtils.getTemplate(MODULES_BUILD_HEATMAP_TEMPLATE_FILE_NAME) + report.modulesBuildHeatmapReport.whenNotNull { + + val labels = mutableListOf() + val data = mutableListOf() + val colors = mutableListOf() + + modules.sortedByDescending { it.dependantModulesCount }.forEach { module -> + labels.add("${module.path} | ${module.dependantModulesCount}D") + colors.add(getColor(module.dependantModulesCount)) + data.add(MathUtils.deductWithPercentage(module.totalBuildCount.toLong(), module.avgMedianCacheHit.toInt())) + } + + val chartHeight = modules.size * 36 + + renderedTemplate = renderedTemplate + .replace("%labels%", labels.toArrayString()) + .replace("%data%", data.toString()) + .replace("%colors%", colors.toArrayString()) + .replace("%chart-height%", "${chartHeight}px") + } + return renderedTemplate + } + + fun getColor(dependantModulesCount: Int): String { + return if (dependantModulesCount > 6) "#d73027" + else if (dependantModulesCount in 5..6) "#fdae61" + else if (dependantModulesCount in 3..4) "#ffffbf" + else if (dependantModulesCount in 1..2) "#abd9e9" + else "#4575b4" + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesbuildheatmap/update/UpdateModulesBuildHeatmapMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesbuildheatmap/update/UpdateModulesBuildHeatmapMetricStage.kt new file mode 100644 index 00000000..f4508683 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesbuildheatmap/update/UpdateModulesBuildHeatmapMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesbuildheatmap.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class UpdateModulesBuildHeatmapMetricStage( + private val updateModulesBuildHeatmapMetricUseCase: UpdateModulesBuildHeatmapMetricUseCase +) : Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + modulesBuildHeatmap = updateModulesBuildHeatmapMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesbuildheatmap/update/UpdateModulesBuildHeatmapMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesbuildheatmap/update/UpdateModulesBuildHeatmapMetricUseCase.kt new file mode 100644 index 00000000..190c4774 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesbuildheatmap/update/UpdateModulesBuildHeatmapMetricUseCase.kt @@ -0,0 +1,39 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesbuildheatmap.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesBuildHeatmapMetric +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.core.UseCaseNoInput + +class UpdateModulesBuildHeatmapMetricUseCase( + private val repo: DatabaseRepository +) : UseCaseNoInput() { + + override suspend fun execute(): ModulesBuildHeatmapMetric { + return ModulesBuildHeatmapMetric( + modules = repo.getTemporaryMetrics().last().modulesBuildHeatmap?.modules ?: emptyList() + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulescrashcount/create/CreateModulesCrashCountMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulescrashcount/create/CreateModulesCrashCountMetricStage.kt new file mode 100644 index 00000000..dc71597e --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulescrashcount/create/CreateModulesCrashCountMetricStage.kt @@ -0,0 +1,40 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulescrashcount.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class CreateModulesCrashCountMetricStage( + private val buildInfo: BuildInfo, + private val createModulesCrashCountMetricUseCase: CreateModulesCrashCountMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + modulesCrashCountMetric = createModulesCrashCountMetricUseCase.execute(buildInfo) + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulescrashcount/create/CreateModulesCrashCountMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulescrashcount/create/CreateModulesCrashCountMetricUseCase.kt new file mode 100644 index 00000000..d6048747 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulescrashcount/create/CreateModulesCrashCountMetricUseCase.kt @@ -0,0 +1,63 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulescrashcount.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.Module +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesCrashCountMetric +import io.github.janbarari.gradle.core.UseCase +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.whenNotNull + +class CreateModulesCrashCountMetricUseCase( + private val modules: List +) : UseCase() { + + override suspend fun execute(input: BuildInfo): ModulesCrashCountMetric { + val modules = mutableListOf() + + if (!input.isSuccessful && input.failure.isNotNull()) { + val findFirstFailedTask = input.executedTasks.sortedBy { it.startedAt }.firstOrNull { !it.isSuccessful } + + findFirstFailedTask.whenNotNull { + val module = this@CreateModulesCrashCountMetricUseCase.modules.firstOrNull { + it.path == getModule() + } + module.whenNotNull { + modules.add( + ModulesCrashCountMetric.ModuleCrash( + path = path, + totalCrashes = 1 + ) + ) + } + } + + } + + return ModulesCrashCountMetric( + modules = modules + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulescrashcount/render/CreateModulesCrashCountReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulescrashcount/render/CreateModulesCrashCountReportStage.kt new file mode 100644 index 00000000..231cc0ed --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulescrashcount/render/CreateModulesCrashCountReportStage.kt @@ -0,0 +1,65 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulescrashcount.render + +import io.github.janbarari.gradle.analytics.domain.model.Module +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesCrashCountMetric +import io.github.janbarari.gradle.analytics.domain.model.report.ModulesCrashCountReport +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.whenEach + +class CreateModulesCrashCountReportStage( + private val modules: List, + private val metrics: List +): Stage { + + override suspend fun process(input: Report): Report { + val modules = mutableListOf() + + this.modules.whenEach { + val crashes = metrics + .filter { it.modulesCrashCountMetric.isNotNull() } + .sumOf { metric -> + metric.modulesCrashCountMetric!!.modules + .filter { it.path == path } + .sumOf { it.totalCrashes } + } + modules.add( + ModulesCrashCountMetric.ModuleCrash( + path = path, + totalCrashes = crashes + ) + ) + } + + return input.apply { + modulesCrashCountReport = ModulesCrashCountReport( + modules = modules + ) + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulescrashcount/render/RenderModulesCrashCountReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulescrashcount/render/RenderModulesCrashCountReportStage.kt new file mode 100644 index 00000000..5c9a74d4 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulescrashcount/render/RenderModulesCrashCountReportStage.kt @@ -0,0 +1,98 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulescrashcount.render + +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNull +import io.github.janbarari.gradle.extension.toArrayString +import io.github.janbarari.gradle.extension.whenEach +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.HtmlUtils + +class RenderModulesCrashCountReportStage( + private val report: Report +): Stage { + + companion object { + private const val MODULES_CRASH_COUNT_METRIC_TEMPLATE_ID = "%modules-crash-count-metric%" + private const val MODULES_CRASH_COUNT_METRIC_TEMPLATE_FILENAME = "modules-crash-count-metric-template" + } + + override suspend fun process(input: String): String { + if (report.modulesCrashCountReport.isNull()) + return input.replace(MODULES_CRASH_COUNT_METRIC_TEMPLATE_ID, getEmptyRender()) + + return input.replace(MODULES_CRASH_COUNT_METRIC_TEMPLATE_ID, getMetricRender()) + } + + fun getEmptyRender(): String { + return HtmlUtils.renderMessage("Modules Crash Count is not available!") + } + + fun getMetricRender(): String { + var renderedTemplate = HtmlUtils.getTemplate(MODULES_CRASH_COUNT_METRIC_TEMPLATE_FILENAME) + report.modulesCrashCountReport.whenNotNull { + val labels = mutableListOf() + val colors = mutableListOf() + val dataset = mutableListOf() + + modules.sortedByDescending { it.totalCrashes } + .whenEach { + labels.add(path) + colors.add(getRandomColor()) + dataset.add(totalCrashes) + } + + val chartHeight = dataset.size * 36 + + renderedTemplate = renderedTemplate + .replace("%labels%", labels.toArrayString()) + .replace("%colors%", colors.toArrayString()) + .replace("%dataset%", dataset.toString()) + .replace("%chart-height%", "${chartHeight}px") + } + return renderedTemplate + } + + fun getRandomColor(): String { + val colors = listOf( + "#3b76af", + "#b3c6e5", + "#ef8536", + "#f5bd82", + "#519d3e", + "#a8dc93", + "#c53a32", + "#f19d99", + "#8d6ab8", + "#c2b1d2", + "#84584e", + "#be9e96", + "#d57ebe", + "#c2cd30" + ) + return colors[colors.indices.random() % colors.size] + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulescrashcount/update/UpdateModulesCrashCountMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulescrashcount/update/UpdateModulesCrashCountMetricStage.kt new file mode 100644 index 00000000..b1e16855 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulescrashcount/update/UpdateModulesCrashCountMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulescrashcount.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class UpdateModulesCrashCountMetricStage( + private val updateModulesCrashCountMetricUseCase: UpdateModulesCrashCountMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + modulesCrashCountMetric = updateModulesCrashCountMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulescrashcount/update/UpdateModulesCrashCountMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulescrashcount/update/UpdateModulesCrashCountMetricUseCase.kt new file mode 100644 index 00000000..ee694d7b --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulescrashcount/update/UpdateModulesCrashCountMetricUseCase.kt @@ -0,0 +1,61 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulescrashcount.update + +import io.github.janbarari.gradle.analytics.domain.model.Module +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesCrashCountMetric +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.core.UseCaseNoInput +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.whenEach + +class UpdateModulesCrashCountMetricUseCase( + private val repo: DatabaseRepository, + private val modules: List +): UseCaseNoInput() { + + override suspend fun execute(): ModulesCrashCountMetric { + val modules = mutableListOf() + + this.modules.whenEach { + val crashes = repo.getTemporaryMetrics() + .filter { it.modulesCrashCountMetric.isNotNull() } + .sumOf { metric -> + metric.modulesCrashCountMetric!!.modules + .filter { it.path == path } + .sumOf { it.totalCrashes } + } + modules.add( + ModulesCrashCountMetric.ModuleCrash( + path = path, + totalCrashes = crashes + ) + ) + } + + return ModulesCrashCountMetric( + modules = modules + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesdependencygraph/create/CreateModulesDependencyGraphMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesdependencygraph/create/CreateModulesDependencyGraphMetricStage.kt new file mode 100644 index 00000000..a168bf57 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesdependencygraph/create/CreateModulesDependencyGraphMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesdependencygraph.create + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class CreateModulesDependencyGraphMetricStage( + private val createModulesDependencyGraphMetricUseCase: CreateModulesDependencyGraphMetricUseCase +) : Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + modulesDependencyGraphMetric = createModulesDependencyGraphMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesdependencygraph/create/CreateModulesDependencyGraphMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesdependencygraph/create/CreateModulesDependencyGraphMetricUseCase.kt new file mode 100644 index 00000000..584cdc1d --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesdependencygraph/create/CreateModulesDependencyGraphMetricUseCase.kt @@ -0,0 +1,39 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesdependencygraph.create + +import io.github.janbarari.gradle.analytics.domain.model.ModulesDependencyGraph +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesDependencyGraphMetric +import io.github.janbarari.gradle.core.UseCaseNoInput + +class CreateModulesDependencyGraphMetricUseCase( + private val modulesDependencyGraph: ModulesDependencyGraph +) : UseCaseNoInput() { + + override suspend fun execute(): ModulesDependencyGraphMetric { + return ModulesDependencyGraphMetric( + dependencies = modulesDependencyGraph.dependencies + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesdependencygraph/report/CreateModulesDependencyGraphReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesdependencygraph/report/CreateModulesDependencyGraphReportStage.kt new file mode 100644 index 00000000..099fdc36 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesdependencygraph/report/CreateModulesDependencyGraphReportStage.kt @@ -0,0 +1,42 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesdependencygraph.report + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.report.ModulesDependencyGraphReport +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage + +class CreateModulesDependencyGraphReportStage( + private val metrics: List +): Stage { + + override suspend fun process(input: Report): Report { + return input.apply { + modulesDependencyGraphReport = ModulesDependencyGraphReport( + dependencies = metrics.last().modulesDependencyGraphMetric?.dependencies ?: emptyList() + ) + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesdependencygraph/report/RenderModulesDependencyGraphReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesdependencygraph/report/RenderModulesDependencyGraphReportStage.kt new file mode 100644 index 00000000..6cfdedae --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesdependencygraph/report/RenderModulesDependencyGraphReportStage.kt @@ -0,0 +1,87 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesdependencygraph.report + +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNull +import io.github.janbarari.gradle.extension.whenEach +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.HtmlUtils + +class RenderModulesDependencyGraphReportStage( + private val report: Report +): Stage { + + companion object { + private const val MODULES_DEPENDENCY_GRAPH_METRIC_TEMPLATE_ID = "%modules-dependency-graph-metric%" + private const val MODULES_DEPENDENCY_GRAPH_METRIC_TEMPLATE_FILE_NAME = "modules-dependency-graph-metric-template" + } + + override suspend fun process(input: String): String { + if (report.modulesDependencyGraphReport.isNull()) { + return input.replace(MODULES_DEPENDENCY_GRAPH_METRIC_TEMPLATE_ID, getEmptyRender()) + } + + return input.replace(MODULES_DEPENDENCY_GRAPH_METRIC_TEMPLATE_ID, getMetricRender()) + } + + fun getEmptyRender(): String { + return HtmlUtils.renderMessage("Modules Dependency Graph is not available!") + } + + fun getMetricRender(): String { + var renderedTemplate = HtmlUtils.getTemplate(MODULES_DEPENDENCY_GRAPH_METRIC_TEMPLATE_FILE_NAME) + report.modulesDependencyGraphReport.whenNotNull { + val mermaidCommands = buildString { + appendLine() + dependencies.whenEach { + val type = when(configuration) { + "api" -> "api" + "implementation" -> "impl" + else -> configuration + } + + val pathColor = dependencies.filter { it.dependency == dependency }.size + var heatmapColor = ":::blue" + if (pathColor in 3 .. 4) { + heatmapColor = ":::yellow" + } else if (pathColor in 5 .. 6) { + heatmapColor = ":::orange" + } else if (pathColor > 6) { + heatmapColor = ":::red" + } + + append("\t$path ---> |$type| $dependency$heatmapColor") + appendLine() + } + + } + + renderedTemplate = renderedTemplate + .replace("%mermaid-commands%", mermaidCommands) + } + return renderedTemplate + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesdependencygraph/update/UpdateModulesDependencyGraphMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesdependencygraph/update/UpdateModulesDependencyGraphMetricStage.kt new file mode 100644 index 00000000..9b3c82e7 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesdependencygraph/update/UpdateModulesDependencyGraphMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesdependencygraph.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class UpdateModulesDependencyGraphMetricStage( + private val updateModulesDependencyGraphMetricUseCase: UpdateModulesDependencyGraphMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + modulesDependencyGraphMetric = updateModulesDependencyGraphMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesdependencygraph/update/UpdateModulesDependencyGraphMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesdependencygraph/update/UpdateModulesDependencyGraphMetricUseCase.kt new file mode 100644 index 00000000..8dde2dd1 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesdependencygraph/update/UpdateModulesDependencyGraphMetricUseCase.kt @@ -0,0 +1,39 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesdependencygraph.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesDependencyGraphMetric +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.core.UseCaseNoInput + +class UpdateModulesDependencyGraphMetricUseCase( + private val repo: DatabaseRepository +) : UseCaseNoInput() { + + override suspend fun execute(): ModulesDependencyGraphMetric { + return ModulesDependencyGraphMetric( + dependencies = repo.getTemporaryMetrics().last().modulesDependencyGraphMetric?.dependencies ?: emptyList() + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesexecutionprocess/create/CreateModulesExecutionProcessMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesexecutionprocess/create/CreateModulesExecutionProcessMetricStage.kt new file mode 100644 index 00000000..23035039 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesexecutionprocess/create/CreateModulesExecutionProcessMetricStage.kt @@ -0,0 +1,42 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesexecutionprocess.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class CreateModulesExecutionProcessMetricStage( + private val buildInfo: BuildInfo, + private val createModulesExecutionProcessMetricUseCase: CreateModulesExecutionProcessMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + if (buildInfo.isSuccessful) { + modulesExecutionProcessMetric = createModulesExecutionProcessMetricUseCase.execute(buildInfo) + } + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesexecutionprocess/create/CreateModulesExecutionProcessMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesexecutionprocess/create/CreateModulesExecutionProcessMetricUseCase.kt new file mode 100644 index 00000000..3fc8fede --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesexecutionprocess/create/CreateModulesExecutionProcessMetricUseCase.kt @@ -0,0 +1,69 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesexecutionprocess.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.Module +import io.github.janbarari.gradle.analytics.domain.model.metric.ModuleExecutionProcess +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesExecutionProcessMetric +import io.github.janbarari.gradle.core.UseCase +import io.github.janbarari.gradle.extension.toPercentageOf +import io.github.janbarari.gradle.extension.whenEach + +class CreateModulesExecutionProcessMetricUseCase( + private val modules: List +): UseCase() { + + override suspend fun execute(input: BuildInfo): ModulesExecutionProcessMetric { + val moduleExecutionProcesses = mutableListOf() + + modules.whenEach { + val tasks = input.executedTasks.filter { it.path.startsWith(path) } + + val moduleParallelExecInMillis = tasks.sumOf { it.getDurationInMillis() } + + val moduleNonParallelExecInMillis = input.calculateNonParallelExecutionInMillis(tasks) + + val moduleParallelRate = (moduleParallelExecInMillis - moduleNonParallelExecInMillis) + .toPercentageOf(moduleNonParallelExecInMillis) + + val overallDuration = input.getExecutionDuration().toMillis() + val moduleCoverageRate = moduleNonParallelExecInMillis.toPercentageOf(overallDuration) + + moduleExecutionProcesses.add( + ModuleExecutionProcess( + path = path, + medianExecInMillis = moduleNonParallelExecInMillis, + medianParallelExecInMillis = moduleParallelExecInMillis, + parallelRate = moduleParallelRate, + coverageRate = moduleCoverageRate + ) + ) + } + + return ModulesExecutionProcessMetric( + modules = moduleExecutionProcesses + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesexecutionprocess/report/CreateModulesExecutionProcessReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesexecutionprocess/report/CreateModulesExecutionProcessReportStage.kt new file mode 100644 index 00000000..2c429aa4 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesexecutionprocess/report/CreateModulesExecutionProcessReportStage.kt @@ -0,0 +1,116 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesexecutionprocess.report + +import io.github.janbarari.gradle.analytics.domain.model.Module +import io.github.janbarari.gradle.analytics.domain.model.TimespanPoint +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.report.ModuleExecutionProcess +import io.github.janbarari.gradle.analytics.domain.model.report.ModulesExecutionProcessReport +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.diffPercentageOf +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.round +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.MathUtils + +class CreateModulesExecutionProcessReportStage( + private val modules: List, + private val metrics: List +) : Stage { + + override suspend fun process(input: Report): Report { + val temp = modules.map { module -> + var firstAvgMedianDuration: Long? = null + var lastAvgMedianDuration: Long? = null + var diffRate: Float? = null + + val avgMedianExecTimespanPoints = mutableListOf() + val avgMedianExecs = mutableListOf() + val avgMedianParallelExecs = mutableListOf() + val avgMedianParallelRates = mutableListOf() + val avgMedianCoverageRates = mutableListOf() + + metrics.firstOrNull { metric -> + metric.modulesExecutionProcessMetric.isNotNull() + }.whenNotNull { + modulesExecutionProcessMetric!! + .modules + .find { it.path == module.path } + .whenNotNull { + firstAvgMedianDuration = medianExecInMillis + } + } + + metrics.lastOrNull { metric -> + metric.modulesExecutionProcessMetric.isNotNull() + }.whenNotNull { + modulesExecutionProcessMetric!! + .modules + .find { it.path == module.path } + .whenNotNull { + lastAvgMedianDuration = medianExecInMillis + } + } + + if (firstAvgMedianDuration.isNotNull() && lastAvgMedianDuration.isNotNull()) + diffRate = firstAvgMedianDuration!!.diffPercentageOf(lastAvgMedianDuration!!) + + metrics.filter { metric -> + metric.modulesExecutionProcessMetric.isNotNull() + }.forEach { metric -> + metric.modulesExecutionProcessMetric!! + .modules + .find { it.path == module.path } + .whenNotNull { + avgMedianExecTimespanPoints.add( + TimespanPoint( + value = medianExecInMillis, + from = metric.createdAt + ) + ) + avgMedianExecs.add(medianExecInMillis) + avgMedianParallelExecs.add(medianParallelExecInMillis) + avgMedianParallelRates.add(parallelRate) + avgMedianCoverageRates.add(coverageRate) + } + } + + ModuleExecutionProcess( + path = module.path, + avgMedianExecInMillis = MathUtils.longMedian(avgMedianExecs), + avgMedianParallelExecInMillis = MathUtils.longMedian(avgMedianParallelExecs), + avgMedianParallelRate = MathUtils.floatMedian(avgMedianParallelRates).round(), + avgMedianCoverageRate = MathUtils.floatMedian(avgMedianCoverageRates).round(), + avgMedianExecs = avgMedianExecTimespanPoints, + diffRate = diffRate + ) + } + + return input.apply { + modulesExecutionProcessReport = ModulesExecutionProcessReport(modules = temp) + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesexecutionprocess/report/RenderModulesExecutionProcessReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesexecutionprocess/report/RenderModulesExecutionProcessReportStage.kt new file mode 100644 index 00000000..18bf6bd6 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesexecutionprocess/report/RenderModulesExecutionProcessReportStage.kt @@ -0,0 +1,133 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesexecutionprocess.report + +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isBiggerThanZero +import io.github.janbarari.gradle.extension.isNull +import io.github.janbarari.gradle.extension.isZero +import io.github.janbarari.gradle.extension.mapToChartPoints +import io.github.janbarari.gradle.extension.millisToSeconds +import io.github.janbarari.gradle.extension.minimize +import io.github.janbarari.gradle.extension.toArrayString +import io.github.janbarari.gradle.extension.toIntList +import io.github.janbarari.gradle.extension.whenEach +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.HtmlUtils +import io.github.janbarari.gradle.utils.MathUtils + +/** + * Generates html render for [io.github.janbarari.gradle.analytics.domain.model.report.ModulesExecutionProcessReport]. + */ +class RenderModulesExecutionProcessReportStage( + private val report: Report +) : Stage { + + companion object { + private const val CHART_MAX_COLUMNS = 12 + private const val CHART_SUGGESTED_MIN_MAX_PERCENTAGE = 30 + private const val MODULES_EXECUTION_PROCESS_METRIC_TEMPLATE_ID = "%modules-execution-process-metric%" + private const val MODULES_EXECUTION_PROCESS_METRIC_FILE_NAME = "modules-execution-process-metric-template" + } + + override suspend fun process(input: String): String { + if (report.modulesExecutionProcessReport.isNull()) { + return input.replace(MODULES_EXECUTION_PROCESS_METRIC_TEMPLATE_ID, getEmptyRender()) + } + + return input.replace(MODULES_EXECUTION_PROCESS_METRIC_TEMPLATE_ID, getMetricRender()) + } + + fun getMetricRender(): String { + var renderedTemplate = HtmlUtils.getTemplate(MODULES_EXECUTION_PROCESS_METRIC_FILE_NAME) + report.modulesExecutionProcessReport.whenNotNull { + val min = (modules.minOfOrNull { it.avgMedianExecInMillis } ?: 0L).millisToSeconds() + val max = (modules.maxOfOrNull { it.avgMedianExecInMillis } ?: 0L).millisToSeconds() + + val chartSuggestedMinValue = MathUtils.deductWithPercentage(min, CHART_SUGGESTED_MIN_MAX_PERCENTAGE) + val chartSuggestedMaxValue = MathUtils.sumWithPercentage(max, CHART_SUGGESTED_MIN_MAX_PERCENTAGE) + + val chartLabels: String = modules.firstOrNull() + ?.avgMedianExecs + ?.minimize(CHART_MAX_COLUMNS) + ?.mapToChartPoints() + ?.map { it.description } + ?.toArrayString() + ?: "[]" + + val chartDatasets = buildString { + modules.whenEach { + append("{") + append("label: \"$path\",") + append("fill: false,") + append("borderColor: getColor(),") + append("backgroundColor: shadeColor(getColor(), 25),") + append("pointRadius: 0,") + append("data: ${avgMedianExecs.map { it.value.millisToSeconds() }.toIntList()},") + append("cubicInterpolationMode: 'monotone',") + append("tension: 0.4,") + append("hidden: false") + append("}") + append(",") + } + } + + val tableData = buildString { + modules.forEachIndexed { i, module -> + append("") + append("${i+1}") + append("${module.path}") + append("${module.avgMedianExecInMillis.millisToSeconds()}s") + append("${module.avgMedianParallelExecInMillis.millisToSeconds()}s") + append("${module.avgMedianParallelRate}%") + append("${module.avgMedianCoverageRate}%") + + if (module.diffRate.isNull()) + append("Unknown") + else if (module.diffRate!!.isZero()) + append("Equals") + else if (module.diffRate.isBiggerThanZero()) + append("+${module.diffRate}%") + else + append("-${module.diffRate}%") + + append("") + } + } + + renderedTemplate = renderedTemplate + .replace("%suggested-min-value%", chartSuggestedMinValue.toString()) + .replace("%suggested-max-value%", chartSuggestedMaxValue.toString()) + .replace("%chart-labels%", chartLabels) + .replace("%chart-datasets%", chartDatasets) + .replace("%table-data%", tableData) + } + return renderedTemplate + } + + fun getEmptyRender(): String { + return HtmlUtils.renderMessage("Modules Execution Process is not available!") + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesexecutionprocess/update/UpdateModulesExecutionProcessMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesexecutionprocess/update/UpdateModulesExecutionProcessMetricStage.kt new file mode 100644 index 00000000..96b0da3a --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesexecutionprocess/update/UpdateModulesExecutionProcessMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesexecutionprocess.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class UpdateModulesExecutionProcessMetricStage( + private val updateModulesExecutionProcessMetricUseCase: UpdateModulesExecutionProcessMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + modulesExecutionProcessMetric = updateModulesExecutionProcessMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesexecutionprocess/update/UpdateModulesExecutionProcessMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesexecutionprocess/update/UpdateModulesExecutionProcessMetricUseCase.kt new file mode 100644 index 00000000..323017dd --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesexecutionprocess/update/UpdateModulesExecutionProcessMetricUseCase.kt @@ -0,0 +1,76 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesexecutionprocess.update + +import io.github.janbarari.gradle.analytics.domain.model.Module +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.metric.ModuleExecutionProcess +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesExecutionProcessMetric +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.core.UseCaseNoInput +import io.github.janbarari.gradle.extension.whenEach +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.MathUtils + +class UpdateModulesExecutionProcessMetricUseCase( + private val repo: DatabaseRepository, + private val modules: List +): UseCaseNoInput() { + + override suspend fun execute(): ModulesExecutionProcessMetric { + val modulesMedianExecutionProcess = modules.map { + calculateMedianModuleExecutionProcess(modulePath = it.path, metrics = repo.getTemporaryMetrics()) + } + + return ModulesExecutionProcessMetric( + modules = modulesMedianExecutionProcess + ) + } + + private fun calculateMedianModuleExecutionProcess(modulePath: String, metrics: List): ModuleExecutionProcess { + val medianExecs = mutableListOf() + val medianParallelExecs = mutableListOf() + val medianParallelRates = mutableListOf() + val medianCoverageRates = mutableListOf() + + metrics.whenEach { + modulesExecutionProcessMetric.whenNotNull { + modules.find { it.path == modulePath }.whenNotNull { + medianExecs.add(medianExecInMillis) + medianParallelExecs.add(medianParallelExecInMillis) + medianParallelRates.add(parallelRate) + medianCoverageRates.add(coverageRate) + } + } + } + + return ModuleExecutionProcess( + path = modulePath, + medianExecInMillis = MathUtils.longMedian(medianExecs), + medianParallelExecInMillis = MathUtils.longMedian(medianParallelExecs), + parallelRate = MathUtils.floatMedian(medianParallelRates), + coverageRate = MathUtils.floatMedian(medianCoverageRates) + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesmethodcount/create/CreateModulesMethodCountMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesmethodcount/create/CreateModulesMethodCountMetricStage.kt new file mode 100644 index 00000000..374e8e20 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesmethodcount/create/CreateModulesMethodCountMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesmethodcount.create + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class CreateModulesMethodCountMetricStage( + private val createModulesMethodCountMetricUseCase: CreateModulesMethodCountMetricUseCase +) : Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + modulesMethodCountMetric = createModulesMethodCountMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesmethodcount/create/CreateModulesMethodCountMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesmethodcount/create/CreateModulesMethodCountMetricUseCase.kt new file mode 100644 index 00000000..c04ede2a --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesmethodcount/create/CreateModulesMethodCountMetricUseCase.kt @@ -0,0 +1,93 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesmethodcount.create + +import io.github.janbarari.gradle.analytics.domain.model.Module +import io.github.janbarari.gradle.analytics.domain.model.metric.ModuleMethodCount +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesMethodCountMetric +import io.github.janbarari.gradle.core.UseCaseNoInput +import io.github.janbarari.gradle.extension.isJavaFile +import io.github.janbarari.gradle.extension.isKotlinFile +import io.github.janbarari.gradle.extension.readText +import io.github.janbarari.gradle.extension.whenEach +import io.github.janbarari.gradle.utils.FileUtils +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.withContext +import java.util.* + +class CreateModulesMethodCountMetricUseCase( + private val modules: List +) : UseCaseNoInput() { + + private val commentRegex = """(//.*)|(\/\*[^/*]*(?:(?!\/\*|\*\/)[/*][^/*]*)*\*\/)""".toRegex() + + private val javaModifiers = "public|private|protected|static|final|native|synchronized|abstract|transient|" + private val javaMethodRegex = """($javaModifiers)+[\w\<\>\[\]\,\s]*\s*(\w+) *\([^\)]*\) *(\{|[^;])""".toRegex() + + private val kotlinMethodRegex = """((fun)+[\\${'$'}\w\<\>\w\s\[\]]*\s+\w.*\([^\)]*\) *(.*) *(\{|\=))""".toRegex() + private val kotlinConstructorRegex = + """(.*class[\\${'$'}\w\<\>\w\s\[\]]*\s+\w.*\([^\)]*\)|.*constructor.*\([^\)]*\))|((\sinit) *(\{|\=))""".toRegex() + + override suspend fun execute(): ModulesMethodCountMetric { + val modulesProperties = Collections.synchronizedList(mutableListOf()) + withContext(dispatcher) { + val defers = mutableListOf>() + modules.whenEach { + defers.add(async { + modulesProperties.add( + ModuleMethodCount( + path = path, + value = getModuleMethodCount(absoluteDir) + ) + ) + }) + } + defers.awaitAll() + } + return ModulesMethodCountMetric(modules = modulesProperties) + } + + private fun getModuleMethodCount(directory: String): Int { + val sourcePaths = FileUtils.getModuleSources(directory) + var result = 0 + + sourcePaths.whenEach { + if (isKotlinFile()) { + val content = readText() + val removedComments = content.replace(commentRegex, "") + result += kotlinMethodRegex.findAll(removedComments).count() + result += kotlinConstructorRegex.findAll(removedComments).count() + } + if (isJavaFile()) { + val content = readText() + val removedComments = content.replace(commentRegex, "") + result += javaMethodRegex.findAll(removedComments).count() + } + } + + return result + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesmethodcount/report/CreateModulesMethodCountReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesmethodcount/report/CreateModulesMethodCountReportStage.kt new file mode 100644 index 00000000..266488bb --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesmethodcount/report/CreateModulesMethodCountReportStage.kt @@ -0,0 +1,115 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesmethodcount.report + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.report.ModuleMethodCount +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesMethodCountMetric +import io.github.janbarari.gradle.analytics.domain.model.report.ModulesMethodCountReport +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.diffPercentageOf +import io.github.janbarari.gradle.extension.hasMultipleItems +import io.github.janbarari.gradle.extension.hasSingleItem +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.toPercentageOf +import io.github.janbarari.gradle.extension.whenEach + +class CreateModulesMethodCountReportStage( + private val metrics: List +) : Stage { + + override suspend fun process(input: Report): Report { + val metrics = metrics.filter { + it.modulesMethodCountMetric.isNotNull() + }.map { + it.modulesMethodCountMetric!! + } + + if (metrics.hasSingleItem()) { + return input.apply { + modulesMethodCountReport = generateSingleItemReport(metrics.single()) + } + } + + if (metrics.hasMultipleItems()) { + return input.apply { + modulesMethodCountReport = generateMultipleItemsReport(metrics) + } + } + + return input + } + + fun generateSingleItemReport(metric: ModulesMethodCountMetric): ModulesMethodCountReport { + val values = mutableListOf() + + val totalSourceCount = metric.modules.sumOf { it.value } + + metric.modules.whenEach { + values.add( + ModuleMethodCount( + path = path, + value = value, + coverageRate = value.toPercentageOf(totalSourceCount), + diffRate = null // The ratio does not exist when there is only one item + ) + ) + } + + return ModulesMethodCountReport( + values = values.sortedByDescending { it.value }, + totalMethodCount = totalSourceCount, + totalDiffRate = null // The ratio does not exist when there is only one item + ) + } + + fun generateMultipleItemsReport(metrics: List): ModulesMethodCountReport { + val firstTotalSourceCount = metrics.first().modules.sumOf { it.value } + val lastTotalSourceCount = metrics.last().modules.sumOf { it.value } + val totalDiffRatio = firstTotalSourceCount.diffPercentageOf(lastTotalSourceCount) + + val values = mutableListOf() + metrics.last().modules.whenEach { + values.add( + ModuleMethodCount( + path = path, + value = value, + coverageRate = value.toPercentageOf(lastTotalSourceCount), + diffRate = calculateModuleDiffRatio(metrics, path, value) + ) + ) + } + + return ModulesMethodCountReport( + values = values.sortedByDescending { it.value }, + totalMethodCount = lastTotalSourceCount, + totalDiffRate = totalDiffRatio + ) + } + + fun calculateModuleDiffRatio(metrics: List, path: String, value: Int): Float? { + return metrics.first().modules.find { it.path == path }?.value?.diffPercentageOf(value) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesmethodcount/report/RenderModulesMethodCountStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesmethodcount/report/RenderModulesMethodCountStage.kt new file mode 100644 index 00000000..ff6098ad --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesmethodcount/report/RenderModulesMethodCountStage.kt @@ -0,0 +1,107 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesmethodcount.report + +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNull +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.HtmlUtils + +class RenderModulesMethodCountStage( + private val report: Report +) : Stage { + + companion object { + private const val MODULES_METHOD_COUNT_METRIC_TEMPLATE_ID = "%modules-method-count-metric%" + private const val MODULES_METHOD_COUNT_METRIC_TEMPLATE_FILE_NAME = "modules-method-count-metric-template" + } + + override suspend fun process(input: String): String { + if (report.modulesMethodCountReport.isNull()) + return input.replace(MODULES_METHOD_COUNT_METRIC_TEMPLATE_ID, getEmptyRender()) + + return input.replace(MODULES_METHOD_COUNT_METRIC_TEMPLATE_ID, getMetricRender()) + } + + fun getEmptyRender(): String { + return HtmlUtils.renderMessage("Modules Method Count is not available!") + } + + fun getMetricRender(): String { + val totalMethodCount = report.modulesMethodCountReport?.totalMethodCount ?: 0 + + var totalDiffRatioRender = "-" + report.modulesMethodCountReport.whenNotNull { + totalDiffRate.whenNotNull { + totalDiffRatioRender = if (this > 0) { + "+${this}%" + } else if (this < 0) { + "-${this}%" + } else { + "Equals" + } + } + } + + val tableData = buildString { + report.modulesMethodCountReport?.values?.forEachIndexed { index, it -> + var diffRatioRender = "-" + it.diffRate.whenNotNull { + diffRatioRender = if (this > 0) { + "+${this}%" + } else if (this < 0) { + "-${this}%" + } else { + "Equals" + } + } + append( + """ + + ${index + 1} + ${it.path} + ${it.value} + ${it.coverageRate}% + $diffRatioRender + + """.trimIndent() + ) + } + } + + val moduleLabels = report.modulesMethodCountReport?.values?.map { "\"${it.path}\"" } + val moduleValues = report.modulesMethodCountReport?.values?.map { it.value } + + var renderedTemplate = HtmlUtils.getTemplate(MODULES_METHOD_COUNT_METRIC_TEMPLATE_FILE_NAME) + renderedTemplate = renderedTemplate + .replace("%table-data%", tableData) + .replace("%total-method-count%", totalMethodCount.toString()) + .replace("%total-diff-rate%", totalDiffRatioRender) + .replace("%module-labels%", moduleLabels.toString()) + .replace("%module-values%", moduleValues.toString()) + + return renderedTemplate + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesmethodcount/update/UpdateModulesMethodCountMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesmethodcount/update/UpdateModulesMethodCountMetricStage.kt new file mode 100644 index 00000000..5654f929 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesmethodcount/update/UpdateModulesMethodCountMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesmethodcount.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class UpdateModulesMethodCountMetricStage( + private val updateModulesMethodCountMetricUseCase: UpdateModulesMethodCountMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + modulesMethodCountMetric = updateModulesMethodCountMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesmethodcount/update/UpdateModulesMethodCountMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesmethodcount/update/UpdateModulesMethodCountMetricUseCase.kt new file mode 100644 index 00000000..7bbbfa2e --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesmethodcount/update/UpdateModulesMethodCountMetricUseCase.kt @@ -0,0 +1,37 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesmethodcount.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesMethodCountMetric +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.core.UseCaseNoInput + +class UpdateModulesMethodCountMetricUseCase( + private val repo: DatabaseRepository +) : UseCaseNoInput() { + + override suspend fun execute(): ModulesMethodCountMetric { + return repo.getTemporaryMetrics().last().modulesMethodCountMetric!! + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesourcecount/create/CreateModulesSourceCountMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesourcecount/create/CreateModulesSourceCountMetricStage.kt new file mode 100644 index 00000000..a5e62d74 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesourcecount/create/CreateModulesSourceCountMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesourcecount.create + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class CreateModulesSourceCountMetricStage( + private val createModulesSourceCountMetricUseCase: CreateModulesSourceCountMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + modulesSourceCountMetric = createModulesSourceCountMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesourcecount/create/CreateModulesSourceCountMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesourcecount/create/CreateModulesSourceCountMetricUseCase.kt new file mode 100644 index 00000000..684f28b7 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesourcecount/create/CreateModulesSourceCountMetricUseCase.kt @@ -0,0 +1,60 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesourcecount.create + +import io.github.janbarari.gradle.analytics.domain.model.Module +import io.github.janbarari.gradle.analytics.domain.model.metric.ModuleSourceCount +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesSourceCountMetric +import io.github.janbarari.gradle.core.UseCaseNoInput +import io.github.janbarari.gradle.extension.whenEach +import io.github.janbarari.gradle.utils.FileUtils +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.withContext +import java.util.* + +class CreateModulesSourceCountMetricUseCase( + private val modules: List +) : UseCaseNoInput() { + + override suspend fun execute(): ModulesSourceCountMetric { + val result = Collections.synchronizedList(mutableListOf()) + withContext(dispatcher) { + val defers = mutableListOf>() + modules.whenEach { + defers.add(async { + result.add( + ModuleSourceCount( + path = path, + value = FileUtils.getModuleSources(absoluteDir).size + ) + ) + }) + } + defers.awaitAll() + } + return ModulesSourceCountMetric(modules = result) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesourcecount/report/CreateModulesSourceCountReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesourcecount/report/CreateModulesSourceCountReportStage.kt new file mode 100644 index 00000000..060153b7 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesourcecount/report/CreateModulesSourceCountReportStage.kt @@ -0,0 +1,114 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesourcecount.report + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.report.ModuleSourceCount +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesSourceCountMetric +import io.github.janbarari.gradle.analytics.domain.model.report.ModulesSourceCountReport +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.hasMultipleItems +import io.github.janbarari.gradle.extension.hasSingleItem +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.diffPercentageOf +import io.github.janbarari.gradle.extension.toPercentageOf +import io.github.janbarari.gradle.extension.whenEach + +class CreateModulesSourceCountReportStage( + private val metrics: List +) : Stage { + + override suspend fun process(input: Report): Report { + val metrics = metrics.filter { + it.modulesSourceCountMetric.isNotNull() + }.map { + it.modulesSourceCountMetric!! + } + + if (metrics.hasSingleItem()) { + return input.apply { + modulesSourceCountReport = generateSingleItemReport(metrics.single()) + } + } + + if (metrics.hasMultipleItems()) { + return input.apply { + modulesSourceCountReport = generateMultipleItemsReport(metrics) + } + } + + return input + } + + fun generateSingleItemReport(metric: ModulesSourceCountMetric): ModulesSourceCountReport { + val values = mutableListOf() + val totalSourceCount = metric.modules.sumOf { it.value } + + metric.modules.whenEach { + values.add( + ModuleSourceCount( + path = path, + value = value, + coverageRate = value.toPercentageOf(totalSourceCount), + diffRate = null // The ratio does not exist when there is only one item + ) + ) + } + + return ModulesSourceCountReport( + values = values.sortedByDescending { it.value }, + totalSourceCount = totalSourceCount, + totalDiffRate = null // The ratio does not exist when there is only one item + ) + } + + fun generateMultipleItemsReport(metrics: List): ModulesSourceCountReport { + val firstTotalSourceCount = metrics.first().modules.sumOf { it.value } + val lastTotalSourceCount = metrics.last().modules.sumOf { it.value } + val totalDiffRatio = firstTotalSourceCount.diffPercentageOf(lastTotalSourceCount) + + val values = mutableListOf() + metrics.last().modules.whenEach { + values.add( + ModuleSourceCount( + path = path, + value = value, + coverageRate = value.toPercentageOf(lastTotalSourceCount), + diffRate = calculateModuleDiffRatio(metrics, path, value) + ) + ) + } + + return ModulesSourceCountReport( + values = values.sortedByDescending { it.value }, + totalSourceCount = lastTotalSourceCount, + totalDiffRate = totalDiffRatio + ) + } + + fun calculateModuleDiffRatio(metrics: List, path: String, value: Int): Float? { + return metrics.first().modules.find { it.path == path }?.value?.diffPercentageOf(value) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesourcecount/report/RenderModulesSourceCountStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesourcecount/report/RenderModulesSourceCountStage.kt new file mode 100644 index 00000000..582a6c76 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesourcecount/report/RenderModulesSourceCountStage.kt @@ -0,0 +1,107 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesourcecount.report + +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNull +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.HtmlUtils + +class RenderModulesSourceCountStage( + private val report: Report +) : Stage { + + companion object { + private const val MODULES_SOURCE_COUNT_METRIC_TEMPLATE_ID = "%modules-source-count-metric%" + private const val MODULES_SOURCE_COUNT_METRIC_TEMPLATE_FILE_NAME = "modules-source-count-metric-template" + } + + override suspend fun process(input: String): String { + if (report.modulesSourceCountReport.isNull()) + return input.replace(MODULES_SOURCE_COUNT_METRIC_TEMPLATE_ID, getEmptyRender()) + + return input.replace(MODULES_SOURCE_COUNT_METRIC_TEMPLATE_ID, getMetricRender()) + } + + fun getEmptyRender(): String { + return HtmlUtils.renderMessage("Modules Source Count is not available!") + } + + fun getMetricRender(): String { + val totalSourceCount = report.modulesSourceCountReport?.totalSourceCount ?: 0 + + var totalDiffRatioRender = "-" + report.modulesSourceCountReport.whenNotNull { + totalDiffRate.whenNotNull { + totalDiffRatioRender = if (this > 0) { + "+${this}%" + } else if (this < 0) { + "-${this}%" + } else { + "Equals" + } + } + } + + val tableData = buildString { + report.modulesSourceCountReport?.values?.forEachIndexed { index, it -> + var diffRatioRender = "-" + it.diffRate.whenNotNull { + diffRatioRender = if (this > 0) { + "+${this}%" + } else if (this < 0){ + "-${this}%" + } else { + "Equals" + } + } + append( + """ + + ${index + 1} + ${it.path} + ${it.value} + ${it.coverageRate}% + $diffRatioRender + + """.trimIndent() + ) + } + } + + val moduleLabels = report.modulesSourceCountReport?.values?.map { "\"${it.path}\"" } + val moduleValues = report.modulesSourceCountReport?.values?.map { it.value } + + var renderedTemplate = HtmlUtils.getTemplate(MODULES_SOURCE_COUNT_METRIC_TEMPLATE_FILE_NAME) + renderedTemplate = renderedTemplate + .replace("%table-data%", tableData) + .replace("%total-source-count%", totalSourceCount.toString()) + .replace("%total-diff-rate%", totalDiffRatioRender) + .replace("%module-labels%", moduleLabels.toString()) + .replace("%module-values%", moduleValues.toString()) + + return renderedTemplate + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesourcecount/update/UpdateModulesSourceCountMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesourcecount/update/UpdateModulesSourceCountMetricStage.kt new file mode 100644 index 00000000..b283b157 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesourcecount/update/UpdateModulesSourceCountMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesourcecount.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class UpdateModulesSourceCountMetricStage( + private val updateModulesSourceCountMetricUseCase: UpdateModulesSourceCountMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + modulesSourceCountMetric = updateModulesSourceCountMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesourcecount/update/UpdateModulesSourceCountMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesourcecount/update/UpdateModulesSourceCountMetricUseCase.kt new file mode 100644 index 00000000..6a9a89d9 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulesourcecount/update/UpdateModulesSourceCountMetricUseCase.kt @@ -0,0 +1,37 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulesourcecount.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesSourceCountMetric +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.core.UseCaseNoInput + +class UpdateModulesSourceCountMetricUseCase( + private val repo: DatabaseRepository +) : UseCaseNoInput() { + + override suspend fun execute(): ModulesSourceCountMetric { + return repo.getTemporaryMetrics().last().modulesSourceCountMetric!! + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulessourcesize/create/CreateModulesSourceSizeMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulessourcesize/create/CreateModulesSourceSizeMetricStage.kt new file mode 100644 index 00000000..b89b4d27 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulessourcesize/create/CreateModulesSourceSizeMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulessourcesize.create + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class CreateModulesSourceSizeMetricStage( + private val createModulesSourceSizeMetricUseCase: CreateModulesSourceSizeMetricUseCase +) : Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + modulesSourceSizeMetric = createModulesSourceSizeMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulessourcesize/create/CreateModulesSourceSizeMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulessourcesize/create/CreateModulesSourceSizeMetricUseCase.kt new file mode 100644 index 00000000..d57c22f0 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulessourcesize/create/CreateModulesSourceSizeMetricUseCase.kt @@ -0,0 +1,65 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulessourcesize.create + +import io.github.janbarari.gradle.analytics.domain.model.Module +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesSourceSizeMetric +import io.github.janbarari.gradle.core.UseCaseNoInput +import io.github.janbarari.gradle.extension.whenEach +import io.github.janbarari.gradle.utils.FileUtils +import kotlinx.coroutines.Deferred +import kotlinx.coroutines.async +import kotlinx.coroutines.awaitAll +import kotlinx.coroutines.withContext +import java.util.* + +class CreateModulesSourceSizeMetricUseCase( + private val modules: List +) : UseCaseNoInput() { + + override suspend fun execute(): ModulesSourceSizeMetric { + val modulesProperties = Collections.synchronizedList(mutableListOf()) + withContext(dispatcher) { + val defers = mutableListOf>() + modules.whenEach { + defers.add(async { + modulesProperties.add( + ModulesSourceSizeMetric.ModuleSourceSize( + path = path, + sizeInKb = getModuleSourceSizeInKb(absoluteDir) + ) + ) + }) + } + defers.awaitAll() + } + return ModulesSourceSizeMetric(modules = modulesProperties) + } + + private fun getModuleSourceSizeInKb(directory: String): Long { + return FileUtils + .getModuleSources(directory) + .sumOf { it.toFile().length() / 1024L } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulessourcesize/report/CreateModulesSourceSizeReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulessourcesize/report/CreateModulesSourceSizeReportStage.kt new file mode 100644 index 00000000..e3a392ba --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulessourcesize/report/CreateModulesSourceSizeReportStage.kt @@ -0,0 +1,114 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulessourcesize.report + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesSourceSizeMetric +import io.github.janbarari.gradle.analytics.domain.model.report.ModulesSourceSizeReport +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.diffPercentageOf +import io.github.janbarari.gradle.extension.hasMultipleItems +import io.github.janbarari.gradle.extension.hasSingleItem +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.toPercentageOf +import io.github.janbarari.gradle.extension.whenEach + +class CreateModulesSourceSizeReportStage( + private val metrics: List +) : Stage { + + override suspend fun process(input: Report): Report { + val metrics = metrics.filter { + it.modulesSourceSizeMetric.isNotNull() + }.map { + it.modulesSourceSizeMetric!! + } + + if (metrics.hasSingleItem()) { + return input.apply { + modulesSourceSizeReport = generateSingleItemReport(metrics.single()) + } + } + + if (metrics.hasMultipleItems()) { + return input.apply { + modulesSourceSizeReport = generateMultipleItemsReport(metrics) + } + } + + return input + } + + fun generateSingleItemReport(metric: ModulesSourceSizeMetric): ModulesSourceSizeReport { + val values = mutableListOf() + + val totalSourceCount = metric.modules.sumOf { it.sizeInKb } + + metric.modules.whenEach { + values.add( + ModulesSourceSizeReport.ModuleSourceSize( + path = path, + sizeInKb = sizeInKb, + coverageRate = sizeInKb.toPercentageOf(totalSourceCount), + diffRate = null // The ratio does not exist when there is only one item + ) + ) + } + + return ModulesSourceSizeReport( + values = values.sortedByDescending { it.sizeInKb }, + totalSourceSizeInKb = totalSourceCount, + totalDiffRate = null // The ratio does not exist when there is only one item + ) + } + + fun generateMultipleItemsReport(metrics: List): ModulesSourceSizeReport { + val firstTotalSourceSize = metrics.first().modules.sumOf { it.sizeInKb } + val lastTotalSourceSize = metrics.last().modules.sumOf { it.sizeInKb } + val totalDiffRate = firstTotalSourceSize.diffPercentageOf(lastTotalSourceSize) + + val values = mutableListOf() + metrics.last().modules.whenEach { + values.add( + ModulesSourceSizeReport.ModuleSourceSize( + path = path, + sizeInKb = sizeInKb, + coverageRate = sizeInKb.toPercentageOf(lastTotalSourceSize), + diffRate = calculateModuleDiffRate(metrics, path, sizeInKb) + ) + ) + } + + return ModulesSourceSizeReport( + values = values.sortedByDescending { it.sizeInKb }, + totalSourceSizeInKb = lastTotalSourceSize, + totalDiffRate = totalDiffRate + ) + } + + fun calculateModuleDiffRate(metrics: List, path: String, value: Long): Float? { + return metrics.first().modules.find { it.path == path }?.sizeInKb?.diffPercentageOf(value) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulessourcesize/report/RenderModulesSourceSizeReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulessourcesize/report/RenderModulesSourceSizeReportStage.kt new file mode 100644 index 00000000..0d0f19e9 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulessourcesize/report/RenderModulesSourceSizeReportStage.kt @@ -0,0 +1,107 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulessourcesize.report + +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNull +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.HtmlUtils + +class RenderModulesSourceSizeReportStage( + private val report: Report +) : Stage { + + companion object { + private const val MODULES_METHOD_COUNT_METRIC_TEMPLATE_ID = "%modules-source-size-metric%" + private const val MODULES_METHOD_COUNT_METRIC_TEMPLATE_FILE_NAME = "modules-source-size-metric-template" + } + + override suspend fun process(input: String): String { + if (report.modulesSourceSizeReport.isNull()) + return input.replace(MODULES_METHOD_COUNT_METRIC_TEMPLATE_ID, getEmptyRender()) + + return input.replace(MODULES_METHOD_COUNT_METRIC_TEMPLATE_ID, getMetricRender()) + } + + fun getEmptyRender(): String { + return HtmlUtils.renderMessage("Modules Source Size is not available!") + } + + fun getMetricRender(): String { + val totalSourceSizeByKb = report.modulesSourceSizeReport?.totalSourceSizeInKb ?: 0 + + var totalDiffRatioRender = "-" + report.modulesSourceSizeReport.whenNotNull { + totalDiffRate.whenNotNull { + totalDiffRatioRender = if (this > 0) { + "+${this}%" + } else if (this < 0) { + "-${this}%" + } else { + "Equals" + } + } + } + + val tableData = buildString { + report.modulesSourceSizeReport?.values?.forEachIndexed { index, it -> + var diffRatioRender = "-" + it.diffRate.whenNotNull { + diffRatioRender = if (this > 0) { + "+${this}%" + } else if (this < 0) { + "-${this}%" + } else { + "Equals" + } + } + append( + """ + + ${index + 1} + ${it.path} + ${it.sizeInKb}kb + ${it.coverageRate}% + $diffRatioRender + + """.trimIndent() + ) + } + } + + val moduleLabels = report.modulesSourceSizeReport?.values?.map { "\"${it.path}\"" } + val moduleValues = report.modulesSourceSizeReport?.values?.map { it.sizeInKb } + + var renderedTemplate = HtmlUtils.getTemplate(MODULES_METHOD_COUNT_METRIC_TEMPLATE_FILE_NAME) + renderedTemplate = renderedTemplate + .replace("%table-data%", tableData) + .replace("%total-source-size%", totalSourceSizeByKb.toString()) + .replace("%total-diff-rate%", totalDiffRatioRender) + .replace("%module-labels%", moduleLabels.toString()) + .replace("%module-values%", moduleValues.toString()) + + return renderedTemplate + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulessourcesize/update/UpdateModulesSourceSizeMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulessourcesize/update/UpdateModulesSourceSizeMetricStage.kt new file mode 100644 index 00000000..edd57f62 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulessourcesize/update/UpdateModulesSourceSizeMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulessourcesize.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class UpdateModulesSourceSizeMetricStage( + private val updateModulesSourceSizeMetricUseCase: UpdateModulesSourceSizeMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + modulesSourceSizeMetric = updateModulesSourceSizeMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulessourcesize/update/UpdateModulesSourceSizeMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulessourcesize/update/UpdateModulesSourceSizeMetricUseCase.kt new file mode 100644 index 00000000..f221ad4d --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulessourcesize/update/UpdateModulesSourceSizeMetricUseCase.kt @@ -0,0 +1,37 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulessourcesize.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesSourceSizeMetric +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.core.UseCaseNoInput + +class UpdateModulesSourceSizeMetricUseCase( + private val repo: DatabaseRepository +) : UseCaseNoInput() { + + override suspend fun execute(): ModulesSourceSizeMetric { + return repo.getTemporaryMetrics().last().modulesSourceSizeMetric!! + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulestimeline/create/CreateModulesTimelineMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulestimeline/create/CreateModulesTimelineMetricStage.kt new file mode 100644 index 00000000..357a31ca --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulestimeline/create/CreateModulesTimelineMetricStage.kt @@ -0,0 +1,42 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulestimeline.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class CreateModulesTimelineMetricStage( + private val buildInfo: BuildInfo, + private val createModulesTimelineMetricUseCase: CreateModulesTimelineMetricUseCase +) : Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + if (buildInfo.isSuccessful) { + modulesTimelineMetric = createModulesTimelineMetricUseCase.execute(buildInfo) + } + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulestimeline/create/CreateModulesTimelineMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulestimeline/create/CreateModulesTimelineMetricUseCase.kt new file mode 100644 index 00000000..d372c274 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulestimeline/create/CreateModulesTimelineMetricUseCase.kt @@ -0,0 +1,62 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulestimeline.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.Module +import io.github.janbarari.gradle.analytics.domain.model.metric.ModuleTimeline +import io.github.janbarari.gradle.analytics.domain.model.metric.ModulesTimelineMetric +import io.github.janbarari.gradle.core.UseCase + +class CreateModulesTimelineMetricUseCase( + private val modules: List +): UseCase() { + + override suspend fun execute(input: BuildInfo): ModulesTimelineMetric { + val start = input.executedTasks.minOfOrNull { it.startedAt } ?: 0 + val end = input.executedTasks.maxOfOrNull { it.finishedAt } ?: 0 + + val result = modules.map { module -> + ModuleTimeline( + path = module.path, + timelines = input.executedTasks.filter { it.path.startsWith(module.path) } + .map { + ModuleTimeline.Timeline( + path = it.path, + start = it.startedAt, + end = it.finishedAt, + isCached = it.isFromCache || it.isUpToDate + ) + } + ) + } + + return ModulesTimelineMetric( + modules = result, + start = start, + end = end, + createdAt = System.currentTimeMillis() + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulestimeline/render/CreateModulesTimelineReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulestimeline/render/CreateModulesTimelineReportStage.kt new file mode 100644 index 00000000..aa774c51 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulestimeline/render/CreateModulesTimelineReportStage.kt @@ -0,0 +1,50 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulestimeline.render + +import io.github.janbarari.gradle.analytics.domain.model.report.ModulesTimelineReport +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.analytics.domain.usecase.GetModulesTimelineUseCase +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.whenNotNull + +class CreateModulesTimelineReportStage( + private val branch: String, + private val getModulesTimelineUseCase: GetModulesTimelineUseCase +) : Stage { + + override suspend fun process(input: Report): Report { + val temp = getModulesTimelineUseCase.execute(branch) + return input.apply { + temp.whenNotNull { + modulesTimelineReport = ModulesTimelineReport( + start = start, + end = end, + createdAt = createdAt, + modules = modules + ) + } + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulestimeline/render/RenderModulesTimelineReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulestimeline/render/RenderModulesTimelineReportStage.kt new file mode 100644 index 00000000..6c87afee --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/modulestimeline/render/RenderModulesTimelineReportStage.kt @@ -0,0 +1,126 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.modulestimeline.render + +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNull +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.DateTimeUtils +import io.github.janbarari.gradle.utils.HtmlUtils + +class RenderModulesTimelineReportStage( + private val report: Report +) : Stage { + + companion object { + private const val MODULES_TIMELINE_METRIC_TEMPLATE_ID = "%modules-execution-timeline-metric%" + private const val MODULES_TIMELINE_METRIC_TEMPLATE_FILE_NAME = "modules-timeline-metric-template" + } + + override suspend fun process(input: String): String { + if (report.modulesTimelineReport.isNull()) { + return input.replace(MODULES_TIMELINE_METRIC_TEMPLATE_ID, getEmptyRender()) + } + + return input.replace(MODULES_TIMELINE_METRIC_TEMPLATE_ID, getMetricRender()) + } + + fun getEmptyRender(): String { + return HtmlUtils.renderMessage("Modules Execution Timeline is not available!") + } + + fun getMetricRender(): String { + var renderedTemplate = HtmlUtils.getTemplate(MODULES_TIMELINE_METRIC_TEMPLATE_FILE_NAME) + + var beginning = 0L + var ending = 0L + var createdAt = 0L + + val result = buildString { + append("[") + appendLine() + report.modulesTimelineReport.whenNotNull { + createdAt = this.createdAt + beginning = start + ending = end + modules.forEachIndexed { index, module -> + append( + buildString { + append("{") + appendLine() + append("label: \"${module.path}\",") + appendLine() + append("times: [") + appendLine() + val color = getColor(index) + module.timelines.forEach { timeline -> + if (timeline.isCached) { + append("{ \"color\": \"#999999\", \"starting_time\": ${ + timeline.start}, \"ending_time\": ${timeline.end} },") + } else { + append("{ \"color\": \"$color\", \"starting_time\": ${ + timeline.start}, \"ending_time\": ${timeline.end} },") + } + appendLine() + } + append("]") + appendLine() + append("},") + } + ) + appendLine() + } + } + appendLine() + append("]") + } + renderedTemplate = renderedTemplate + .replace("%timelines%", result) + .replace("%beginning%", beginning.toString()) + .replace("%ending%", ending.toString()) + .replace("%datetime%", DateTimeUtils.formatToDateTime(createdAt)) + return renderedTemplate + } + + fun getColor(index: Int): String { + val colors = listOf( + "#3b76af", + "#b3c6e5", + "#ef8536", + "#f5bd82", + "#519d3e", + "#a8dc93", + "#c53a32", + "#f19d99", + "#8d6ab8", + "#c2b1d2", + "#84584e", + "#be9e96", + "#d57ebe", + "#c2cd30" + ) + return colors[index % colors.size] + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/noncacheabletasks/create/CreateNonCacheableTasksMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/noncacheabletasks/create/CreateNonCacheableTasksMetricStage.kt new file mode 100644 index 00000000..7fcd497a --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/noncacheabletasks/create/CreateNonCacheableTasksMetricStage.kt @@ -0,0 +1,42 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.noncacheabletasks.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class CreateNonCacheableTasksMetricStage( + private val buildInfo: BuildInfo, + private val createNonCacheableTasksMetricUseCase: CreateNonCacheableTasksMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + if (buildInfo.isSuccessful) { + nonCacheableTasksMetric = createNonCacheableTasksMetricUseCase.execute(buildInfo) + } + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/noncacheabletasks/create/CreateNonCacheableTasksMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/noncacheabletasks/create/CreateNonCacheableTasksMetricUseCase.kt new file mode 100644 index 00000000..dad980c4 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/noncacheabletasks/create/CreateNonCacheableTasksMetricUseCase.kt @@ -0,0 +1,51 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.noncacheabletasks.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.metric.NonCacheableTasksMetric +import io.github.janbarari.gradle.core.UseCase +import io.github.janbarari.gradle.extension.whenEach + +class CreateNonCacheableTasksMetricUseCase( + private val nonCacheableTasks: List +) : UseCase() { + + override suspend fun execute(input: BuildInfo): NonCacheableTasksMetric { + val temp = mutableListOf() + + nonCacheableTasks.whenEach { + input.executedTasks.filter { it.path == this }.forEach { task -> + temp.add( + NonCacheableTasksMetric.NonCacheableTask( + path = task.path, + avgExecutionDurationInMillis = task.getDurationInMillis() + ) + ) + } + } + + return NonCacheableTasksMetric(tasks = temp) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/noncacheabletasks/render/CreateNonCacheableTasksReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/noncacheabletasks/render/CreateNonCacheableTasksReportStage.kt new file mode 100644 index 00000000..bed08750 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/noncacheabletasks/render/CreateNonCacheableTasksReportStage.kt @@ -0,0 +1,60 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.noncacheabletasks.render + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.report.NonCacheableTasksReport +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isBigger +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.modify +import io.github.janbarari.gradle.utils.MathUtils + +class CreateNonCacheableTasksReportStage( + private val metrics: List +): Stage { + + override suspend fun process(input: Report): Report { + return input.apply { + val tasks = metrics.last().nonCacheableTasksMetric?.tasks + ?.modify { + val medianValue = metrics + .filter { it.nonCacheableTasksMetric.isNotNull() } + .flatMap { metric -> + metric.nonCacheableTasksMetric!!.tasks + .filter { it.path == path } + .map { it.avgExecutionDurationInMillis } + } + avgExecutionDurationInMillis = MathUtils.longMedian(medianValue) + } + ?.filter { it.avgExecutionDurationInMillis.isBigger(0) } + ?: emptyList() + + nonCacheableTasksReport = NonCacheableTasksReport( + tasks = tasks + ) + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/noncacheabletasks/render/RenderNonCacheableTasksReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/noncacheabletasks/render/RenderNonCacheableTasksReportStage.kt new file mode 100644 index 00000000..ded0de61 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/noncacheabletasks/render/RenderNonCacheableTasksReportStage.kt @@ -0,0 +1,98 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.noncacheabletasks.render + +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNull +import io.github.janbarari.gradle.extension.toArrayString +import io.github.janbarari.gradle.extension.whenEach +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.HtmlUtils + +class RenderNonCacheableTasksReportStage( + private val report: Report +): Stage { + + companion object { + private const val NON_CACHEABLE_TASKS_METRIC_TEMPLATE_ID = "%non-cacheable-tasks-metric%" + private const val NON_CACHEABLE_TASKS_METRIC_TEMPLATE_FILENAME = "non-cacheable-tasks-metric-template" + } + + override suspend fun process(input: String): String { + if (report.nonCacheableTasksReport.isNull()) + return input.replace(NON_CACHEABLE_TASKS_METRIC_TEMPLATE_ID, getEmptyRender()) + + return input.replace(NON_CACHEABLE_TASKS_METRIC_TEMPLATE_ID, getMetricRender()) + } + + fun getEmptyRender(): String { + return HtmlUtils.renderMessage("Non-cacheable Tasks is not available!") + } + + fun getMetricRender(): String { + var renderedTemplate = HtmlUtils.getTemplate(NON_CACHEABLE_TASKS_METRIC_TEMPLATE_FILENAME) + report.nonCacheableTasksReport.whenNotNull { + val labels = mutableListOf() + val colors = mutableListOf() + val dataset = mutableListOf() + + tasks.sortedByDescending { it.avgExecutionDurationInMillis } + .whenEach { + labels.add(path) + colors.add(getRandomColor()) + dataset.add(avgExecutionDurationInMillis) + } + + val chartHeight = dataset.size * 36 + + renderedTemplate = renderedTemplate + .replace("%labels%", labels.toArrayString()) + .replace("%colors%", colors.toArrayString()) + .replace("%dataset%", dataset.toString()) + .replace("%chart-height%", "${chartHeight}px") + } + return renderedTemplate + } + + fun getRandomColor(): String { + val colors = listOf( + "#3b76af", + "#b3c6e5", + "#ef8536", + "#f5bd82", + "#519d3e", + "#a8dc93", + "#c53a32", + "#f19d99", + "#8d6ab8", + "#c2b1d2", + "#84584e", + "#be9e96", + "#d57ebe", + "#c2cd30" + ) + return colors[colors.indices.random() % colors.size] + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/noncacheabletasks/update/UpdateNonCacheableTasksMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/noncacheabletasks/update/UpdateNonCacheableTasksMetricStage.kt new file mode 100644 index 00000000..47c586f2 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/noncacheabletasks/update/UpdateNonCacheableTasksMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.noncacheabletasks.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class UpdateNonCacheableTasksMetricStage( + private val updateNonCacheableTasksMetricUseCase: UpdateNonCacheableTasksMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + nonCacheableTasksMetric = updateNonCacheableTasksMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/noncacheabletasks/update/UpdateNonCacheableTasksMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/noncacheabletasks/update/UpdateNonCacheableTasksMetricUseCase.kt new file mode 100644 index 00000000..8569025e --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/noncacheabletasks/update/UpdateNonCacheableTasksMetricUseCase.kt @@ -0,0 +1,54 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.noncacheabletasks.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.NonCacheableTasksMetric +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.core.UseCaseNoInput +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.modify +import io.github.janbarari.gradle.utils.MathUtils + +class UpdateNonCacheableTasksMetricUseCase( + private val repo: DatabaseRepository +) : UseCaseNoInput() { + + override suspend fun execute(): NonCacheableTasksMetric { + val tasks = repo.getTemporaryMetrics().last().nonCacheableTasksMetric!!.tasks + .modify { + val medianValue = repo.getTemporaryMetrics() + .filter { it.nonCacheableTasksMetric.isNotNull() } + .flatMap { metric -> + metric.nonCacheableTasksMetric!!.tasks + .filter { task -> task.path == path } + .map { task -> task.avgExecutionDurationInMillis } + } + avgExecutionDurationInMillis = MathUtils.longMedian(medianValue) + } + + return NonCacheableTasksMetric( + tasks = tasks + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/overallbuildprocess/create/CreateOverallBuildProcessMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/overallbuildprocess/create/CreateOverallBuildProcessMetricStage.kt new file mode 100644 index 00000000..68265dba --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/overallbuildprocess/create/CreateOverallBuildProcessMetricStage.kt @@ -0,0 +1,42 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.overallbuildprocess.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class CreateOverallBuildProcessMetricStage( + private val buildInfo: BuildInfo, + private val createOverallBuildProcessMetricUseCase: CreateOverallBuildProcessMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + if (buildInfo.isSuccessful) { + overallBuildProcessMetric = createOverallBuildProcessMetricUseCase.execute(buildInfo) + } + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/overallbuildprocess/create/CreateOverallBuildProcessMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/overallbuildprocess/create/CreateOverallBuildProcessMetricUseCase.kt new file mode 100644 index 00000000..088a049c --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/overallbuildprocess/create/CreateOverallBuildProcessMetricUseCase.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.overallbuildprocess.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.metric.OverallBuildProcessMetric +import io.github.janbarari.gradle.core.UseCase + +class CreateOverallBuildProcessMetricUseCase: UseCase() { + + override suspend fun execute(input: BuildInfo): OverallBuildProcessMetric { + return OverallBuildProcessMetric( + median = input.getTotalDuration().toMillis(), + mean = input.getTotalDuration().toMillis() + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/overallbuildprocess/report/CreateOverallBuildProcessReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/overallbuildprocess/report/CreateOverallBuildProcessReportStage.kt new file mode 100644 index 00000000..17172928 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/overallbuildprocess/report/CreateOverallBuildProcessReportStage.kt @@ -0,0 +1,68 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.overallbuildprocess.report + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.report.OverallBuildProcessReport +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isBiggerEquals +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.mapToOverallBuildProcessMeanTimespanChartPoints +import io.github.janbarari.gradle.extension.mapToOverallBuildProcessMedianTimespanChartPoints +import io.github.janbarari.gradle.extension.whenEmpty + +class CreateOverallBuildProcessReportStage( + private val metrics: List +) : Stage { + + companion object { + private const val SKIP_THRESHOLD_IN_MS = 50L + } + + override suspend fun process(input: Report): Report { + val medianChartPoints = metrics.filter { metric -> + metric.overallBuildProcessMetric.isNotNull() && + metric.overallBuildProcessMetric?.median?.isBiggerEquals(SKIP_THRESHOLD_IN_MS) ?: false + }.mapToOverallBuildProcessMedianTimespanChartPoints() + .whenEmpty { + return input + } + + val meanChartPoints = metrics.filter { metric -> + metric.overallBuildProcessMetric.isNotNull() && + metric.overallBuildProcessMetric?.mean?.isBiggerEquals(SKIP_THRESHOLD_IN_MS) ?: false + }.mapToOverallBuildProcessMeanTimespanChartPoints() + .whenEmpty { + return input + } + + return input.apply { + overallBuildProcessReport = OverallBuildProcessReport( + medianValues = medianChartPoints, + meanValues = meanChartPoints, + ) + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/overallbuildprocess/report/RenderOverallBuildProcessReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/overallbuildprocess/report/RenderOverallBuildProcessReportStage.kt new file mode 100644 index 00000000..2f5d90b4 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/overallbuildprocess/report/RenderOverallBuildProcessReportStage.kt @@ -0,0 +1,98 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.overallbuildprocess.report + +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNull +import io.github.janbarari.gradle.extension.mapToChartPoints +import io.github.janbarari.gradle.extension.maxValue +import io.github.janbarari.gradle.extension.minValue +import io.github.janbarari.gradle.extension.minimize +import io.github.janbarari.gradle.extension.toArrayString +import io.github.janbarari.gradle.extension.toIntList +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.HtmlUtils +import io.github.janbarari.gradle.utils.MathUtils + +class RenderOverallBuildProcessReportStage( + private val report: Report +) : Stage { + + companion object { + private const val CHART_MAX_COLUMNS = 12 + private const val CHART_SUGGESTED_MIN_MAX_PERCENTAGE = 30 + private const val OVERALL_BUILD_PROCESS_METRIC_TEMPLATE_ID = "%overall-build-process-metric%" + private const val OVERALL_BUILD_PROCESS_METRIC_TEMPLATE_FILE_NAME = "overall-build-process-metric-template" + } + + override suspend fun process(input: String): String { + if (report.overallBuildProcessReport.isNull()) + return input.replace(OVERALL_BUILD_PROCESS_METRIC_TEMPLATE_ID, getEmptyRender()) + + return input.replace(OVERALL_BUILD_PROCESS_METRIC_TEMPLATE_ID, getMetricRender()) + } + + fun getEmptyRender(): String { + return HtmlUtils.renderMessage("Overall Build Process is not available!") + } + + fun getMetricRender(): String { + var renderedTemplate = HtmlUtils.getTemplate(OVERALL_BUILD_PROCESS_METRIC_TEMPLATE_FILE_NAME) + report.overallBuildProcessReport.whenNotNull { + val medianChartValues = medianValues + .minimize(CHART_MAX_COLUMNS) + .mapToChartPoints() + .map { it.value } + .toIntList() + .toString() + + val meanChartValues = meanValues + .minimize(CHART_MAX_COLUMNS) + .mapToChartPoints() + .map { it.value } + .toIntList() + .toString() + + val chartLabels = medianValues + .minimize(CHART_MAX_COLUMNS) + .mapToChartPoints() + .map { it.description } + .toArrayString() + + val maximumValue = Math.max(medianValues.maxValue(), meanValues.maxValue()) + val minimumValue = Math.min(medianValues.minValue(), meanValues.minValue()) + val chartSuggestedMaxValue = MathUtils.sumWithPercentage(maximumValue, CHART_SUGGESTED_MIN_MAX_PERCENTAGE) + val chartSuggestedMinValue = MathUtils.deductWithPercentage(minimumValue, CHART_SUGGESTED_MIN_MAX_PERCENTAGE) + + renderedTemplate = renderedTemplate + .replace("%suggested-max-value%", chartSuggestedMaxValue.toString()) + .replace("%suggested-min-value%", chartSuggestedMinValue.toString()) + .replace("%chart-median-values%", medianChartValues) + .replace("%chart-mean-values%", meanChartValues) + .replace("%chart-labels%", chartLabels) + } + return renderedTemplate + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/overallbuildprocess/update/UpdateOverallBuildProcessMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/overallbuildprocess/update/UpdateOverallBuildProcessMetricStage.kt new file mode 100644 index 00000000..d1f34db6 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/overallbuildprocess/update/UpdateOverallBuildProcessMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.overallbuildprocess.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class UpdateOverallBuildProcessMetricStage( + private val updateOverallBuildProcessMetricUseCase: UpdateOverallBuildProcessMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + overallBuildProcessMetric = updateOverallBuildProcessMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/overallbuildprocess/update/UpdateOverallBuildProcessMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/overallbuildprocess/update/UpdateOverallBuildProcessMetricUseCase.kt new file mode 100644 index 00000000..be601fe8 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/overallbuildprocess/update/UpdateOverallBuildProcessMetricUseCase.kt @@ -0,0 +1,63 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.overallbuildprocess.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.OverallBuildProcessMetric +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.core.UseCaseNoInput +import io.github.janbarari.gradle.extension.isBiggerEquals +import io.github.janbarari.gradle.extension.whenEach +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.extension.whenTrue +import io.github.janbarari.gradle.utils.MathUtils + +class UpdateOverallBuildProcessMetricUseCase( + private val repo: DatabaseRepository +) : UseCaseNoInput() { + + companion object { + private const val SKIP_THRESHOLD_IN_MS = 50L + } + + override suspend fun execute(): OverallBuildProcessMetric { + val medianValues = mutableListOf() + val meanValues = mutableListOf() + repo.getTemporaryMetrics().whenEach { + overallBuildProcessMetric.whenNotNull { + // In order to have accurate metric, don't add metric value in Median dataset if it's under 50 milliseconds. + median.isBiggerEquals(SKIP_THRESHOLD_IN_MS).whenTrue { + medianValues.add(median) + } + mean.isBiggerEquals(SKIP_THRESHOLD_IN_MS).whenTrue { + meanValues.add(mean) + } + } + } + + return OverallBuildProcessMetric( + median = MathUtils.longMedian(medianValues), + mean = MathUtils.longMean(meanValues) + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/paralleexecutionrate/create/CreateParallelExecutionRateMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/paralleexecutionrate/create/CreateParallelExecutionRateMetricStage.kt new file mode 100644 index 00000000..380c670e --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/paralleexecutionrate/create/CreateParallelExecutionRateMetricStage.kt @@ -0,0 +1,42 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.paralleexecutionrate.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class CreateParallelExecutionRateMetricStage( + private val buildInfo: BuildInfo, + private val createParallelExecutionRateMetricUseCase: CreateParallelExecutionRateMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + if (buildInfo.isSuccessful) { + parallelExecutionRateMetric = createParallelExecutionRateMetricUseCase.execute(buildInfo) + } + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/paralleexecutionrate/create/CreateParallelExecutionRateMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/paralleexecutionrate/create/CreateParallelExecutionRateMetricUseCase.kt new file mode 100644 index 00000000..5514cb41 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/paralleexecutionrate/create/CreateParallelExecutionRateMetricUseCase.kt @@ -0,0 +1,42 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.paralleexecutionrate.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.metric.ParallelExecutionRateMetric +import io.github.janbarari.gradle.core.UseCase +import io.github.janbarari.gradle.extension.toPercentageOf + +class CreateParallelExecutionRateMetricUseCase : UseCase() { + + override suspend fun execute(input: BuildInfo): ParallelExecutionRateMetric { + val nonParallelExecutionInMillis = input.calculateNonParallelExecutionInMillis() + val parallelExecutionInMillis = input.calculateParallelExecutionByMillis() + val rate = (parallelExecutionInMillis - nonParallelExecutionInMillis) + .toPercentageOf(nonParallelExecutionInMillis) + .toLong() + + return ParallelExecutionRateMetric(medianRate = rate) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/paralleexecutionrate/report/CreateParallelExecutionRateReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/paralleexecutionrate/report/CreateParallelExecutionRateReportStage.kt new file mode 100644 index 00000000..71f175b9 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/paralleexecutionrate/report/CreateParallelExecutionRateReportStage.kt @@ -0,0 +1,52 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.paralleexecutionrate.report + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.report.ParallelExecutionRateReport +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.mapToParallelExecutionRateMedianTimespanPoints +import io.github.janbarari.gradle.extension.whenEmpty + +class CreateParallelExecutionRateReportStage( + private val metrics: List +) : Stage { + + override suspend fun process(input: Report): Report { + val timespanPoints = metrics + .filter { it.parallelExecutionRateMetric.isNotNull() } + .mapToParallelExecutionRateMedianTimespanPoints() + .whenEmpty { + return input + } + + return input.apply { + parallelExecutionRateReport = ParallelExecutionRateReport( + medianValues = timespanPoints + ) + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/paralleexecutionrate/report/RenderParallelExecutionRateReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/paralleexecutionrate/report/RenderParallelExecutionRateReportStage.kt new file mode 100644 index 00000000..31ee5873 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/paralleexecutionrate/report/RenderParallelExecutionRateReportStage.kt @@ -0,0 +1,88 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.paralleexecutionrate.report + +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNull +import io.github.janbarari.gradle.extension.mapToChartPoints +import io.github.janbarari.gradle.extension.maxValue +import io.github.janbarari.gradle.extension.minimize +import io.github.janbarari.gradle.extension.toArrayString +import io.github.janbarari.gradle.extension.toIntList +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.HtmlUtils +import io.github.janbarari.gradle.utils.MathUtils + +/** + * Generates html result for [io.github.janbarari.gradle.analytics.domain.model.report.ParallelExecutionRateReport] + */ +class RenderParallelExecutionRateReportStage( + private val report: Report +): Stage { + + companion object { + private const val CHART_MAX_COLUMNS = 12 + private const val CHART_SUGGESTED_MAX_PERCENTAGE = 30 + private const val PARALLEL_EXECUTION_RATE_METRIC_TEMPLATE_ID = "%parallel-execution-rate-metric%" + private const val PARALLEL_EXECUTION_RATE_METRIC_TEMPLATE_FILE_NAME = "parallel-execution-rate-metric-template" + } + + override suspend fun process(input: String): String { + if (report.parallelExecutionRateReport.isNull()) + return input.replace(PARALLEL_EXECUTION_RATE_METRIC_TEMPLATE_ID, getEmptyRender()) + + return input.replace(PARALLEL_EXECUTION_RATE_METRIC_TEMPLATE_ID, getMetricRender()) + } + + fun getMetricRender(): String { + var renderedTemplate = HtmlUtils.getTemplate(PARALLEL_EXECUTION_RATE_METRIC_TEMPLATE_FILE_NAME) + report.parallelExecutionRateReport.whenNotNull { + val chartPoints = medianValues + .minimize(CHART_MAX_COLUMNS) + .mapToChartPoints() + + val chartValues = chartPoints.map { it.value } + .toIntList() + .toString() + + val chartLabels = chartPoints.map { it.description } + .toArrayString() + + val chartSuggestedMaxValue = MathUtils.sumWithPercentage(chartPoints.maxValue(), CHART_SUGGESTED_MAX_PERCENTAGE) + val chartSuggestedMinValue = 0 + + renderedTemplate = renderedTemplate + .replace("%chart-median-values%", chartValues) + .replace("%chart-labels%", chartLabels) + .replace("%suggested-min-value%", chartSuggestedMinValue.toString()) + .replace("%suggested-max-value%", chartSuggestedMaxValue.toString()) + } + return renderedTemplate + } + + fun getEmptyRender(): String { + return HtmlUtils.renderMessage("Parallel Execution Rate is not available!") + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/paralleexecutionrate/update/UpdateParallelExecutionRateMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/paralleexecutionrate/update/UpdateParallelExecutionRateMetricStage.kt new file mode 100644 index 00000000..9cd010cf --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/paralleexecutionrate/update/UpdateParallelExecutionRateMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.paralleexecutionrate.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class UpdateParallelExecutionRateMetricStage( + private val updateParallelExecutionRateMetricUseCase: UpdateParallelExecutionRateMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + parallelExecutionRateMetric = updateParallelExecutionRateMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/paralleexecutionrate/update/UpdateParallelExecutionRateMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/paralleexecutionrate/update/UpdateParallelExecutionRateMetricUseCase.kt new file mode 100644 index 00000000..d2e700e3 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/paralleexecutionrate/update/UpdateParallelExecutionRateMetricUseCase.kt @@ -0,0 +1,45 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.paralleexecutionrate.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.ParallelExecutionRateMetric +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.core.UseCaseNoInput +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.utils.MathUtils + +class UpdateParallelExecutionRateMetricUseCase( + private val repo: DatabaseRepository +): UseCaseNoInput() { + + override suspend fun execute(): ParallelExecutionRateMetric { + val rates = repo.getTemporaryMetrics() + .filter { it.parallelExecutionRateMetric.isNotNull() } + .map { it.parallelExecutionRateMetric!!.medianRate } + + return ParallelExecutionRateMetric( + medianRate = MathUtils.longMedian(rates) + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/successbuildrate/create/CreateSuccessBuildRateMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/successbuildrate/create/CreateSuccessBuildRateMetricStage.kt new file mode 100644 index 00000000..fda73672 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/successbuildrate/create/CreateSuccessBuildRateMetricStage.kt @@ -0,0 +1,40 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.successbuildrate.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class CreateSuccessBuildRateMetricStage( + private val buildInfo: BuildInfo, + private val createSuccessBuildRateMetricUseCase: CreateSuccessBuildRateMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + successBuildRateMetric = createSuccessBuildRateMetricUseCase.execute(buildInfo) + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/successbuildrate/create/CreateSuccessBuildRateMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/successbuildrate/create/CreateSuccessBuildRateMetricUseCase.kt new file mode 100644 index 00000000..4b71d93b --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/successbuildrate/create/CreateSuccessBuildRateMetricUseCase.kt @@ -0,0 +1,40 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.successbuildrate.create + +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.metric.SuccessBuildRateMetric +import io.github.janbarari.gradle.core.UseCase + +class CreateSuccessBuildRateMetricUseCase: UseCase() { + + override suspend fun execute(input: BuildInfo): SuccessBuildRateMetric { + return SuccessBuildRateMetric( + medianRate = if (input.isSuccessful) 100F else 0F, + meanRate = if (input.isSuccessful) 100F else 0F, + successes = if (input.isSuccessful) 1 else 0, + fails = if (!input.isSuccessful) 1 else 0 + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/successbuildrate/report/CreateSuccessBuildRateReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/successbuildrate/report/CreateSuccessBuildRateReportStage.kt new file mode 100644 index 00000000..152b9421 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/successbuildrate/report/CreateSuccessBuildRateReportStage.kt @@ -0,0 +1,61 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.successbuildrate.report + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.analytics.domain.model.report.SuccessBuildRateReport +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.mapToSuccessBuildRateMeanTimespanChartPoints +import io.github.janbarari.gradle.extension.mapToSuccessBuildRateMedianTimespanChartPoints +import io.github.janbarari.gradle.extension.whenEmpty + +class CreateSuccessBuildRateReportStage( + private val metrics: List +) : Stage { + + override suspend fun process(input: Report): Report { + val medianChartPoints = metrics.filter { metric -> + metric.successBuildRateMetric.isNotNull() + }.mapToSuccessBuildRateMedianTimespanChartPoints() + .whenEmpty { + return input + } + + val meanChartPoints = metrics.filter { metric -> + metric.successBuildRateMetric.isNotNull() + }.mapToSuccessBuildRateMeanTimespanChartPoints() + .whenEmpty { + return input + } + + return input.apply { + successBuildRateReport = SuccessBuildRateReport( + meanValues = meanChartPoints, + medianValues = medianChartPoints + ) + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/successbuildrate/report/RenderSuccessBuildRateReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/successbuildrate/report/RenderSuccessBuildRateReportStage.kt new file mode 100644 index 00000000..76229f45 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/successbuildrate/report/RenderSuccessBuildRateReportStage.kt @@ -0,0 +1,87 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.successbuildrate.report + +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.extension.isNull +import io.github.janbarari.gradle.extension.mapToChartPoints +import io.github.janbarari.gradle.extension.minimize +import io.github.janbarari.gradle.extension.toArrayString +import io.github.janbarari.gradle.extension.toIntList +import io.github.janbarari.gradle.extension.whenNotNull +import io.github.janbarari.gradle.utils.HtmlUtils + +class RenderSuccessBuildRateReportStage( + private val report: Report +): Stage { + + companion object { + private const val CHART_MAX_COLUMNS = 12 + private const val BUILD_SUCCESS_RATIO_METRIC_TEMPLATE_ID = "%success-build-rate-metric%" + private const val BUILD_SUCCESS_RATIO_METRIC_TEMPLATE_FILE_NAME = "success-build-rate-metric-template" + } + + override suspend fun process(input: String): String { + if (report.successBuildRateReport.isNull()) + return input.replace(BUILD_SUCCESS_RATIO_METRIC_TEMPLATE_ID, getEmptyRender()) + + return input.replace(BUILD_SUCCESS_RATIO_METRIC_TEMPLATE_ID, getMetricRender()) + } + + fun getEmptyRender(): String { + return HtmlUtils.renderMessage("Success Build Rate is not available!") + } + + fun getMetricRender(): String { + var renderedTemplate = HtmlUtils.getTemplate(BUILD_SUCCESS_RATIO_METRIC_TEMPLATE_FILE_NAME) + report.successBuildRateReport.whenNotNull { + val medianChartValues = medianValues + .minimize(CHART_MAX_COLUMNS) + .mapToChartPoints() + .map { it.value } + .toIntList() + .toString() + + val meanChartValues = meanValues + .minimize(CHART_MAX_COLUMNS) + .mapToChartPoints() + .map { it.value } + .toIntList() + .toString() + + val chartLabels = medianValues + .minimize(CHART_MAX_COLUMNS) + .mapToChartPoints() + .map { it.description } + .toArrayString() + + renderedTemplate = renderedTemplate + .replace("%chart-median-values%", medianChartValues) + .replace("%chart-mean-values%", meanChartValues) + .replace("%chart-labels%", chartLabels) + } + return renderedTemplate + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/successbuildrate/update/UpdateSuccessBuildRateMetricStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/successbuildrate/update/UpdateSuccessBuildRateMetricStage.kt new file mode 100644 index 00000000..54d13db8 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/successbuildrate/update/UpdateSuccessBuildRateMetricStage.kt @@ -0,0 +1,38 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.successbuildrate.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Stage + +class UpdateSuccessBuildRateMetricStage( + private val updateSuccessBuildRateMetricUseCase: UpdateSuccessBuildRateMetricUseCase +): Stage { + + override suspend fun process(input: BuildMetric): BuildMetric { + return input.apply { + successBuildRateMetric = updateSuccessBuildRateMetricUseCase.execute() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/successbuildrate/update/UpdateSuccessBuildRateMetricUseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/successbuildrate/update/UpdateSuccessBuildRateMetricUseCase.kt new file mode 100644 index 00000000..01fbdb48 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/metric/successbuildrate/update/UpdateSuccessBuildRateMetricUseCase.kt @@ -0,0 +1,70 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.metric.successbuildrate.update + +import io.github.janbarari.gradle.analytics.domain.model.metric.SuccessBuildRateMetric +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.core.UseCaseNoInput +import io.github.janbarari.gradle.extension.toPercentageOf +import io.github.janbarari.gradle.extension.whenEach +import io.github.janbarari.gradle.extension.whenNotNull + +class UpdateSuccessBuildRateMetricUseCase( + private val repo: DatabaseRepository +) : UseCaseNoInput() { + + override suspend fun execute(): SuccessBuildRateMetric { + var meanSuccesses = 0 + var meanFailures = 0 + var medianSuccesses = 0 + var medianFailures = 0 + var successesCount = 0 + var failsCount = 0 + + repo.getTemporaryMetrics().whenEach { + successBuildRateMetric.whenNotNull { + when (meanRate) { + 0F -> meanFailures++ + 100F -> meanSuccesses++ + } + when (medianRate) { + 0F -> medianFailures++ + 100F -> medianSuccesses++ + } + successesCount += successes + failsCount += fails + } + } + + val meanTotalBuildCount = meanFailures + meanSuccesses + val medianTotalBuildCount = medianFailures + medianSuccesses + + return SuccessBuildRateMetric( + medianRate = medianSuccesses.toPercentageOf(medianTotalBuildCount), + meanRate = meanSuccesses.toPercentageOf(meanTotalBuildCount), + successes = successesCount, + fails = failsCount + ) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/CreateReportPipeline.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/CreateReportPipeline.kt new file mode 100644 index 00000000..c5aa91a2 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/CreateReportPipeline.kt @@ -0,0 +1,30 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.reporttask + +import io.github.janbarari.gradle.core.Pipeline +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.analytics.domain.model.report.Report + +open class CreateReportPipeline(firstStage: Stage) : + Pipeline(firstStage) diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/RenderInitialReportStage.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/RenderInitialReportStage.kt new file mode 100644 index 00000000..b03326f2 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/RenderInitialReportStage.kt @@ -0,0 +1,104 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.reporttask + +import io.github.janbarari.gradle.analytics.GradleAnalyticsPlugin +import io.github.janbarari.gradle.core.Stage +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.utils.DateTimeUtils + +class RenderInitialReportStage private constructor( + private val data: List, + private val projectName: String, + private val requestedTasks: String, + private val branch: String, + private val gitHeadCommitHash: String, + private val isCI: Boolean +) : Stage { + + class Builder { + + private var data: List? = null + private var projectName: String? = null + private var requestedTasks: String? = null + private var branch: String? = null + private var gitHeadCommitHash: String? = null + private var isCI: Boolean? = null + + fun data(value: List) = apply { + data = value + } + + fun projectName(value: String) = apply { + projectName = value + } + + fun requestedTasks(value: String) = apply { + requestedTasks = value + } + + fun branch(value: String) = apply { + branch = value + } + + fun gitHeadCommitHash(value: String) = apply { + gitHeadCommitHash = value + } + + fun isCI(value: Boolean) = apply { + isCI = value + } + + fun build(): RenderInitialReportStage { + return RenderInitialReportStage( + data!!, + projectName!!, + requestedTasks!!, + branch!!, + gitHeadCommitHash!!, + isCI!! + ) + } + + } + + override suspend fun process(input: String): String { + var result = input.replace("%root-project-name%", projectName) + .replace("%task-path%", requestedTasks) + .replace("%branch%", branch) + .replace("%git-head-commit-hash%", gitHeadCommitHash) + .replace("%reported-at%", DateTimeUtils.formatToDateTime(System.currentTimeMillis())) + .replace("%is-ci%", if (isCI) "Yes" else "No") + .replace("%plugin-version%", GradleAnalyticsPlugin.PLUGIN_VERSION) + + if (data.isNotEmpty()) { + val oldest = data.last() + val newest = data.first() + result = result.replace("%time-period-end%", DateTimeUtils.formatToDate(oldest.createdAt)) + .replace("%time-period-start%", DateTimeUtils.formatToDate(newest.createdAt)) + } + + return result + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/RenderReportPipeline.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/RenderReportPipeline.kt new file mode 100644 index 00000000..2179adbb --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/RenderReportPipeline.kt @@ -0,0 +1,28 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.reporttask + +import io.github.janbarari.gradle.core.Pipeline +import io.github.janbarari.gradle.core.Stage + +class RenderReportPipeline(firstStage: Stage): Pipeline(firstStage) diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/ReportAnalyticsInjector.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/ReportAnalyticsInjector.kt new file mode 100644 index 00000000..f1938acf --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/ReportAnalyticsInjector.kt @@ -0,0 +1,92 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.reporttask + +import com.squareup.moshi.Moshi +import io.github.janbarari.gradle.analytics.data.DatabaseRepositoryImp +import io.github.janbarari.gradle.analytics.database.Database +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.analytics.domain.usecase.GetMetricsUseCase +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.DatabaseConfig +import io.github.janbarari.gradle.analytics.domain.model.Module +import io.github.janbarari.gradle.analytics.domain.usecase.GetModulesTimelineUseCase + +/** + * Dependency injection for [io.github.janbarari.gradle.analytics.reporttask.ReportAnalyticsTask]. + */ +@ExcludeJacocoGenerated +class ReportAnalyticsInjector( + var branch: String? = null, + var requestedTasks: String? = null, + var isCI: Boolean? = null, + var databaseConfig: DatabaseConfig? = null, + var outputPath: String? = null, + var projectName: String? = null, + var modules: List? = null +) + +@ExcludeJacocoGenerated +fun ReportAnalyticsInjector.provideDatabase(): Database { + return Database(databaseConfig!!, isCI!!) +} + +@ExcludeJacocoGenerated +fun ReportAnalyticsInjector.provideMoshi(): Moshi { + return Moshi.Builder().build() +} + +@ExcludeJacocoGenerated +fun ReportAnalyticsInjector.provideDatabaseRepository(): DatabaseRepository { + return DatabaseRepositoryImp( + db = provideDatabase(), + branch = branch!!, + requestedTasks = requestedTasks!!, + moshi = provideMoshi() + ) +} + +@ExcludeJacocoGenerated +fun ReportAnalyticsInjector.provideGetMetricsUseCase(): GetMetricsUseCase { + return GetMetricsUseCase(provideDatabaseRepository()) +} + +@ExcludeJacocoGenerated +fun ReportAnalyticsInjector.provideGetModulesTimelineUseCase(): GetModulesTimelineUseCase { + return GetModulesTimelineUseCase( + moshi = provideMoshi(), + repo = provideDatabaseRepository() + ) +} + +@ExcludeJacocoGenerated +fun ReportAnalyticsInjector.provideReportAnalyticsLogic(): ReportAnalyticsLogic { + return ReportAnalyticsLogicImp( + provideGetMetricsUseCase(), + provideGetModulesTimelineUseCase(), + isCI!!, + outputPath!!, + projectName!!, + modules!! + ) +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/ReportAnalyticsLogic.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/ReportAnalyticsLogic.kt new file mode 100644 index 00000000..aee9684d --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/ReportAnalyticsLogic.kt @@ -0,0 +1,48 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.reporttask + +import io.github.janbarari.gradle.analytics.reporttask.exception.InvalidPropertyException +import io.github.janbarari.gradle.analytics.reporttask.exception.MissingPropertyException +import java.io.IOException + +interface ReportAnalyticsLogic { + + @kotlin.jvm.Throws(IOException::class) + suspend fun saveReport(renderedHTML: String): String + + suspend fun generateReport(branch: String, requestedTasks: String, period: String): String + + @kotlin.jvm.Throws(MissingPropertyException::class, InvalidPropertyException::class) + fun ensureBranchArgumentValid(branchArgument: String) + + @kotlin.jvm.Throws(MissingPropertyException::class, InvalidPropertyException::class) + fun ensurePeriodArgumentValid(periodArgument: String) + + @kotlin.jvm.Throws(MissingPropertyException::class, InvalidPropertyException::class) + fun ensureTaskArgumentValid(requestedTasksArgument: String) + + @kotlin.jvm.Throws(InvalidPropertyException::class) + fun convertQueryToPeriod(query: String): Pair + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/ReportAnalyticsLogicImp.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/ReportAnalyticsLogicImp.kt new file mode 100644 index 00000000..1d25a27a --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/ReportAnalyticsLogicImp.kt @@ -0,0 +1,285 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.reporttask + +import io.github.janbarari.gradle.analytics.domain.model.Module +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.model.report.Report +import io.github.janbarari.gradle.analytics.domain.usecase.GetMetricsUseCase +import io.github.janbarari.gradle.analytics.domain.usecase.GetModulesTimelineUseCase +import io.github.janbarari.gradle.analytics.metric.buildstatus.render.CreateBuildStatusReportStage +import io.github.janbarari.gradle.analytics.metric.buildstatus.render.RenderBuildStatusReportStage +import io.github.janbarari.gradle.analytics.metric.cachehit.report.CreateCacheHitReportStage +import io.github.janbarari.gradle.analytics.metric.cachehit.report.RenderCacheHitReportStage +import io.github.janbarari.gradle.analytics.metric.configurationprocess.report.CreateConfigurationProcessReportStage +import io.github.janbarari.gradle.analytics.metric.configurationprocess.report.RenderConfigurationProcessReportStage +import io.github.janbarari.gradle.analytics.metric.dependencydetails.render.CreateDependencyDetailsReportStage +import io.github.janbarari.gradle.analytics.metric.dependencydetails.render.RenderDependencyDetailsReportStage +import io.github.janbarari.gradle.analytics.metric.dependencyresolveprocess.report.CreateDependencyResolveProcessReportStage +import io.github.janbarari.gradle.analytics.metric.dependencyresolveprocess.report.RenderDependencyResolveProcessReportStage +import io.github.janbarari.gradle.analytics.metric.executionprocess.report.CreateExecutionProcessReportStage +import io.github.janbarari.gradle.analytics.metric.executionprocess.report.RenderExecutionProcessReportStage +import io.github.janbarari.gradle.analytics.metric.initializationprocess.report.CreateInitializationProcessReportStage +import io.github.janbarari.gradle.analytics.metric.initializationprocess.report.RenderInitializationProcessReportStage +import io.github.janbarari.gradle.analytics.metric.modulesbuildheatmap.render.CreateModulesBuildHeatmapReportStage +import io.github.janbarari.gradle.analytics.metric.modulesbuildheatmap.render.RenderModulesBuildHeatmapReportStage +import io.github.janbarari.gradle.analytics.metric.modulescrashcount.render.CreateModulesCrashCountReportStage +import io.github.janbarari.gradle.analytics.metric.modulescrashcount.render.RenderModulesCrashCountReportStage +import io.github.janbarari.gradle.analytics.metric.modulesdependencygraph.report.CreateModulesDependencyGraphReportStage +import io.github.janbarari.gradle.analytics.metric.modulesdependencygraph.report.RenderModulesDependencyGraphReportStage +import io.github.janbarari.gradle.analytics.metric.modulesexecutionprocess.report.CreateModulesExecutionProcessReportStage +import io.github.janbarari.gradle.analytics.metric.modulesexecutionprocess.report.RenderModulesExecutionProcessReportStage +import io.github.janbarari.gradle.analytics.metric.modulesmethodcount.report.CreateModulesMethodCountReportStage +import io.github.janbarari.gradle.analytics.metric.modulesmethodcount.report.RenderModulesMethodCountStage +import io.github.janbarari.gradle.analytics.metric.modulesourcecount.report.CreateModulesSourceCountReportStage +import io.github.janbarari.gradle.analytics.metric.modulesourcecount.report.RenderModulesSourceCountStage +import io.github.janbarari.gradle.analytics.metric.modulessourcesize.report.CreateModulesSourceSizeReportStage +import io.github.janbarari.gradle.analytics.metric.modulessourcesize.report.RenderModulesSourceSizeReportStage +import io.github.janbarari.gradle.analytics.metric.modulestimeline.render.CreateModulesTimelineReportStage +import io.github.janbarari.gradle.analytics.metric.modulestimeline.render.RenderModulesTimelineReportStage +import io.github.janbarari.gradle.analytics.metric.noncacheabletasks.render.CreateNonCacheableTasksReportStage +import io.github.janbarari.gradle.analytics.metric.noncacheabletasks.render.RenderNonCacheableTasksReportStage +import io.github.janbarari.gradle.analytics.metric.overallbuildprocess.report.CreateOverallBuildProcessReportStage +import io.github.janbarari.gradle.analytics.metric.overallbuildprocess.report.RenderOverallBuildProcessReportStage +import io.github.janbarari.gradle.analytics.metric.paralleexecutionrate.report.CreateParallelExecutionRateReportStage +import io.github.janbarari.gradle.analytics.metric.paralleexecutionrate.report.RenderParallelExecutionRateReportStage +import io.github.janbarari.gradle.analytics.metric.successbuildrate.report.CreateSuccessBuildRateReportStage +import io.github.janbarari.gradle.analytics.metric.successbuildrate.report.RenderSuccessBuildRateReportStage +import io.github.janbarari.gradle.analytics.reporttask.exception.EmptyMetricsException +import io.github.janbarari.gradle.analytics.reporttask.exception.InvalidPropertyException +import io.github.janbarari.gradle.analytics.reporttask.exception.MissingPropertyException +import io.github.janbarari.gradle.extension.getSafeResourceAsStream +import io.github.janbarari.gradle.extension.getTextResourceContent +import io.github.janbarari.gradle.extension.hasSpace +import io.github.janbarari.gradle.extension.toRealPath +import io.github.janbarari.gradle.utils.DateTimeUtils +import org.apache.commons.io.FileUtils +import java.io.File +import java.io.IOException + +/** + * In order to make the [io.github.janbarari.gradle.analytics.reporttask.ReportAnalyticsTask] + * testable and the logic framework independent. + */ +class ReportAnalyticsLogicImp( + private val getMetricsUseCase: GetMetricsUseCase, + private val getModulesTimelineUseCase: GetModulesTimelineUseCase, + private val isCI: Boolean, + private val outputPath: String, + private val projectName: String, + private val modules: List +) : ReportAnalyticsLogic { + + @kotlin.jvm.Throws(EmptyMetricsException::class) + override suspend fun generateReport(branch: String, requestedTasks: String, period: String): String { + val convertedPeriod = convertQueryToPeriod(period) + val data = getMetricsUseCase.execute(convertedPeriod) + + if (data.isEmpty()) throw EmptyMetricsException() + + val report = generateReport( + data = data, + branch = branch, + requestedTasks = requestedTasks + ) + + return generateRender( + data = data, + report = report, + branch = branch, + requestedTasks = requestedTasks + ) + } + + private suspend fun generateReport(data: List, branch: String, requestedTasks: String): Report { + return CreateReportPipeline(CreateInitializationProcessReportStage(data)).addStage( + CreateConfigurationProcessReportStage( + data + ) + ).addStage(CreateExecutionProcessReportStage(data)).addStage(CreateOverallBuildProcessReportStage(data)) + .addStage(CreateModulesSourceCountReportStage(data)).addStage(CreateModulesMethodCountReportStage(data)) + .addStage(CreateCacheHitReportStage(data)).addStage(CreateSuccessBuildRateReportStage(data)) + .addStage(CreateDependencyResolveProcessReportStage(data)).addStage(CreateParallelExecutionRateReportStage(data)) + .addStage(CreateModulesExecutionProcessReportStage(modules, data)) + .addStage(CreateModulesDependencyGraphReportStage(data)) + .addStage(CreateModulesTimelineReportStage(branch, getModulesTimelineUseCase)) + .addStage(CreateBuildStatusReportStage(modules, data)).addStage(CreateModulesBuildHeatmapReportStage(data)) + .addStage(CreateDependencyDetailsReportStage(data)).addStage(CreateNonCacheableTasksReportStage(data)) + .addStage(CreateModulesSourceSizeReportStage(data)).addStage(CreateModulesCrashCountReportStage(modules, data)) + .execute( + Report( + branch = branch, requestedTasks = requestedTasks + ) + ) + } + + private suspend fun generateRender( + data: List, report: Report, branch: String, requestedTasks: String + ): String { + val rawHTML: String = getTextResourceContent("index-template.html") + + val renderInitialReportStage = RenderInitialReportStage.Builder() + .gitHeadCommitHash(data.last().gitHeadCommitHash.replace("\"", "")) + .requestedTasks(requestedTasks) + .projectName(projectName) + .branch(branch) + .data(data) + .isCI(isCI) + .build() + + return RenderReportPipeline(renderInitialReportStage) + .addStage(RenderInitializationProcessReportStage(report)) + .addStage(RenderConfigurationProcessReportStage(report)) + .addStage(RenderExecutionProcessReportStage(report)) + .addStage(RenderOverallBuildProcessReportStage(report)) + .addStage(RenderModulesSourceCountStage(report)) + .addStage(RenderModulesMethodCountStage(report)) + .addStage(RenderCacheHitReportStage(report)) + .addStage(RenderSuccessBuildRateReportStage(report)) + .addStage(RenderDependencyResolveProcessReportStage(report)) + .addStage(RenderParallelExecutionRateReportStage(report)) + .addStage(RenderModulesExecutionProcessReportStage(report)) + .addStage(RenderModulesDependencyGraphReportStage(report)) + .addStage(RenderModulesTimelineReportStage(report)) + .addStage(RenderBuildStatusReportStage(report)) + .addStage(RenderModulesBuildHeatmapReportStage(report)) + .addStage(RenderDependencyDetailsReportStage(report)) + .addStage(RenderNonCacheableTasksReportStage(report)) + .addStage(RenderModulesSourceSizeReportStage(report)) + .addStage(RenderModulesCrashCountReportStage(report)) + .execute(rawHTML) + } + + @kotlin.jvm.Throws(IOException::class) + override suspend fun saveReport(renderedHTML: String): String { + val resources = listOf( + "nunito.ttf", "plugin-logo.png", "styles.css", "functions.js", "chart.js", "mermaid.js", "d3.js", "timeline.js" + ) + val savePath = "${outputPath.toRealPath()}/gradle-analytics-plugin" + + //copy resources + resources.forEach { resource -> + FileUtils.copyInputStreamToFile( + javaClass.getSafeResourceAsStream("/res/$resource"), File("$savePath/res/$resource") + ) + } + + //write index.html + File("$savePath/index.html").writeText(renderedHTML) + + return "$savePath/index.html" + } + + /** + * Ensures the `--branch` input argument is set and valid. + */ + @kotlin.jvm.Throws(MissingPropertyException::class, InvalidPropertyException::class) + override fun ensureBranchArgumentValid(branchArgument: String) { + if (branchArgument.isEmpty()) throw MissingPropertyException("`--branch` is not present!") + if (branchArgument.hasSpace()) throw InvalidPropertyException("`--branch` is not valid!") + } + + /** + * Ensures the `--period` input argument is set and valid. + */ + @kotlin.jvm.Throws(MissingPropertyException::class, InvalidPropertyException::class) + override fun ensurePeriodArgumentValid(periodArgument: String) { + if (periodArgument.isEmpty()) throw MissingPropertyException("`--period` is not present!") + convertQueryToPeriod(periodArgument) + } + + /** + * Ensures the `--task` input argument is set and valid. + */ + @kotlin.jvm.Throws(MissingPropertyException::class) + override fun ensureTaskArgumentValid(requestedTasksArgument: String) { + if (requestedTasksArgument.isEmpty()) throw MissingPropertyException("`--task` is not present!") + } + + @kotlin.jvm.Throws(InvalidPropertyException::class) + override fun convertQueryToPeriod(query: String): Pair { + + @Suppress("ComplexCondition") + fun isCorrectlySorted(query: String): Boolean { + val temp = query.split(" ").filter { it.isNotEmpty() }.map { it.last() } + + return when (temp) { + listOf('y', 'm', 'd') -> true + listOf('y', 'd') -> true + listOf('y', 'm') -> true + listOf('y') -> true + listOf('m', 'd') -> true + listOf('m') -> true + listOf('d') -> true + else -> false + } + } + + try { + // today + if (query == "today") return DateTimeUtils.getDayStartMs() to DateTimeUtils.getDayEndMs() + + // custom dates + if (query.contains(",")) { + var startDate = query.split(",")[0] + var endDate = query.split(",")[1] + startDate = startDate.replace("s:", "") + endDate = endDate.replace("e:", "") + val start = DateTimeUtils.convertDateToEpochMilli(startDate) + val end = DateTimeUtils.convertDateToEpochMilli(endDate) + return start to end + } + + // relative + var years = 0 + var months = 0 + var days = 0 + if (query.chars().filter { it == 'y'.code }.count() >= 2 || query.chars().filter { it == 'm'.code } + .count() >= 2 || query.chars().filter { it == 'd'.code } + .count() >= 2) throw InvalidPropertyException("--period has duplicate input") + + query.split(" ").forEach { + if (it.last() == 'y') years = it.substring(0, it.length - 1).toInt() + else if (it.last() == 'm') months = it.substring(0, it.length - 1).toInt() + else if (it.last() == 'd') days = it.substring(0, it.length - 1).toInt() + else throw InvalidPropertyException("--period is wrong") + + } + + if (!isCorrectlySorted(query)) throw InvalidPropertyException("--period is wrong sorted") + + val duration: Long = (years * 32_140_800_000L) + (months * 2_678_400_000L) + (days * 86_400_000L) + if (duration > 32_140_800_000L) throw InvalidPropertyException("--period can not be more than 1 year!") + + val start = DateTimeUtils.getDayStartMs() - duration + val end = DateTimeUtils.getDayEndMs() + return start to end + } catch (e: Throwable) { + if (e is InvalidPropertyException) throw e + throw InvalidPropertyException( + "--period can be like \"today\", \"s:yyyy/MM/dd,e:yyyy/MM/dd\", \"1y\", \"4m\", \"38d\", \"3m 06d\"" + ) + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/ReportAnalyticsTask.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/ReportAnalyticsTask.kt new file mode 100644 index 00000000..c5fabe8a --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/ReportAnalyticsTask.kt @@ -0,0 +1,162 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.reporttask + +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.DatabaseConfig +import io.github.janbarari.gradle.analytics.GradleAnalyticsPluginConfig +import io.github.janbarari.gradle.analytics.domain.model.Module.Companion.toModule +import io.github.janbarari.gradle.analytics.reporttask.exception.EmptyMetricsException +import io.github.janbarari.gradle.extension.envCI +import io.github.janbarari.gradle.extension.isDependingOnOtherProject +import io.github.janbarari.gradle.extension.registerTask +import io.github.janbarari.gradle.utils.ConsolePrinter +import kotlinx.coroutines.runBlocking +import org.gradle.api.DefaultTask +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.tasks.Input +import org.gradle.api.tasks.TaskAction +import org.gradle.api.tasks.options.Option +import org.gradle.work.DisableCachingByDefault + +/** + * A Gradle task that generates the report based on `git branch`, `time period` and `task`. + * + * A quick instruction about how to invoke the task: + * `./gradlew reportAnalytics --branch="{your-branch}" --task="{your-task}" --period="{a-number-between-1-to-12}"` + */ +@ExcludeJacocoGenerated +@DisableCachingByDefault +abstract class ReportAnalyticsTask : DefaultTask() { + + companion object { + const val TASK_NAME = "reportAnalytics" + + @ExcludeJacocoGenerated + fun register(config: GradleAnalyticsPluginConfig) { + config.project.registerTask(TASK_NAME) { + projectNameProperty.set(project.rootProject.name) + envCIProperty.set(project.envCI().isPresent) + outputPathProperty.set(config.outputPath) + trackingTasksProperty.set(config.trackingTasks) + trackingBranchesProperty.set(config.trackingBranches) + databaseConfigProperty.set(config.getDatabaseConfig()) + outputs.cacheIf { false } + } + } + } + + @set:Option(option = "branch", description = "Git branch name") + @get:Input + var branchArgument: String = "" + + @set:Option(option = "task", description = "Tracking task path") + @get:Input + var requestedTasksArgument: String = "" + + @set:Option(option = "period", description = "Report period") + @get:Input + var periodArgument: String = "" + + @get:Input + abstract val projectNameProperty: Property + + @get:Input + abstract val envCIProperty: Property + + @get:Input + abstract val outputPathProperty: Property + + @get:Input + abstract val trackingTasksProperty: ListProperty + + @get:Input + abstract val trackingBranchesProperty: ListProperty + + @get:Input + abstract val databaseConfigProperty: Property + + /** + * Invokes when the task execution process started. + */ + @TaskAction + fun execute() = runBlocking { + val modules = project.subprojects + .filter { it.isDependingOnOtherProject() } + .map { it.toModule() } + + val injector = ReportAnalyticsInjector( + requestedTasks = requestedTasksArgument, + isCI = envCIProperty.get(), + databaseConfig = databaseConfigProperty.get(), + branch = branchArgument, + outputPath = outputPathProperty.get(), + projectName = projectNameProperty.get(), + modules = modules + ) + + with(injector.provideReportAnalyticsLogic()) { + ensureBranchArgumentValid(branchArgument) + ensurePeriodArgumentValid(periodArgument) + ensureTaskArgumentValid(requestedTasksArgument) + try { + val reportPath = saveReport(generateReport(branchArgument, requestedTasksArgument, periodArgument)) + printSuccessfulResult(reportPath) + } catch (e: EmptyMetricsException) { + printNoData() + } + } + } + + private fun printSuccessfulResult(reportPath: String) { + ConsolePrinter(blockCharWidth = reportPath.length).run { + printFirstLine() + printLine(left = "Gradle Analytics Plugin") + printBreakLine(char = '-') + printLine(left = "Report generated successfully") + printLine(left = reportPath) + printBreakLine(char = '-') + printLine(left = "Made with โค for everyone") + printLine(left = "https://github.com/janbarari/gradle-analytics-plugin") + printLine(right = " โ†– Tap the โ˜† button to support us") + printLastLine() + } + } + + private fun printNoData() { + val message = listOf( + "There is no data to process. Please check the plugin configuration", + "and wait until the first desired task information is saved." + ) + ConsolePrinter(blockCharWidth = message[0].length).run { + printFirstLine() + printLine(left = "Gradle Analytics Plugin") + printBreakLine(char = '-') + printLine(left = message[0]) + printLine(left = message[1]) + printLastLine() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/exception/EmptyMetricsException.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/exception/EmptyMetricsException.kt new file mode 100644 index 00000000..1883d1ed --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/exception/EmptyMetricsException.kt @@ -0,0 +1,27 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.reporttask.exception + +class EmptyMetricsException: Throwable() { + override val message: String = "No metrics are available for generating reports!" +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/exception/InvalidPropertyException.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/exception/InvalidPropertyException.kt new file mode 100644 index 00000000..97d28ff2 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/exception/InvalidPropertyException.kt @@ -0,0 +1,30 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.reporttask.exception + +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +class InvalidPropertyException(msg: String): Throwable() { + override val message: String = msg +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/exception/MissingPropertyException.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/exception/MissingPropertyException.kt new file mode 100644 index 00000000..c3dad1a4 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/reporttask/exception/MissingPropertyException.kt @@ -0,0 +1,30 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.reporttask.exception + +import io.github.janbarari.gradle.ExcludeJacocoGenerated + +@ExcludeJacocoGenerated +class MissingPropertyException(msg: String): Throwable() { + override val message: String = msg +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/DependencyGraphGenerator.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/DependencyGraphGenerator.kt new file mode 100644 index 00000000..d46cacbc --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/DependencyGraphGenerator.kt @@ -0,0 +1,61 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.scanner + +import io.github.janbarari.gradle.analytics.domain.model.ModuleDependency +import io.github.janbarari.gradle.analytics.domain.model.ModulesDependencyGraph +import io.github.janbarari.gradle.extension.isDependingOnOtherProject +import org.gradle.api.Project +import org.gradle.api.artifacts.ProjectDependency + +object DependencyGraphGenerator { + + fun generate(project: Project): ModulesDependencyGraph { + val dependencies = mutableListOf() + + project.subprojects.filter { + it.isDependingOnOtherProject() + }.forEach { subProject -> + subProject.configurations.forEach { configuration -> + configuration.dependencies.withType(ProjectDependency::class.java).forEach { dependency -> + if (dependency.dependencyProject.path != subProject.path) { + dependencies.add( + ModuleDependency( + path = subProject.path, + configuration = configuration.name, + dependency = dependency.dependencyProject.path + ) + ) + } + + } + } + + } + + return ModulesDependencyGraph(dependencies) + } + +} + + diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/ScannerUtils.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/ScannerUtils.kt new file mode 100644 index 00000000..90a1d02f --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/ScannerUtils.kt @@ -0,0 +1,100 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.scanner + +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.GradleAnalyticsPluginConfig +import io.github.janbarari.gradle.analytics.domain.model.Dependency.Companion.getThirdPartyDependencies +import io.github.janbarari.gradle.analytics.domain.model.Module.Companion.toModule +import io.github.janbarari.gradle.analytics.scanner.configuration.BuildConfigurationService +import io.github.janbarari.gradle.analytics.scanner.dependencyresolution.BuildDependencyResolutionService +import io.github.janbarari.gradle.analytics.scanner.execution.BuildExecutionService +import io.github.janbarari.gradle.analytics.scanner.initialization.BuildInitializationService +import io.github.janbarari.gradle.extension.envCI +import io.github.janbarari.gradle.extension.getNonCacheableTasks +import io.github.janbarari.gradle.extension.getRequestedTasks +import io.github.janbarari.gradle.extension.isDependingOnOtherProject +import org.gradle.api.Project +import org.gradle.build.event.BuildEventsListenerRegistry + +@ExcludeJacocoGenerated +object ScannerUtils { + + fun setupScannerServices( + config: GradleAnalyticsPluginConfig, + registry: BuildEventsListenerRegistry + ) { + setupInitializationService(config.project) + setupDependencyResolutionService(config.project) + setupConfigurationService(config.project) + setupExecutionService(config.project, registry, config) + } + + private fun setupExecutionService( + project: Project, + registry: BuildEventsListenerRegistry, + configuration: GradleAnalyticsPluginConfig + ) { + project.gradle.projectsEvaluated { + val nonCacheableTasks = project.allprojects + .flatMap { it.tasks.getNonCacheableTasks() } + + val modules = project.subprojects + .filter { it.isDependingOnOtherProject() } + .map { it.toModule() } + + val modulesDependencyGraph = DependencyGraphGenerator.generate(project) + + val buildExecutionService = project.gradle.sharedServices.registerIfAbsent( + BuildExecutionService::class.java.simpleName, + BuildExecutionService::class.java + ) { spec -> + with(spec.parameters) { + databaseConfig.set(configuration.getDatabaseConfig()) + envCI.set(project.envCI().isPresent) + requestedTasks.set(project.gradle.getRequestedTasks()) + trackingTasks.set(configuration.trackingTasks) + trackingBranches.set(configuration.trackingBranches) + this.modules.set(modules) + this.modulesDependencyGraph.set(modulesDependencyGraph) + this.dependencies.set(project.getThirdPartyDependencies()) + this.nonCachableTasks.set(nonCacheableTasks) + } + } + registry.onTaskCompletion(buildExecutionService) + } + } + + private fun setupInitializationService(project: Project) { + project.gradle.addBuildListener(BuildInitializationService(project.gradle)) + } + + private fun setupConfigurationService(project: Project) { + project.gradle.addBuildListener(BuildConfigurationService()) + } + + private fun setupDependencyResolutionService(project: Project) { + project.gradle.addBuildListener(BuildDependencyResolutionService()) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/configuration/BuildConfigurationService.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/configuration/BuildConfigurationService.kt new file mode 100644 index 00000000..082cb61a --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/configuration/BuildConfigurationService.kt @@ -0,0 +1,70 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.scanner.configuration + +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import org.gradle.BuildResult +import org.gradle.api.initialization.Settings +import org.gradle.api.invocation.Gradle +import org.gradle.internal.InternalBuildListener + +/** + * Track and holds the build configuration finish timestamp to use by + * [io.github.janbarari.gradle.analytics.scanner.execution.BuildExecutionService]. + */ +class BuildConfigurationService : InternalBuildListener { + + companion object { + var CONFIGURED_AT: Long = 0L + + fun reset() { + CONFIGURED_AT = 0L + } + + } + + init { + reset() + } + + @ExcludeJacocoGenerated + override fun settingsEvaluated(settings: Settings) { + // called when the root project settings evaluated. + } + + @ExcludeJacocoGenerated + override fun projectsLoaded(gradle: Gradle) { + // called when projects files loaded. + } + + override fun projectsEvaluated(gradle: Gradle) { + CONFIGURED_AT = System.currentTimeMillis() + } + + @ExcludeJacocoGenerated + @Deprecated("Deprecated") + override fun buildFinished(result: BuildResult) { + // This method is deprecated, Execution process are handled by BuildExecutionService.kt + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/dependencyresolution/BuildDependencyResolutionService.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/dependencyresolution/BuildDependencyResolutionService.kt new file mode 100644 index 00000000..9caa7575 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/dependencyresolution/BuildDependencyResolutionService.kt @@ -0,0 +1,89 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.scanner.dependencyresolution + +import io.github.janbarari.gradle.analytics.domain.model.DependencyResolveInfo +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import org.gradle.BuildResult +import org.gradle.api.artifacts.DependencyResolutionListener +import org.gradle.api.artifacts.ResolvableDependencies +import org.gradle.api.initialization.Settings +import org.gradle.api.invocation.Gradle +import org.gradle.internal.InternalBuildListener +import java.util.concurrent.ConcurrentHashMap + +/** + * Records the build dependencies resolve information to use by + * [io.github.janbarari.gradle.analytics.scanner.execution.BuildExecutionService]. + */ +class BuildDependencyResolutionService : InternalBuildListener, DependencyResolutionListener { + + companion object { + val dependenciesResolveInfo: ConcurrentHashMap = + ConcurrentHashMap() + + fun reset() { + dependenciesResolveInfo.clear() + } + + } + + init { + reset() + } + + @ExcludeJacocoGenerated + override fun beforeResolve(dependencies: ResolvableDependencies) { + dependenciesResolveInfo[dependencies.path] = DependencyResolveInfo( + dependencies.path, + startedAt = System.currentTimeMillis() + ) + } + + @ExcludeJacocoGenerated + override fun afterResolve(dependencies: ResolvableDependencies) { + dependenciesResolveInfo[dependencies.path]?.finishedAt = System.currentTimeMillis() + } + + @ExcludeJacocoGenerated + override fun settingsEvaluated(settings: Settings) { + // Added because gradle allows when InternalBuildListener.kt is implemented in the service class. + } + + @ExcludeJacocoGenerated + override fun projectsLoaded(gradle: Gradle) { + // Added because gradle allows when InternalBuildListener.kt is implemented in the service class. + } + + @ExcludeJacocoGenerated + override fun projectsEvaluated(gradle: Gradle) { + // Added because gradle allows when InternalBuildListener.kt is implemented in the service class. + } + + @ExcludeJacocoGenerated + @Deprecated("Deprecated") + override fun buildFinished(result: BuildResult) { + // Added because gradle allows when InternalBuildListener.kt is implemented in the service class. + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/execution/BuildExecutionInjector.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/execution/BuildExecutionInjector.kt new file mode 100644 index 00000000..45cbda17 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/execution/BuildExecutionInjector.kt @@ -0,0 +1,353 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.scanner.execution + +import com.squareup.moshi.Moshi +import io.github.janbarari.gradle.analytics.data.DatabaseRepositoryImp +import io.github.janbarari.gradle.analytics.database.Database +import io.github.janbarari.gradle.analytics.domain.repository.DatabaseRepository +import io.github.janbarari.gradle.analytics.domain.usecase.SaveMetricUseCase +import io.github.janbarari.gradle.analytics.domain.usecase.SaveTemporaryMetricUseCase +import io.github.janbarari.gradle.analytics.metric.initializationprocess.update.UpdateInitializationProcessMetricUseCase +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.DatabaseConfig +import io.github.janbarari.gradle.analytics.domain.model.Dependency +import io.github.janbarari.gradle.analytics.domain.model.Module +import io.github.janbarari.gradle.analytics.domain.model.ModulesDependencyGraph +import io.github.janbarari.gradle.analytics.domain.usecase.UpsertModulesTimelineUseCase +import io.github.janbarari.gradle.analytics.metric.successbuildrate.create.CreateSuccessBuildRateMetricUseCase +import io.github.janbarari.gradle.analytics.metric.successbuildrate.update.UpdateSuccessBuildRateMetricUseCase +import io.github.janbarari.gradle.analytics.metric.cachehit.create.CreateCacheHitMetricUseCase +import io.github.janbarari.gradle.analytics.metric.cachehit.update.UpdateCacheHitMetricUseCase +import io.github.janbarari.gradle.analytics.metric.configurationprocess.create.CreateConfigurationProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.configurationprocess.update.UpdateConfigurationProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.dependencydetails.create.CreateDependencyDetailsMetricUseCase +import io.github.janbarari.gradle.analytics.metric.dependencydetails.update.UpdateDependencyDetailsMetricUseCase +import io.github.janbarari.gradle.analytics.metric.dependencyresolveprocess.create.CreateDependencyResolveProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.dependencyresolveprocess.update.UpdateDependencyResolveProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.executionprocess.create.CreateExecutionProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.executionprocess.update.UpdateExecutionProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.initializationprocess.create.CreateInitializationProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulesbuildheatmap.create.CreateModulesBuildHeatmapMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulesbuildheatmap.update.UpdateModulesBuildHeatmapMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulescrashcount.create.CreateModulesCrashCountMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulescrashcount.update.UpdateModulesCrashCountMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulesdependencygraph.create.CreateModulesDependencyGraphMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulesdependencygraph.update.UpdateModulesDependencyGraphMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulesexecutionprocess.create.CreateModulesExecutionProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulesexecutionprocess.update.UpdateModulesExecutionProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulesmethodcount.create.CreateModulesMethodCountMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulesmethodcount.update.UpdateModulesMethodCountMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulesourcecount.create.CreateModulesSourceCountMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulesourcecount.update.UpdateModulesSourceCountMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulessourcesize.create.CreateModulesSourceSizeMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulessourcesize.update.UpdateModulesSourceSizeMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulestimeline.create.CreateModulesTimelineMetricUseCase +import io.github.janbarari.gradle.analytics.metric.noncacheabletasks.create.CreateNonCacheableTasksMetricUseCase +import io.github.janbarari.gradle.analytics.metric.noncacheabletasks.update.UpdateNonCacheableTasksMetricUseCase +import io.github.janbarari.gradle.analytics.metric.paralleexecutionrate.create.CreateParallelExecutionRateMetricUseCase +import io.github.janbarari.gradle.analytics.metric.paralleexecutionrate.update.UpdateParallelExecutionRateMetricUseCase +import io.github.janbarari.gradle.analytics.metric.overallbuildprocess.create.CreateOverallBuildProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.overallbuildprocess.update.UpdateOverallBuildProcessMetricUseCase +import io.github.janbarari.gradle.extension.separateElementsWithSpace + +/** + * Dependency injector for [io.github.janbarari.gradle.analytics.scanner.execution.BuildExecutionLogic]. + */ +@ExcludeJacocoGenerated +data class BuildExecutionInjector( + var databaseConfig: DatabaseConfig? = null, + var isCI: Boolean? = null, + var branch: String? = null, + var requestedTasks: List? = null, + var trackingBranches: List? = null, + var trackingTasks: List? = null, + var modules: List? = null, + var modulesDependencyGraph: ModulesDependencyGraph? = null, + var thirdPartyDependencies: List? = null, + var nonCachableTasks: List? = null +) + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideDatabase(): Database { + return Database(databaseConfig!!, isCI!!) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideMoshi(): Moshi { + return Moshi.Builder().build() +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideDatabaseRepository(): DatabaseRepository { + return DatabaseRepositoryImp( + db = provideDatabase(), + branch = branch!!, + requestedTasks = requestedTasks!!.separateElementsWithSpace(), + moshi = provideMoshi() + ) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideUpdateInitializationMetricUseCase(): UpdateInitializationProcessMetricUseCase { + return UpdateInitializationProcessMetricUseCase(provideDatabaseRepository()) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideUpdateConfigurationMetricUseCase(): UpdateConfigurationProcessMetricUseCase { + return UpdateConfigurationProcessMetricUseCase(provideDatabaseRepository()) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideUpdateExecutionProcessMetricUseCase(): UpdateExecutionProcessMetricUseCase { + return UpdateExecutionProcessMetricUseCase(provideDatabaseRepository()) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideUpdateOverallBuildProcessMetricUseCase(): UpdateOverallBuildProcessMetricUseCase { + return UpdateOverallBuildProcessMetricUseCase(provideDatabaseRepository()) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideUpdateModulesSourceCountMetricUseCase(): UpdateModulesSourceCountMetricUseCase { + return UpdateModulesSourceCountMetricUseCase(provideDatabaseRepository()) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideUpdateModulesMethodCountMetricUseCase(): UpdateModulesMethodCountMetricUseCase { + return UpdateModulesMethodCountMetricUseCase(provideDatabaseRepository()) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideUpdateCacheHitMetricUseCase(): UpdateCacheHitMetricUseCase { + return UpdateCacheHitMetricUseCase(provideDatabaseRepository()) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideUpdateSuccessBuildRateMetricUseCase(): UpdateSuccessBuildRateMetricUseCase { + return UpdateSuccessBuildRateMetricUseCase(provideDatabaseRepository()) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideUpdateDependencyResolveProcessMetricUseCase(): UpdateDependencyResolveProcessMetricUseCase { + return UpdateDependencyResolveProcessMetricUseCase(provideDatabaseRepository()) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideUpdateParallelExecutionRateMetricUseCase(): UpdateParallelExecutionRateMetricUseCase { + return UpdateParallelExecutionRateMetricUseCase(provideDatabaseRepository()) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideUpdateModulesExecutionProcessMetricUseCase(): UpdateModulesExecutionProcessMetricUseCase { + return UpdateModulesExecutionProcessMetricUseCase(provideDatabaseRepository(), modules!!) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideUpdateModulesDependencyGraphMetricUseCase(): UpdateModulesDependencyGraphMetricUseCase { + return UpdateModulesDependencyGraphMetricUseCase(provideDatabaseRepository()) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideUpdateModulesBuildHeatmapMetricUseCase(): UpdateModulesBuildHeatmapMetricUseCase { + return UpdateModulesBuildHeatmapMetricUseCase(provideDatabaseRepository()) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideUpdateDependencyDetailsMetricUseCase(): UpdateDependencyDetailsMetricUseCase { + return UpdateDependencyDetailsMetricUseCase(provideDatabaseRepository()) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideUpdateNonCacheableTasksMetricUseCase(): UpdateNonCacheableTasksMetricUseCase { + return UpdateNonCacheableTasksMetricUseCase(provideDatabaseRepository()) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideUpdateModulesSourceSizeMetricUseCase(): UpdateModulesSourceSizeMetricUseCase { + return UpdateModulesSourceSizeMetricUseCase(provideDatabaseRepository()) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideUpdateModulesCrashCountMetricUseCase(): UpdateModulesCrashCountMetricUseCase { + return UpdateModulesCrashCountMetricUseCase(provideDatabaseRepository(), modules!!) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideSaveMetricUseCase(): SaveMetricUseCase { + return SaveMetricUseCase( + provideDatabaseRepository(), + provideUpdateInitializationMetricUseCase(), + provideUpdateConfigurationMetricUseCase(), + provideUpdateExecutionProcessMetricUseCase(), + provideUpdateOverallBuildProcessMetricUseCase(), + provideUpdateModulesSourceCountMetricUseCase(), + provideUpdateModulesMethodCountMetricUseCase(), + provideUpdateCacheHitMetricUseCase(), + provideUpdateSuccessBuildRateMetricUseCase(), + provideUpdateDependencyResolveProcessMetricUseCase(), + provideUpdateParallelExecutionRateMetricUseCase(), + provideUpdateModulesExecutionProcessMetricUseCase(), + provideUpdateModulesDependencyGraphMetricUseCase(), + provideUpdateModulesBuildHeatmapMetricUseCase(), + provideUpdateDependencyDetailsMetricUseCase(), + provideUpdateNonCacheableTasksMetricUseCase(), + provideUpdateModulesSourceSizeMetricUseCase(), + provideUpdateModulesCrashCountMetricUseCase() + ) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideSaveTemporaryMetricUseCase(): SaveTemporaryMetricUseCase { + return SaveTemporaryMetricUseCase(provideDatabaseRepository()) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideUpsertModulesTimelineUseCase(): UpsertModulesTimelineUseCase { + return UpsertModulesTimelineUseCase( + moshi = provideMoshi(), + repo = provideDatabaseRepository() + ) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideCreateInitializationProcessMetricUseCase(): CreateInitializationProcessMetricUseCase { + return CreateInitializationProcessMetricUseCase() +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideCreateConfigurationProcessMetricUseCase(): CreateConfigurationProcessMetricUseCase { + return CreateConfigurationProcessMetricUseCase() +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideCreateExecutionProcessMetricUseCase(): CreateExecutionProcessMetricUseCase { + return CreateExecutionProcessMetricUseCase() +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideCreateOverallBuildProcessMetricUseCase(): CreateOverallBuildProcessMetricUseCase { + return CreateOverallBuildProcessMetricUseCase() +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideCreateModulesSourceCountMetricUseCase(): CreateModulesSourceCountMetricUseCase { + return CreateModulesSourceCountMetricUseCase(modules!!) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideCreateModulesMethodCountMetricUseCase(): CreateModulesMethodCountMetricUseCase { + return CreateModulesMethodCountMetricUseCase(modules!!) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideCreateCacheHitMetricUseCase(): CreateCacheHitMetricUseCase { + return CreateCacheHitMetricUseCase(modules!!) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideCreateSuccessBuildRateMetricUseCase(): CreateSuccessBuildRateMetricUseCase { + return CreateSuccessBuildRateMetricUseCase() +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideCreateDependencyResolveProcessMetricUseCase(): CreateDependencyResolveProcessMetricUseCase { + return CreateDependencyResolveProcessMetricUseCase() +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideCreateParallelExecutionRateMetricUseCase(): CreateParallelExecutionRateMetricUseCase { + return CreateParallelExecutionRateMetricUseCase() +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideCreateModulesExecutionProcessMetricUseCase(): CreateModulesExecutionProcessMetricUseCase { + return CreateModulesExecutionProcessMetricUseCase(modules!!) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideCreateModulesDependencyGraphMetricUseCase(): CreateModulesDependencyGraphMetricUseCase { + return CreateModulesDependencyGraphMetricUseCase(modulesDependencyGraph!!) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideCreateModulesTimelineMetricUseCase(): CreateModulesTimelineMetricUseCase { + return CreateModulesTimelineMetricUseCase(modules!!) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideCreateModulesBuildHeatmapMetricUseCase(): CreateModulesBuildHeatmapMetricUseCase { + return CreateModulesBuildHeatmapMetricUseCase(modules!!, modulesDependencyGraph!!) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideCreateDependencyDetailsMetricUseCase(): CreateDependencyDetailsMetricUseCase { + return CreateDependencyDetailsMetricUseCase(thirdPartyDependencies!!) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideCreateNonCacheableTasksMetricUseCase(): CreateNonCacheableTasksMetricUseCase { + return CreateNonCacheableTasksMetricUseCase(nonCachableTasks!!) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideCreateModulesSourceSizeMetricUseCase(): CreateModulesSourceSizeMetricUseCase { + return CreateModulesSourceSizeMetricUseCase(modules!!) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideCreateModulesCrashCountMetricUseCase(): CreateModulesCrashCountMetricUseCase { + return CreateModulesCrashCountMetricUseCase(modules!!) +} + +@ExcludeJacocoGenerated +fun BuildExecutionInjector.provideBuildExecutionLogic(): BuildExecutionLogic { + return BuildExecutionLogicImp( + databaseConfig = databaseConfig!!, + envCI = isCI!!, + trackingBranches = trackingBranches!!, + trackingTasks = trackingTasks!!, + requestedTasks = requestedTasks!!, + saveMetricUseCase = provideSaveMetricUseCase(), + saveTemporaryMetricUseCase = provideSaveTemporaryMetricUseCase(), + upsertModulesTimelineUseCase = provideUpsertModulesTimelineUseCase(), + provideCreateInitializationProcessMetricUseCase(), + provideCreateConfigurationProcessMetricUseCase(), + provideCreateExecutionProcessMetricUseCase(), + provideCreateOverallBuildProcessMetricUseCase(), + provideCreateModulesSourceCountMetricUseCase(), + provideCreateModulesMethodCountMetricUseCase(), + provideCreateCacheHitMetricUseCase(), + provideCreateSuccessBuildRateMetricUseCase(), + provideCreateDependencyResolveProcessMetricUseCase(), + provideCreateParallelExecutionRateMetricUseCase(), + provideCreateModulesExecutionProcessMetricUseCase(), + provideCreateModulesDependencyGraphMetricUseCase(), + provideCreateModulesTimelineMetricUseCase(), + provideCreateModulesBuildHeatmapMetricUseCase(), + provideCreateDependencyDetailsMetricUseCase(), + provideCreateNonCacheableTasksMetricUseCase(), + provideCreateModulesSourceSizeMetricUseCase(), + provideCreateModulesCrashCountMetricUseCase(), + ) +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/execution/BuildExecutionLogic.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/execution/BuildExecutionLogic.kt new file mode 100644 index 00000000..5c59caf3 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/execution/BuildExecutionLogic.kt @@ -0,0 +1,68 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.scanner.execution + +import io.github.janbarari.gradle.analytics.domain.model.TaskInfo + +/** + * Logics for [io.github.janbarari.gradle.analytics.scanner.execution.BuildExecutionService]. + */ +interface BuildExecutionLogic { + + /** + * Checks the current git branch exists in the project trackable branches. + */ + fun isBranchTrackable(): Boolean + + /** + * Checks the 'requested tasks' is exists on the project trackable tasks. + * + * Note: Every Gradle process started with a task(s). It is called 'requestedTasks'. + */ + fun isTaskTrackable(): Boolean + + /** + * Checks the 'requested tasks' is not the plugin custom tasks. + */ + fun isForbiddenTasksRequested(): Boolean + + /** + * Checks the database configuration is valid. + */ + fun isDatabaseConfigurationValid(): Boolean + + /** + * Since the build initialization, configuration, and execution process are separated + * and based on various situations they have their own lifecycle this method resets + * their static variables in heap memory. + */ + fun resetDependentServices() + + /** + * Once the build is finished, this method should be + * invoked by [io.github.janbarari.gradle.analytics.scanner.execution.BuildExecutionService] to start + * storing the build metrics. + */ + fun onExecutionFinished(executedTasks: Collection) + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/execution/BuildExecutionLogicImp.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/execution/BuildExecutionLogicImp.kt new file mode 100644 index 00000000..b9ab56c4 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/execution/BuildExecutionLogicImp.kt @@ -0,0 +1,246 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.scanner.execution + +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.DatabaseConfig +import io.github.janbarari.gradle.analytics.domain.model.BuildInfo +import io.github.janbarari.gradle.analytics.domain.model.TaskInfo +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.analytics.domain.usecase.SaveMetricUseCase +import io.github.janbarari.gradle.analytics.domain.usecase.SaveTemporaryMetricUseCase +import io.github.janbarari.gradle.analytics.domain.usecase.UpsertModulesTimelineUseCase +import io.github.janbarari.gradle.analytics.metric.cachehit.create.CreateCacheHitMetricStage +import io.github.janbarari.gradle.analytics.metric.cachehit.create.CreateCacheHitMetricUseCase +import io.github.janbarari.gradle.analytics.metric.configurationprocess.create.CreateConfigurationProcessMetricStage +import io.github.janbarari.gradle.analytics.metric.configurationprocess.create.CreateConfigurationProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.dependencydetails.create.CreateDependencyDetailsMetricStage +import io.github.janbarari.gradle.analytics.metric.dependencydetails.create.CreateDependencyDetailsMetricUseCase +import io.github.janbarari.gradle.analytics.metric.dependencyresolveprocess.create.CreateDependencyResolveProcessMetricStage +import io.github.janbarari.gradle.analytics.metric.dependencyresolveprocess.create.CreateDependencyResolveProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.executionprocess.create.CreateExecutionProcessMetricStage +import io.github.janbarari.gradle.analytics.metric.executionprocess.create.CreateExecutionProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.initializationprocess.create.CreateInitializationProcessMetricStage +import io.github.janbarari.gradle.analytics.metric.initializationprocess.create.CreateInitializationProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulesbuildheatmap.create.CreateModulesBuildHeatmapMetricStage +import io.github.janbarari.gradle.analytics.metric.modulesbuildheatmap.create.CreateModulesBuildHeatmapMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulescrashcount.create.CreateModulesCrashCountMetricStage +import io.github.janbarari.gradle.analytics.metric.modulescrashcount.create.CreateModulesCrashCountMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulesdependencygraph.create.CreateModulesDependencyGraphMetricStage +import io.github.janbarari.gradle.analytics.metric.modulesdependencygraph.create.CreateModulesDependencyGraphMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulesexecutionprocess.create.CreateModulesExecutionProcessMetricStage +import io.github.janbarari.gradle.analytics.metric.modulesexecutionprocess.create.CreateModulesExecutionProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulesmethodcount.create.CreateModulesMethodCountMetricStage +import io.github.janbarari.gradle.analytics.metric.modulesmethodcount.create.CreateModulesMethodCountMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulesourcecount.create.CreateModulesSourceCountMetricStage +import io.github.janbarari.gradle.analytics.metric.modulesourcecount.create.CreateModulesSourceCountMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulessourcesize.create.CreateModulesSourceSizeMetricStage +import io.github.janbarari.gradle.analytics.metric.modulessourcesize.create.CreateModulesSourceSizeMetricUseCase +import io.github.janbarari.gradle.analytics.metric.modulestimeline.create.CreateModulesTimelineMetricStage +import io.github.janbarari.gradle.analytics.metric.modulestimeline.create.CreateModulesTimelineMetricUseCase +import io.github.janbarari.gradle.analytics.metric.noncacheabletasks.create.CreateNonCacheableTasksMetricStage +import io.github.janbarari.gradle.analytics.metric.noncacheabletasks.create.CreateNonCacheableTasksMetricUseCase +import io.github.janbarari.gradle.analytics.metric.overallbuildprocess.create.CreateOverallBuildProcessMetricStage +import io.github.janbarari.gradle.analytics.metric.overallbuildprocess.create.CreateOverallBuildProcessMetricUseCase +import io.github.janbarari.gradle.analytics.metric.paralleexecutionrate.create.CreateParallelExecutionRateMetricStage +import io.github.janbarari.gradle.analytics.metric.paralleexecutionrate.create.CreateParallelExecutionRateMetricUseCase +import io.github.janbarari.gradle.analytics.metric.successbuildrate.create.CreateSuccessBuildRateMetricStage +import io.github.janbarari.gradle.analytics.metric.successbuildrate.create.CreateSuccessBuildRateMetricUseCase +import io.github.janbarari.gradle.analytics.reporttask.ReportAnalyticsTask +import io.github.janbarari.gradle.analytics.scanner.configuration.BuildConfigurationService +import io.github.janbarari.gradle.analytics.scanner.dependencyresolution.BuildDependencyResolutionService +import io.github.janbarari.gradle.analytics.scanner.initialization.BuildInitializationService +import io.github.janbarari.gradle.extension.isNotNull +import io.github.janbarari.gradle.extension.isNull +import io.github.janbarari.gradle.extension.separateElementsWithSpace +import io.github.janbarari.gradle.utils.ConsolePrinter +import io.github.janbarari.gradle.utils.DateTimeUtils +import io.github.janbarari.gradle.utils.GitUtils +import kotlinx.coroutines.runBlocking + +/** + * Implementation of [io.github.janbarari.gradle.analytics.scanner.execution.BuildExecutionLogic]. + */ +class BuildExecutionLogicImp( + private val databaseConfig: DatabaseConfig, + private val envCI: Boolean, + private val trackingBranches: List, + private val trackingTasks: List, + private val requestedTasks: List, + private val saveMetricUseCase: SaveMetricUseCase, + private val saveTemporaryMetricUseCase: SaveTemporaryMetricUseCase, + private val upsertModulesTimelineUseCase: UpsertModulesTimelineUseCase, + private val createInitializationProcessMetricUseCase: CreateInitializationProcessMetricUseCase, + private val createConfigurationProcessMetricUseCase: CreateConfigurationProcessMetricUseCase, + private val createExecutionProcessMetricUseCase: CreateExecutionProcessMetricUseCase, + private val createOverallBuildProcessMetricUseCase: CreateOverallBuildProcessMetricUseCase, + private val createModulesSourceCountMetricUseCase: CreateModulesSourceCountMetricUseCase, + private val createModulesMethodCountMetricUseCase: CreateModulesMethodCountMetricUseCase, + private val createCacheHitMetricUseCase: CreateCacheHitMetricUseCase, + private val createSuccessBuildRateMetricUseCase: CreateSuccessBuildRateMetricUseCase, + private val createDependencyResolveProcessMetricUseCase: CreateDependencyResolveProcessMetricUseCase, + private val createParallelExecutionRateMetricUseCase: CreateParallelExecutionRateMetricUseCase, + private val createModulesExecutionProcessMetricUseCase: CreateModulesExecutionProcessMetricUseCase, + private val createModulesDependencyGraphMetricUseCase: CreateModulesDependencyGraphMetricUseCase, + private val createModulesTimelineMetricUseCase: CreateModulesTimelineMetricUseCase, + private val createModulesBuildHeatmapMetricUseCase: CreateModulesBuildHeatmapMetricUseCase, + private val createDependencyDetailsMetricUseCase: CreateDependencyDetailsMetricUseCase, + private val createNonCacheableTasksMetricUseCase: CreateNonCacheableTasksMetricUseCase, + private val createModulesSourceSizeMetricUseCase: CreateModulesSourceSizeMetricUseCase, + private val createModulesCrashCountMetricUseCase: CreateModulesCrashCountMetricUseCase, +) : BuildExecutionLogic { + + override fun onExecutionFinished(executedTasks: Collection) = runBlocking { + if (isForbiddenTasksRequested()) return@runBlocking + + if (!isDatabaseConfigurationValid()) return@runBlocking + + if (!isTaskTrackable()) return@runBlocking + + if (!isBranchTrackable()) return@runBlocking + + val isSuccessful = executedTasks.all { it.isSuccessful } + val failure = executedTasks.find { !it.isSuccessful && it.failures.isNotNull() }?.failures + + val buildInfo = BuildInfo( + createdAt = System.currentTimeMillis(), + startedAt = BuildInitializationService.STARTED_AT, + initializedAt = BuildInitializationService.INITIALIZED_AT, + configuredAt = BuildConfigurationService.CONFIGURED_AT, + finishedAt = System.currentTimeMillis(), + dependenciesResolveInfo = BuildDependencyResolutionService.dependenciesResolveInfo.values, + executedTasks = executedTasks.toList(), + branch = GitUtils.currentBranch(), + gitHeadCommitHash = GitUtils.getHeadCommitHash(), + requestedTasks = requestedTasks, + isSuccessful = isSuccessful, + failure = failure + ) + + resetDependentServices() + + val buildMetric = + CreateMetricPipeline(CreateInitializationProcessMetricStage(buildInfo, createInitializationProcessMetricUseCase)) + .addStage(CreateConfigurationProcessMetricStage(buildInfo, createConfigurationProcessMetricUseCase)) + .addStage(CreateExecutionProcessMetricStage(buildInfo, createExecutionProcessMetricUseCase)) + .addStage(CreateOverallBuildProcessMetricStage(buildInfo, createOverallBuildProcessMetricUseCase)) + .addStage(CreateModulesSourceCountMetricStage(createModulesSourceCountMetricUseCase)) + .addStage(CreateModulesMethodCountMetricStage(createModulesMethodCountMetricUseCase)) + .addStage(CreateCacheHitMetricStage(buildInfo, createCacheHitMetricUseCase)) + .addStage(CreateSuccessBuildRateMetricStage(buildInfo, createSuccessBuildRateMetricUseCase)) + .addStage(CreateDependencyResolveProcessMetricStage(buildInfo, createDependencyResolveProcessMetricUseCase)) + .addStage(CreateParallelExecutionRateMetricStage(buildInfo, createParallelExecutionRateMetricUseCase)) + .addStage(CreateModulesExecutionProcessMetricStage(buildInfo, createModulesExecutionProcessMetricUseCase)) + .addStage(CreateModulesDependencyGraphMetricStage(createModulesDependencyGraphMetricUseCase)) + .addStage(CreateModulesTimelineMetricStage(buildInfo, createModulesTimelineMetricUseCase)) + .addStage(CreateModulesBuildHeatmapMetricStage(createModulesBuildHeatmapMetricUseCase)) + .addStage(CreateDependencyDetailsMetricStage(createDependencyDetailsMetricUseCase)) + .addStage(CreateNonCacheableTasksMetricStage(buildInfo, createNonCacheableTasksMetricUseCase)) + .addStage(CreateModulesSourceSizeMetricStage(createModulesSourceSizeMetricUseCase)) + .addStage(CreateModulesCrashCountMetricStage(buildInfo, createModulesCrashCountMetricUseCase)) + .execute( + BuildMetric( + buildInfo.branch, + buildInfo.requestedTasks, + buildInfo.createdAt, + buildInfo.gitHeadCommitHash + ) + ) + + saveTemporaryMetricUseCase.execute(buildMetric) + saveMetricUseCase.execute(buildMetric) + + if (buildMetric.modulesTimelineMetric.isNotNull()) + upsertModulesTimelineUseCase.execute(buildInfo.branch to buildMetric.modulesTimelineMetric!!) + + printBuildInfo(buildMetric) + } + + private fun printBuildInfo(buildMetric: BuildMetric) { + val requestedTasks = buildMetric.requestedTasks.separateElementsWithSpace() + val repoLink = "https://github.com/janbarari/gradle-analytics-plugin" + + var width = requestedTasks.length + 25 + if (width < 60) width = 60 + + ConsolePrinter(width).run { + printFirstLine() + printLine(left = "Gradle Analytics Plugin") + printBreakLine(char = '-') + printLine(left = "Requested Tasks:", right = requestedTasks) + printLine(left = "Branch:", right = buildMetric.branch) + printLine(left = "Head Commit Hash:", right = buildMetric.gitHeadCommitHash) + printBreakLine(char = '-') + printLine(left = "Initialization Process:", right = "${buildMetric.initializationProcessMetric?.median}ms") + printLine(left = "Configuration Process:", right = "${buildMetric.configurationProcessMetric?.median}ms") + printLine(left = "Dependency Resolve Process:", right = "${buildMetric.dependencyResolveProcessMetric?.median}ms") + printLine(left = "Execution Process:", right = "${buildMetric.executionProcessMetric?.median}ms") + printLine(left = "Overall Build Process:", right = "${buildMetric.overallBuildProcessMetric?.median}ms") + printLine(left = "Cache Hit:", right = "${buildMetric.cacheHitMetric?.rate}%") + printLine(left = "Parallel Execution Rate:", right = "${buildMetric.parallelExecutionRateMetric?.medianRate}%") + printBreakLine(char = '-') + printLine(left = "Datetime:", right = DateTimeUtils.formatToDateTime(buildMetric.createdAt)) + printBreakLine(char = '-') + printLine(left = "Made with โค for everyone") + printLine(left = repoLink) + printLine(right = "โ†– Tap the โ˜† button to support us") + printLastLine() + } + + } + + @ExcludeJacocoGenerated + override fun resetDependentServices() { + BuildInitializationService.reset() + BuildConfigurationService.reset() + BuildDependencyResolutionService.reset() + } + + override fun isDatabaseConfigurationValid(): Boolean { + //return false if local machine executed and the config is not set. + if (databaseConfig.local.isNull() && !envCI) { + return false + } + + //return false if CI machine executed and the config is not set. + if (databaseConfig.ci.isNull() && envCI) { + return false + } + + return true + } + + override fun isForbiddenTasksRequested(): Boolean { + return requestedTasks.contains(ReportAnalyticsTask.TASK_NAME) + } + + override fun isTaskTrackable(): Boolean { + val requestedTasks = requestedTasks.separateElementsWithSpace() + return trackingTasks.contains(requestedTasks) + } + + override fun isBranchTrackable(): Boolean { + return trackingBranches.contains(GitUtils.currentBranch()) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/execution/BuildExecutionService.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/execution/BuildExecutionService.kt new file mode 100644 index 00000000..65d68951 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/execution/BuildExecutionService.kt @@ -0,0 +1,196 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.scanner.execution + +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import io.github.janbarari.gradle.analytics.DatabaseConfig +import io.github.janbarari.gradle.analytics.GradleAnalyticsPluginConfig +import io.github.janbarari.gradle.analytics.domain.model.Dependency +import io.github.janbarari.gradle.analytics.domain.model.Module +import io.github.janbarari.gradle.analytics.domain.model.ModulesDependencyGraph +import io.github.janbarari.gradle.analytics.domain.model.TaskInfo +import io.github.janbarari.gradle.analytics.scanner.configuration.BuildConfigurationService +import io.github.janbarari.gradle.analytics.scanner.initialization.BuildInitializationService +import io.github.janbarari.gradle.utils.GitUtils +import org.gradle.api.provider.ListProperty +import org.gradle.api.provider.Property +import org.gradle.api.services.BuildService +import org.gradle.api.services.BuildServiceParameters +import org.gradle.tooling.Failure +import org.gradle.tooling.events.FailureResult +import org.gradle.tooling.events.FinishEvent +import org.gradle.tooling.events.OperationCompletionListener +import org.gradle.tooling.events.OperationDescriptor +import org.gradle.tooling.events.SkippedResult +import org.gradle.tooling.events.SuccessResult +import org.gradle.tooling.events.task.TaskFailureResult +import org.gradle.tooling.events.task.TaskFinishEvent +import org.gradle.tooling.events.task.TaskSuccessResult +import java.util.concurrent.ConcurrentLinkedQueue + +/** + * Tracks the task's execution information. + */ +@ExcludeJacocoGenerated +abstract class BuildExecutionService : BuildService, OperationCompletionListener, AutoCloseable { + + interface Params : BuildServiceParameters { + val databaseConfig: Property + val envCI: Property + val requestedTasks: ListProperty + val trackingTasks: ListProperty + val trackingBranches: ListProperty + val modules: ListProperty + val modulesDependencyGraph: Property + val dependencies: ListProperty + val nonCachableTasks: ListProperty + } + + private val executedTasks: ConcurrentLinkedQueue = ConcurrentLinkedQueue() + + init { + assignStartTimestampIfProcessSkipped() + assignInitializationTimestampIfProcessSkipped() + assignConfigurationTimestampIfProcessSkipped() + } + + /** + * If the build initialization is reused by configuration-cache, then the + * [io.github.janbarari.gradle.analytics.scanner.initialization.BuildInitializationService] won't + * register to the project and the start time won't assign. + */ + private fun assignStartTimestampIfProcessSkipped() { + if (BuildInitializationService.STARTED_AT == 0L) { + BuildInitializationService.STARTED_AT = System.currentTimeMillis() + } + } + + /** + * If the build initialization is reused by configuration-cache, then the + * [io.github.janbarari.gradle.analytics.scanner.initialization.BuildInitializationService] won't + * register to the project and the initialization time won't assign. + */ + private fun assignInitializationTimestampIfProcessSkipped() { + if (BuildInitializationService.INITIALIZED_AT == 0L) { + BuildInitializationService.INITIALIZED_AT = System.currentTimeMillis() + } + } + + /** + * If the build configuration is reused by configuration-cache, then the + * [io.github.janbarari.gradle.analytics.scanner.configuration.BuildConfigurationService] won't + * register to the project and the configuration time won't assign. + */ + private fun assignConfigurationTimestampIfProcessSkipped() { + if (BuildConfigurationService.CONFIGURED_AT == 0L) { + BuildConfigurationService.CONFIGURED_AT = System.currentTimeMillis() + } + } + + /** + * Called when each task execution is finished. + * @param event Task finish event + */ + @ExcludeJacocoGenerated + override fun onFinish(event: FinishEvent?) { + if (event is TaskFinishEvent) { + var dependencies: List? = null + var isSuccessful = false + var isSkipped = false + var failures: List? = null + var isIncremental = false + var isFromCache = false + var isUpToDate = false + var executionReasons: List? = null + + runCatching { + dependencies = event.descriptor.dependencies.toList() + } + + when (event.result) { + is FailureResult -> { + val result = event.result as TaskFailureResult + failures = result.failures + isIncremental = result.isIncremental + executionReasons = result.executionReasons + } + + is SuccessResult -> { + isSuccessful = true + val result = event.result as TaskSuccessResult + isIncremental = result.isIncremental + isFromCache = result.isFromCache + isUpToDate = result.isUpToDate + executionReasons = result.executionReasons + } + + is SkippedResult -> { + isSuccessful = true + isSkipped = true + } + } + + executedTasks.add( + TaskInfo( + startedAt = event.result.startTime, + finishedAt = event.result.endTime, + path = event.descriptor.taskPath, + displayName = event.descriptor.displayName, + name = event.descriptor.name, + isSuccessful = isSuccessful, + failures = failures, + dependencies = dependencies, + isIncremental = isIncremental, + isFromCache = isFromCache, + isUpToDate = isUpToDate, + isSkipped = isSkipped, + executionReasons = executionReasons + ) + ) + } + } + + /** + * Called once the build execution process finished. + */ + @ExcludeJacocoGenerated + override fun close() { + BuildExecutionInjector( + databaseConfig = parameters.databaseConfig.get(), + isCI = parameters.envCI.get(), + branch = GitUtils.currentBranch(), + requestedTasks = parameters.requestedTasks.get(), + trackingBranches = parameters.trackingBranches.get(), + trackingTasks = parameters.trackingTasks.get(), + modules = parameters.modules.get(), + modulesDependencyGraph = parameters.modulesDependencyGraph.get(), + thirdPartyDependencies = parameters.dependencies.get(), + nonCachableTasks = parameters.nonCachableTasks.get() + ).apply { + provideBuildExecutionLogic().onExecutionFinished(executedTasks) + }.also { + executedTasks.clear() + } + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/execution/CreateMetricPipeline.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/execution/CreateMetricPipeline.kt new file mode 100644 index 00000000..4b63b7e1 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/execution/CreateMetricPipeline.kt @@ -0,0 +1,29 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.scanner.execution + +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric +import io.github.janbarari.gradle.core.Pipeline +import io.github.janbarari.gradle.core.Stage + +class CreateMetricPipeline(firstStage: Stage): Pipeline(firstStage) diff --git a/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/initialization/BuildInitializationService.kt b/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/initialization/BuildInitializationService.kt new file mode 100644 index 00000000..3f9f7af6 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/analytics/scanner/initialization/BuildInitializationService.kt @@ -0,0 +1,102 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.analytics.scanner.initialization + +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import org.gradle.BuildResult +import org.gradle.api.Project +import org.gradle.api.ProjectEvaluationListener +import org.gradle.api.ProjectState +import org.gradle.api.initialization.Settings +import org.gradle.api.internal.GradleInternal +import org.gradle.api.invocation.Gradle +import org.gradle.internal.InternalBuildListener +import org.gradle.internal.scan.time.BuildScanBuildStartedTime + +/** + * Track and holds the build start and initialization finish timestamp to use by + * [io.github.janbarari.gradle.analytics.scanner.execution.BuildExecutionService]. + */ +class BuildInitializationService( + private val gradle: Gradle +) : InternalBuildListener, ProjectEvaluationListener { + + companion object { + var STARTED_AT: Long = 0L + var INITIALIZED_AT: Long = 0L + + fun reset() { + STARTED_AT = 0L + INITIALIZED_AT = 0L + } + } + + init { + reset() + } + + override fun beforeEvaluate(project: Project) { + assignInitializationTimestamp() + } + + override fun afterEvaluate(project: Project, state: ProjectState) { + assignInitializationTimestamp() + } + + private fun assignInitializationTimestamp() { + if (INITIALIZED_AT == 0L) { + INITIALIZED_AT = System.currentTimeMillis() + } + } + + @ExcludeJacocoGenerated + override fun settingsEvaluated(settings: Settings) { + // Added because gradle allows when InternalBuildListener.kt is implemented in the service class. + } + + @ExcludeJacocoGenerated + override fun projectsLoaded(gradle: Gradle) { + // Added because gradle allows when InternalBuildListener.kt is implemented in the service class. + } + + @ExcludeJacocoGenerated + override fun projectsEvaluated(gradle: Gradle) { + STARTED_AT = getStartTimestamp() + } + + @ExcludeJacocoGenerated + @Deprecated("Deprecated") + override fun buildFinished(result: BuildResult) { + // Added because gradle allows when InternalBuildListener.kt is implemented in the service class. + } + + /** + * Returns the build start timestamp from [org.gradle.internal.scan.time.BuildScanBuildStartedTime]. + */ + @ExcludeJacocoGenerated + private fun getStartTimestamp(): Long { + val buildStartedTimeService = (gradle as GradleInternal).services.get(BuildScanBuildStartedTime::class.java) + return buildStartedTimeService?.buildStartedTime ?: System.currentTimeMillis() + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/core/Pipeline.kt b/src/main/kotlin/io/github/janbarari/gradle/core/Pipeline.kt new file mode 100644 index 00000000..1628b2d1 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/core/Pipeline.kt @@ -0,0 +1,46 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.core + +/** + * Pipeline design pattern main class. + */ +open class Pipeline(firstStage: Stage) { + + val currentStage: Stage = firstStage + + fun addStage(newStage: Stage): Pipeline { + return Pipeline( + object : Stage { + override suspend fun process(input: I): K { + return newStage.process(currentStage.process(input)) + } + } + ) + } + + suspend fun execute(input: I): O { + return currentStage.process(input) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/core/Stage.kt b/src/main/kotlin/io/github/janbarari/gradle/core/Stage.kt new file mode 100644 index 00000000..25fbc9b6 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/core/Stage.kt @@ -0,0 +1,30 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.core + +/** + * Pipeline design pattern stage interface. + */ +interface Stage { + suspend fun process(input: I): O +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/core/Triple.kt b/src/main/kotlin/io/github/janbarari/gradle/core/Triple.kt new file mode 100644 index 00000000..d818deb6 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/core/Triple.kt @@ -0,0 +1,32 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.core + +/** + * Custom generic data class to hold 3 object together. + */ +open class Triple( + val first: A, + val second: B, + val third: C +) : java.io.Serializable diff --git a/src/main/kotlin/io/github/janbarari/gradle/core/UseCase.kt b/src/main/kotlin/io/github/janbarari/gradle/core/UseCase.kt new file mode 100644 index 00000000..90e4eec1 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/core/UseCase.kt @@ -0,0 +1,34 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.core + +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import kotlinx.coroutines.Dispatchers +import kotlin.coroutines.CoroutineContext + +@ExcludeJacocoGenerated +abstract class UseCase( + val dispatcher: CoroutineContext = Dispatchers.IO +) { + abstract suspend fun execute(input: INPUT): OUTPUT +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/core/UseCaseNoInput.kt b/src/main/kotlin/io/github/janbarari/gradle/core/UseCaseNoInput.kt new file mode 100644 index 00000000..d75f9da4 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/core/UseCaseNoInput.kt @@ -0,0 +1,34 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.core + +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import kotlinx.coroutines.Dispatchers +import kotlin.coroutines.CoroutineContext + +@ExcludeJacocoGenerated +abstract class UseCaseNoInput( + val dispatcher: CoroutineContext = Dispatchers.IO +) { + abstract suspend fun execute(): OUTPUT +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/core/UseCaseNoOutput.kt b/src/main/kotlin/io/github/janbarari/gradle/core/UseCaseNoOutput.kt new file mode 100644 index 00000000..bec32ad3 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/core/UseCaseNoOutput.kt @@ -0,0 +1,34 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.core + +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import kotlinx.coroutines.Dispatchers +import kotlin.coroutines.CoroutineContext + +@ExcludeJacocoGenerated +abstract class UseCaseNoOutput( + val dispatcher: CoroutineContext = Dispatchers.IO +) { + abstract suspend fun execute(input: INPUT) +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/extension/BuildMetricExtensions.kt b/src/main/kotlin/io/github/janbarari/gradle/extension/BuildMetricExtensions.kt new file mode 100644 index 00000000..1da2f1a2 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/extension/BuildMetricExtensions.kt @@ -0,0 +1,194 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.extension + +import io.github.janbarari.gradle.analytics.domain.model.TimespanPoint +import io.github.janbarari.gradle.analytics.domain.model.metric.BuildMetric + +/** + * Map the InitializationProcessMetric collection to TimespanPoint median collection. + */ +fun List.mapToInitializationMedianTimespanChartPoints(): List { + return map { + TimespanPoint( + value = it.initializationProcessMetric!!.median, + from = it.createdAt, + to = null + ) + } +} + +/** + * Map the InitializationProcessMetric collection to TimespanPoint mean collection. + */ +fun List.mapToInitializationMeanTimespanChartPoints(): List { + return map { + TimespanPoint( + value = it.initializationProcessMetric!!.mean, + from = it.createdAt, + to = null + ) + } +} + +/** + * Map the ConfigurationProcessMetric collection to TimespanPoint median collection. + */ +fun List.mapToConfigurationMedianTimespanChartPoints(): List { + return map { + TimespanPoint( + value = it.configurationProcessMetric!!.median, + from = it.createdAt, + to = null + ) + } +} + +/** + * Map the ConfigurationProcessMetric collection to TimespanPoint mean collection. + */ +fun List.mapToConfigurationMeanTimespanChartPoints(): List { + return map { + TimespanPoint( + value = it.configurationProcessMetric!!.mean, + from = it.createdAt, + to = null + ) + } +} + +/** + * Map the ExecutionProcessMetric collection to TimespanPoint median collection. + */ +fun List.mapToExecutionMedianTimespanChartPoints(): List { + return map { + TimespanPoint( + value = it.executionProcessMetric!!.median / 1000L, + from = it.createdAt, + to = null + ) + } +} +/** + * Map the ExecutionProcessMetric collection to TimespanPoint mean collection. + */ +fun List.mapToExecutionMeanTimespanChartPoints(): List { + return map { + TimespanPoint( + value = it.executionProcessMetric!!.mean / 1000L, + from = it.createdAt, + to = null + ) + } +} + +/** + * Map OverallBuildProcessMetric collection to TimespanPoint median collection. + */ +fun List.mapToOverallBuildProcessMedianTimespanChartPoints(): List { + return map { + TimespanPoint( + value = it.overallBuildProcessMetric!!.median / 1000L, + from = it.createdAt, + to = null + ) + } +} + +/** + * Map the OverallBuildProcessMetric collection to TimespanPoint mean collection. + */ +fun List.mapToOverallBuildProcessMeanTimespanChartPoints(): List { + return map { + TimespanPoint( + value = it.overallBuildProcessMetric!!.mean / 1000L, + from = it.createdAt, + to = null + ) + } +} + +/** + * Map the SuccessBuildRateProcess Metric collection to TimespanPoint median collection. + */ +fun List.mapToSuccessBuildRateMedianTimespanChartPoints(): List { + return map { + TimespanPoint( + value = it.successBuildRateMetric!!.medianRate.toLong(), + from = it.createdAt, + to = null + ) + } +} + +/** + * Map the SuccessBuildRateProcess Metric collection to TimespanPoint mean collection. + */ +fun List.mapToSuccessBuildRateMeanTimespanChartPoints(): List { + return map { + TimespanPoint( + value = it.successBuildRateMetric!!.meanRate.toLong(), + from = it.createdAt, + to = null + ) + } +} + +/** + * Map the DependencyResolveProcessMetric Metric collection to TimespanPoint median collection. + */ +fun List.mapToDependencyResolveMedianTimespanChartPoints(): List { + return map { + TimespanPoint( + value = it.dependencyResolveProcessMetric!!.median, + from = it.createdAt, + to = null + ) + } +} + +/** + * Map the DependencyResolveProcessMetric Metric collection to TimespanPoint mean collection. + */ +fun List.mapToDependencyResolveMeanTimespanChartPoints(): List { + return map { + TimespanPoint( + value = it.dependencyResolveProcessMetric!!.mean, + from = it.createdAt, + to = null + ) + } +} + +/** + * Map the ParallelExecutionRateMetric Metric collection to TimespanPoint median collection. + */ +fun List.mapToParallelExecutionRateMedianTimespanPoints(): List { + return map { + TimespanPoint( + value = it.parallelExecutionRateMetric!!.medianRate, + from = it.createdAt, + to = null + ) + } +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/extension/ChartPointExtensions.kt b/src/main/kotlin/io/github/janbarari/gradle/extension/ChartPointExtensions.kt new file mode 100644 index 00000000..86f8cfb7 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/extension/ChartPointExtensions.kt @@ -0,0 +1,39 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.extension + +import io.github.janbarari.gradle.analytics.domain.model.ChartPoint + +/** + * Get maximum value of ChartPoint collection. + */ +fun List.maxValue(): Long { + return this.maxOf { it.value } +} + +/** + * Get minimum value of ChartPoint collection. + */ +fun List.minValue(): Long { + return this.minOf { it.value } +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/extension/FloatExtensions.kt b/src/main/kotlin/io/github/janbarari/gradle/extension/FloatExtensions.kt new file mode 100644 index 00000000..69c52937 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/extension/FloatExtensions.kt @@ -0,0 +1,50 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.extension + +import java.math.BigDecimal +import java.math.RoundingMode + +/** + * Round the given float number. + * + * @param decimalsCount The fraction numbers count, Default is Two. + */ +fun Float.round(decimalsCount: Int = 2): Float { + val bd = BigDecimal(this.toDouble()) + return bd.setScale(decimalsCount, RoundingMode.FLOOR).toFloat() +} + +/** + * Check is the given Long value is zero. + */ +fun Float.isZero(): Boolean { + return this == 0F +} + +/** + * Check is the given Long value is > 0. + */ +fun Float.isBiggerThanZero(): Boolean { + return this > 0F +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/extension/GeneralExtensions.kt b/src/main/kotlin/io/github/janbarari/gradle/extension/GeneralExtensions.kt new file mode 100644 index 00000000..dacdaae5 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/extension/GeneralExtensions.kt @@ -0,0 +1,43 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.extension + +/** + * Invoke function body when the value is true. + * + * This function helps to reduce the code complexity and increase the development speed by removing the + * boilerplate if condition for booleans. + */ +fun Boolean.whenTrue(block: Boolean.() -> Unit) { + if (this) block(true) +} + +/** + * Invoke function body when the value is false. + * + * This function helps to reduce the code complexity and increase the development speed by removing the + * boilerplate if condition for booleans. + */ +fun Boolean.whenFalse(block: Boolean.() -> Unit) { + if (!this) block(false) +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/extension/GradleExtensions.kt b/src/main/kotlin/io/github/janbarari/gradle/extension/GradleExtensions.kt new file mode 100644 index 00000000..9fc28b47 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/extension/GradleExtensions.kt @@ -0,0 +1,99 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.extension + +import io.github.janbarari.gradle.ExcludeJacocoGenerated +import org.gradle.api.DefaultTask +import org.gradle.api.Project +import org.gradle.api.Task +import org.gradle.api.artifacts.ProjectDependency +import org.gradle.api.invocation.Gradle +import org.gradle.api.provider.Provider + +/** + * Get the Gradle requested tasks list. `requestedTasks` are the tasks that CLI + * sent them to Gradle to start the build process. + */ +@ExcludeJacocoGenerated +fun Gradle.getRequestedTasks(): List { + return startParameter.taskNames +} + +/** + * Get the 'CI' value provider from the system environments. + */ +@ExcludeJacocoGenerated +fun Project.envCI(): Provider { + return providers.environmentVariable("CI").forUseAtConfigurationTime() +} + +/** + * Check is project dependency has dependency to ProjectDependency type. + */ +@ExcludeJacocoGenerated +fun Project.isDependingOnOtherProject(): Boolean { + return configurations.any { configuration -> configuration.dependencies.any { it is ProjectDependency } } +} + +/** + * Register the given task. + */ +@ExcludeJacocoGenerated +inline fun Project.registerTask(name: String, crossinline block: T.() -> Unit) { + project.tasks.register(name, T::class.java) { + it.also(block) + } +} + +/** + * Check if the given Task is cacheable. + */ +fun Task.isCacheable(): Boolean { + return this.outputs.hasOutput && this.inputs.hasInputs +} + +/** + * Get task dependencies. + */ +fun Task.getSafeTaskDependencies(): Set { + return try { + taskDependencies.getDependencies(this) + } catch (e: Throwable) { + emptySet() + } +} + +/** + * Filter non-cacheable tasks from the given task collection. + */ +fun Collection.getNonCacheableTasks(): Set { + val nonCacheableTasks = mutableSetOf() + forEach { task -> + task.getSafeTaskDependencies() + .filter { !it.isCacheable() } + .whenNotEmpty { + nonCacheableTasks.addAll(this.map { it.path }) + } + } + return nonCacheableTasks +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/extension/IntExtensions.kt b/src/main/kotlin/io/github/janbarari/gradle/extension/IntExtensions.kt new file mode 100644 index 00000000..5bc7b9c3 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/extension/IntExtensions.kt @@ -0,0 +1,78 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.extension + +/** + * Calculate the current value difference as percentage from the target value. + * + * Example: + * val a = 10 + * val b = 100 + * a.diffPercentageOf(b) equals 100%+ (b is 100%+ of a) + */ +fun Int.diffPercentageOf(target: Int): Float { + if(this == 0) return 0F + val result = ((target.toFloat() - this.toFloat()) / this.toFloat()) * 100F + return result.round() +} + +/** + * Calculate the current value difference as percentage from the target value. + * + * Example: + * val a = 10 + * val b = 100 + * a.diffPercentageOf(b) equals 100%+ (b is 100%+ of a) + */ +fun Long.diffPercentageOf(target: Long): Float { + if(this == 0L) return 0F + val result = ((target.toFloat() - this.toFloat()) / this.toFloat()) * 100F + return result.round() +} + +/** + * Calculate the current value coverage from the target value. + * + * Example: + * val a = 10 + * val b = 100 + * a.toPercentageOf(b) equals 10% (a is 10% of b). + */ +fun Int.toPercentageOf(target: Int): Float { + if (target == 0) return 0f + return ((this.toFloat() * 100F) / target).round() +} + +/** + * Calculate the current value coverage from the target value. + * + * Example: + * val a = 10 + * val b = 100 + * a.toPercentageOf(b) equals 10% (a is 10% of b). + */ +fun Long.toPercentageOf(target: Long): Float { + if (target == 0L) return 0F + return ((this.toFloat() * 100F) / target).round() +} + diff --git a/src/main/kotlin/io/github/janbarari/gradle/extension/ListExtensions.kt b/src/main/kotlin/io/github/janbarari/gradle/extension/ListExtensions.kt new file mode 100644 index 00000000..064bf428 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/extension/ListExtensions.kt @@ -0,0 +1,142 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.extension + +/** + * Iterates across the list items, but this function allows each iteration to + * add and remove items from the list. + */ +fun Collection.whenEach(block: T.() -> Unit) { + val iterator = this.iterator() + while (iterator.hasNext()) { + block(iterator.next()) + } +} + +/** + * Map the Long list to Int list. + */ +fun List.toIntList(): List { + return this.map { it.toInt() } +} + +/** + * Check is the given list has more items than dedicated count. + */ +fun List.isBiggerThan(count: Int): Boolean { + return this.size > count +} + +/** + * Invoke the function body if the given list has no items. + */ +inline fun List.whenEmpty(block: Collection.() -> Unit): List { + if (isEmpty()) block(this) + return this +} + +/** + * Invoke the function body if the given list is not empty. + */ +inline fun List.whenNotEmpty(block: Collection.() -> Unit): List { + if (isNotEmpty()) block(this) + return this +} + +/** + * Invoke the function body if the given set is not empty. + */ +inline fun Set.whenNotEmpty(block: Collection.() -> Unit): Set { + if (isNotEmpty()) block(this) + return this +} + +/** + * Get the first index value. + */ +val List.firstIndex: Int + get() = 0 + +/** + * Check is the given list has only a single item. + */ +fun List.hasSingleItem(): Boolean { + return this.size == 1 +} + +/** + * Check is the given list has multiple items. + */ +fun List.hasMultipleItems(): Boolean { + return this.size > 1 +} + +/** + * Convert list of string to comma separated string. + */ +fun List.toArrayString(): String { + val labels = StringBuilder() + labels.append("[") + whenEach { + labels.append("\"$this\"") + .append(",") + } + if (labels.length > 1) { + // because the last item should not have ',' separator. + labels.removeLastChar() + } + labels.append("]") + return labels.toString() +} + +/** + * I was refactoring my Gradle plugin source code and I saw that I use + * "map{}" and "list duplication" to create the same list with some modifications, + * I find out this is a bug because it: + * + * 1- leaks the performance by "object recreation" and "memory duplication". + * 2- Decreases the readability by putting the modification operation outside of collection operators. + * 3- Decreases the extensibility because it is not a collection operator, so can't use a chain with other collection operators. + * + * Then I decided to create a modification operator for a list to manipulate items in place. + * Less code + * More extensibility + * Better performance + */ +public inline fun Iterable.modify(modification: T.() -> Unit): Iterable { + for (item in this) + item.apply(modification) + return this +} + +public inline fun Collection.modify(modification: T.() -> Unit): Collection { + for (item in this) + item.apply(modification) + return this +} + +public inline fun List.modify(modification: T.() -> Unit): List { + for (item in this) + item.apply(modification) + return this +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/extension/LongExtensions.kt b/src/main/kotlin/io/github/janbarari/gradle/extension/LongExtensions.kt new file mode 100644 index 00000000..b88356da --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/extension/LongExtensions.kt @@ -0,0 +1,51 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.extension + +/** + * Check is the given Long value is zero. + */ +fun Long.isZero(): Boolean { + return this == 0L +} + +/** + * Check is the given long value is >= dedicated target value. + */ +fun Long.isBiggerEquals(target: Long): Boolean { + return this >= target +} + +/** + * Check is the given long value is > dedicated target value. + */ +fun Long.isBigger(target: Long): Boolean { + return this > target +} + +/** + * Convert given milliseconds to seconds. + */ +fun Long.millisToSeconds(): Long { + return this / 1000L +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/extension/NullExtensions.kt b/src/main/kotlin/io/github/janbarari/gradle/extension/NullExtensions.kt new file mode 100644 index 00000000..230f1a79 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/extension/NullExtensions.kt @@ -0,0 +1,51 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.extension + +/** + * Check if the object is null. + */ +fun Any?.isNull(): Boolean { + return this == null +} + +/** + * Check if the object is not null. + */ +fun Any?.isNotNull(): Boolean { + return this != null +} + +/** + * Invoke the lambda function if the object is NOT null. + */ +fun T?.whenNotNull(block: T.() -> Unit) { + if (this != null) block(this) +} + +/** + * Invoke the lambda function if the object is null. + */ +fun T?.whenNull(block: () -> Unit) { + if(this == null) block() +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/extension/PathExtensions.kt b/src/main/kotlin/io/github/janbarari/gradle/extension/PathExtensions.kt new file mode 100644 index 00000000..706556bc --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/extension/PathExtensions.kt @@ -0,0 +1,57 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.extension + +import java.nio.file.Files +import java.nio.file.Path +import kotlin.io.path.extension +import kotlin.io.path.pathString + +/** + * Check is given path is Kotlin/Java source file. + */ +fun Path.isSourcePath(): Boolean { + return (pathString.contains("src/main/java") || pathString.contains("src/main/kotlin")) + && (extension == "kt" || extension == "java") + && Files.isRegularFile(this) +} + +/** + * Check is the given path is Kotlin file. + */ +fun Path.isKotlinFile(): Boolean = extension == "kt" + +/** + * Check is the given path is Java file. + */ +fun Path.isJavaFile(): Boolean = extension == "java" + +/** + * Get given path file content as string. + */ +fun Path.readText(): String { + return toFile() + .inputStream() + .bufferedReader() + .use { it.readText() } +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/extension/ResourceExtensions.kt b/src/main/kotlin/io/github/janbarari/gradle/extension/ResourceExtensions.kt new file mode 100644 index 00000000..4bde6114 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/extension/ResourceExtensions.kt @@ -0,0 +1,52 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.extension + +import java.io.InputStream +import java.net.URL + +/** + * Due to https://bugs.openjdk.java.net/browse/JDK-6947916 and https://bugs.openjdk.java.net/browse/JDK-8155607, + * it is necessary to disallow caches to maintain stability on JDK 8 and 11 (and possibly more). + * Otherwise, simultaneous invocations of Detekt in the same VM can fail spuriously. A similar bug is referenced + * in https://github.com/detekt/detekt/issues/3396. The performance regression is likely unnoticeable. + * Due to https://github.com/detekt/detekt/issues/4332 it is included for all JDKs. + */ +fun URL.openSafeStream(): InputStream { + return openConnection().apply { useCaches = false }.getInputStream() +} + +fun Class.getSafeResourceAsStream(name: String): InputStream? { + return getResource(name)?.openSafeStream() +} + +/** + * Get the given file content as string. + */ +fun Any.getTextResourceContent(fileName: String): String { + return javaClass.getResource("/$fileName")!! + .openSafeStream() + .bufferedReader() + .use { it.readText() } +} + diff --git a/src/main/kotlin/io/github/janbarari/gradle/extension/StringExtensions.kt b/src/main/kotlin/io/github/janbarari/gradle/extension/StringExtensions.kt new file mode 100644 index 00000000..bfae4135 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/extension/StringExtensions.kt @@ -0,0 +1,59 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.extension + +/** + * Check the given string has any space. + */ +fun String.hasSpace(): Boolean { + return this.contains(" ") +} + +/** + * Remove the latest character of given string. + */ +fun String.removeLast(): String { + return substring(0, length - 1) +} + +/** + * Convert the given path string to real path. + */ +fun String.toRealPath(): String { + if (endsWith("/")) { + return removeLast().toRealPath() + } + return this +} + +fun List.separateElementsWithSpace(): String { + return this.joinToString(separator = " ") +} + +/** + * Remove the latest character of given StringBuilder. + */ +fun StringBuilder.removeLastChar() { + if (isNotEmpty()) + deleteCharAt(length - 1) +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/extension/TimespanChartPointExtensions.kt b/src/main/kotlin/io/github/janbarari/gradle/extension/TimespanChartPointExtensions.kt new file mode 100644 index 00000000..1ba52934 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/extension/TimespanChartPointExtensions.kt @@ -0,0 +1,96 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.extension + +import io.github.janbarari.gradle.analytics.domain.model.ChartPoint +import io.github.janbarari.gradle.analytics.domain.model.TimespanPoint +import io.github.janbarari.gradle.utils.DateTimeUtils +import io.github.janbarari.gradle.utils.MathUtils + +/** + * Minimize TimespanPoint collection. + */ +fun List.minimize(targetSize: Int): List { + return if (size > targetSize) + calculatePointsMean(this).minimize(targetSize) + else this +} + +private fun calculatePointsMean(values: List): List { + if (values.isEmpty()) return values + + val mean = arrayListOf() + val size = values.size + var nextIndex = 0 + + for (i in values.indices) { + if (i < nextIndex) continue + + if (i + 1 >= size) { + mean.add(values[i]) + } else { + var finishedAt = values[i + 1].to + if (finishedAt.isNull()) finishedAt = values[i + 1].from + + mean.add( + TimespanPoint( + value = MathUtils.longMean(values[i].value, values[i + 1].value), + from = values[i].from, + to = finishedAt + ) + ) + + nextIndex = i + 2 + } + } + + return mean +} + +/** + * Map TimespanPoint collection to ChartPoint collection. + */ +fun Collection.mapToChartPoints(): List { + return map { + val period = if (it.to.isNull()) + DateTimeUtils.format(it.from, "dd/MM") + else + DateTimeUtils.format(it.from, "dd/MM") + "-" + + DateTimeUtils.format(it.to!!, "dd/MM") + ChartPoint(it.value, period) + } +} + +/** + * Get the maximum value of TimespanPoint collection. + */ +fun List.maxValue(): Long { + return this.maxOf { it.value } +} + +/** + * Get the minimum value of TimespanPoint collection. + */ +fun List.minValue(): Long { + return this.minOf { it.value } +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/utils/ConsolePrinter.kt b/src/main/kotlin/io/github/janbarari/gradle/utils/ConsolePrinter.kt new file mode 100644 index 00000000..5b3e556a --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/utils/ConsolePrinter.kt @@ -0,0 +1,85 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.utils + +/** + * Prints the messages in the terminal console in a visual format. + */ +class ConsolePrinter(private var blockCharWidth: Int) { + + init { + blockCharWidth += 1 + } + + fun printFirstLine(firstSpace: Boolean = true) { + val output = StringBuilder() + if (firstSpace) + output.append("\n") + output.append(" ") + output.append("โ”Œ") + (0..blockCharWidth).forEach { + output.append("โ”€") + } + output.append("โ”") + println(output.toString()) + } + + fun printLastLine() { + val output = StringBuilder() + output.append(" ") + output.append("โ””") + (0..blockCharWidth).forEach { _ -> + output.append("โ”€") + } + output.append("โ”˜") + println(output.toString()) + } + + fun printLine(left: String = "", right: String = "") { + val output = StringBuilder() + output.append(" ") + output.append("โ”‚") + output.append(" ") + output.append(left) + val remainingEmptyCharactersCount = blockCharWidth - left.length - right.length - 1 + (0 until remainingEmptyCharactersCount).forEach { _ -> + output.append(" ") + } + output.append(right) + output.append(" ") + output.append("โ”‚") + println(output.toString()) + } + + fun printBreakLine(char: Char = 'โ”€') { + val output = StringBuilder() + output.append(" ") + output.append("โ”‚") + (0..blockCharWidth).forEach { _ -> + output.append(char) + } + output.append("โ”‚") + println(output.toString()) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/utils/DateTimeUtils.kt b/src/main/kotlin/io/github/janbarari/gradle/utils/DateTimeUtils.kt new file mode 100644 index 00000000..edd7ab28 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/utils/DateTimeUtils.kt @@ -0,0 +1,110 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.utils + +import java.time.Instant +import java.time.LocalDate +import java.time.LocalDateTime +import java.time.ZoneId +import java.time.ZonedDateTime +import java.time.format.DateTimeFormatter +import kotlin.math.floor + +/** + * A collection of datetime functions. + */ +object DateTimeUtils { + + const val ONE_DAY_IN_MILLIS = 86_400_000 + val DEFAULT_ZONE: ZoneId = ZoneId.of("UTC") + + /** + * Get the current day start time in milliseconds. + * + * Note: Timezone is UTC + */ + fun getDayStartMs(): Long { + return LocalDate.now().atStartOfDay(DEFAULT_ZONE).toEpochSecond() * 1000 + } + + /** + * Get the current day end time in milliseconds. + * + * Note: Timezone is UTC + */ + fun getDayEndMs(): Long { + return getDayStartMs() + ONE_DAY_IN_MILLIS + } + + /** + * Convert time in milliseconds to defined datetime pattern. + */ + fun format(timeInMs: Long, pattern: String): String { + return ZonedDateTime.ofInstant(Instant.ofEpochMilli(timeInMs), DEFAULT_ZONE) + .format(DateTimeFormatter.ofPattern(pattern)) + } + + /** + * Convert time in milliseconds to defined datetime pattern. + */ + fun convertDateToEpochMilli(date: String): Long { + return ZonedDateTime.parse("$date 00:00:00 AM UTC", DateTimeFormatter.ofPattern("yyyy/MM/dd HH:mm:ss a z")) + .toInstant() + .toEpochMilli() + } + + /** + * Convert time in milliseconds to `yyyy/MM/dd`. + */ + fun formatToDate(timeInMs: Long): String { + return format(timeInMs ,"yyyy/MM/dd") + } + + /** + * Convert time in milliseconds to `yyyy/MM/dd HH:mm a UTC`. + */ + fun formatToDateTime(timeInMs: Long): String { + return format(timeInMs, "yyyy/MM/dd HH:mm a 'UTC'") + } + + /** + * Convert time in seconds to the human-readable elapsed time. + */ + fun convertSecondsToHumanReadableTime(seconds: Long): String { + val numYears = floor(seconds / 31536000F).toInt() + val numDays = floor((seconds % 31536000) / 86400F).toInt() + val numHours = floor(((seconds % 31536000F) % 86400F) / 3600F).toInt() + val numMinutes = floor((((seconds % 31536000F) % 86400F) % 3600F) / 60F).toInt() + val numSeconds = (((seconds % 31536000) % 86400) % 3600) % 60 + if (numYears > 0) + return "${numYears}y ${numDays}d" + if (numDays > 0) + return "${numDays}d ${numHours}h" + if (numHours > 0) + return "${numHours}h ${numMinutes}m" + if (numMinutes > 0) + return "${numMinutes}m ${numSeconds}s" + return "${numSeconds}s" + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/utils/FileUtils.kt b/src/main/kotlin/io/github/janbarari/gradle/utils/FileUtils.kt new file mode 100644 index 00000000..5a3f8c06 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/utils/FileUtils.kt @@ -0,0 +1,49 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.utils + +import io.github.janbarari.gradle.extension.isSourcePath +import java.nio.file.Files +import java.nio.file.Path +import java.util.stream.Collectors +import kotlin.io.path.Path + +/** + * A collection of I/O functions. + */ +object FileUtils { + + /** + * Get all source files in the module path. + */ + fun getModuleSources(directory: String): List { + var sourcePaths: List + Files.walk(Path(directory)).use { stream -> + sourcePaths = stream.map { obj: Path -> obj.normalize() } + .filter { it.isSourcePath() } + .collect(Collectors.toList()) + } + return sourcePaths + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/utils/GitException.kt b/src/main/kotlin/io/github/janbarari/gradle/utils/GitException.kt new file mode 100644 index 00000000..d5e6ae4b --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/utils/GitException.kt @@ -0,0 +1,27 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.utils + +class GitException(msg: String): Throwable() { + override val message: String = msg +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/utils/GitUtils.kt b/src/main/kotlin/io/github/janbarari/gradle/utils/GitUtils.kt new file mode 100644 index 00000000..fe0836f4 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/utils/GitUtils.kt @@ -0,0 +1,66 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.utils + +/** + * A collection of git functions. + */ +object GitUtils { + + /** + * Get the git current branch name. + * + * @throws io.github.janbarari.gradle.utils.GitException if the command execution failed. + */ + @kotlin.jvm.Throws(GitException::class) + fun currentBranch(): String { + try { + return TerminalUtils.execCommand("git rev-parse --abbrev-ref HEAD") + } catch (e: IllegalStateException) { + throw GitException("Git command execution failed with message of ${e.message}") + } catch(e: RuntimeException) { + throw GitException("Git initialization not found with message of ${e.message}") + } + } + + /** + * Get the git HEAD commit hash. + * + * @throws io.github.janbarari.gradle.utils.GitException if the command execution failed. + */ + @kotlin.jvm.Throws(GitException::class) + fun getHeadCommitHash(): String { + try { + return TerminalUtils + .execCommand("git log --format=\"%H\" -n 1") + .replace("\"","") + } catch (e: IllegalStateException) { + throw GitException("Git command execution failed with message of ${e.message}") + } catch (e: RuntimeException) { + throw GitException("Git initialization not found with message of ${e.message}") + } + } + +} + + diff --git a/src/main/kotlin/io/github/janbarari/gradle/utils/HtmlUtils.kt b/src/main/kotlin/io/github/janbarari/gradle/utils/HtmlUtils.kt new file mode 100644 index 00000000..71858020 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/utils/HtmlUtils.kt @@ -0,0 +1,47 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.utils + +import io.github.janbarari.gradle.extension.getTextResourceContent + +/** + * A collection of HTML functions. + */ +object HtmlUtils { + + /** + * Return the empty message as HTML render. + */ + fun renderMessage(message: String): String { + return "

$message

" + + "
" + } + + /** + * Return the text resource file content as String. + */ + fun getTemplate(fileName: String): String { + return getTextResourceContent("$fileName.html") + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/utils/MathUtils.kt b/src/main/kotlin/io/github/janbarari/gradle/utils/MathUtils.kt new file mode 100644 index 00000000..6d1979e8 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/utils/MathUtils.kt @@ -0,0 +1,88 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.utils + +/** + * A collection of mathematics functions. + */ +object MathUtils { + + /** + * Calculate the Mean average of a long dataset. + */ + fun longMean(vararg dataset: Long): Long { + return longMean(dataset.toList()) + } + + /** + * Calculate the Mean average of a long dataset. + */ + fun longMean(dataset: List): Long { + if (dataset.isEmpty()) return 0 + return dataset.sum() / dataset.size + } + + /** + * Calculate the Median average of a long dataset. + */ + fun longMedian(dataset: List): Long { + if (dataset.isEmpty()) return 0 + return dataset.sorted().let { + if (it.size % 2 == 0) (it[it.size / 2] + it[(it.size - 1) / 2]) / 2 + else it[it.size / 2] + } + } + + /** + * Calculates the Median average of a float dataset. + */ + fun floatMedian(dataset: List): Float { + if (dataset.isEmpty()) return 0F + return dataset.sorted().let { + if (it.size % 2 == 0) ((it[it.size / 2] + it[(it.size - 1) / 2]) / 2) + else it[it.size / 2] + } + } + + /** + * Calculate the Median average of a long dataset. + */ + fun longMedian(vararg values: Long): Long { + return longMedian(values.toList()) + } + + /** + * Add to the value by defined percentage. + */ + fun sumWithPercentage(value: Long, percentage: Int): Long { + return value + ((value * percentage) / 100L) + } + + /** + * Deduct the value by defined percentage. + */ + fun deductWithPercentage(value: Long, percentage: Int): Long { + return value - ((value * percentage) / 100L) + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/utils/ProjectUtils.kt b/src/main/kotlin/io/github/janbarari/gradle/utils/ProjectUtils.kt new file mode 100644 index 00000000..972a461a --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/utils/ProjectUtils.kt @@ -0,0 +1,83 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The aboVe copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.utils + +import org.gradle.util.GradleVersion + +/** + * A collection of gradle project functions. + */ +object ProjectUtils { + + /** + * List of official Gradle versions. + */ + enum class GradleVersions(val versionNumber: String) { + V7_5_1("7.5.1"), + V7_5("7.5"), + V7_4_2("7.4.2"), + V7_4_1("7.4.1"), + V7_4("7.4"), + V7_3_3("7.3.3"), + V7_3_2("7.3.2"), + V7_3_1("7.3.1"), + V7_3("7.3"), + V7_2("7.2"), + V7_1_1("7.1.1"), + V7_1("7.1"), + V7_0_2("7.0.2"), + V7_0_1("7.0.1"), + V7_0("7.0"), + V6_9_2("6.9.2"), + V6_9_1("6.9.1"), + V6_9("6.9"), + V6_8_3("6.8.3"), + V6_8_2("6.8.2"), + V6_8_1("6.8.1"), + V6_8("6.8"), + V6_7_1("6.7.1"), + V6_7("6.7"), + V6_6_1("6.6.1"), + V6_6("6.6"), + V6_5_1("6.5.1"), + V6_5("6.5"), + V6_4_1("6.4.1"), + V6_4("6.4"), + V6_3("6.3"), + V6_2_2("6.2.2"), + V6_2_1("6.2.1"), + V6_2("6.2"), + V6_1_1("6.1.1"), + V6_1("6.1") + } + + /** + * Check the project Gradle version is compatible(above the entered gradle version). + */ + fun isCompatibleWith(version: GradleVersions): Boolean { + val projectGradleVersion = GradleVersion.current() + val minimumRequiredGradleVersion = GradleVersion.version(version.versionNumber) + return projectGradleVersion > minimumRequiredGradleVersion + } + +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/utils/TerminalCommandException.kt b/src/main/kotlin/io/github/janbarari/gradle/utils/TerminalCommandException.kt new file mode 100644 index 00000000..14468551 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/utils/TerminalCommandException.kt @@ -0,0 +1,27 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.utils + +class TerminalCommandException(cmd: String, e: Throwable): java.lang.RuntimeException() { + override val message: String = "Error executing $cmd with message $e" +} diff --git a/src/main/kotlin/io/github/janbarari/gradle/utils/TerminalUtils.kt b/src/main/kotlin/io/github/janbarari/gradle/utils/TerminalUtils.kt new file mode 100644 index 00000000..6782b9a9 --- /dev/null +++ b/src/main/kotlin/io/github/janbarari/gradle/utils/TerminalUtils.kt @@ -0,0 +1,49 @@ +/** + * MIT License + * Copyright (c) 2022 Mehdi Janbarari (@janbarari) + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package io.github.janbarari.gradle.utils + +import java.io.BufferedReader +import java.io.InputStreamReader + +/** + * A collection of terminal functions. + */ +object TerminalUtils { + + /** + * Execute the command in terminal. + * + * @throws io.github.janbarari.gradle.utils.TerminalCommandException if the command execution failed. + */ + @kotlin.jvm.Throws(TerminalCommandException::class) + fun execCommand(cmd: String): String { + val runtime = Runtime.getRuntime() + try { + val reader = BufferedReader(InputStreamReader(runtime.exec(cmd).inputStream)) + return reader.readLine() + } catch (e: Exception) { + throw TerminalCommandException(cmd, e) + } + } + +} diff --git a/src/main/resources/build-status-metric-template.html b/src/main/resources/build-status-metric-template.html new file mode 100644 index 00000000..7fb6a456 --- /dev/null +++ b/src/main/resources/build-status-metric-template.html @@ -0,0 +1,180 @@ +

Build Status

+

An overview of metrics results of the `requested task` in the build processes over the aforementioned period.

+
+ +
+ + + + + + + +
+
+ Cumulative
Overall Build
Process +
%cumulative-build-process-duration%
+
+ +
+ + + + + + + +
+
+ Total
Build Process
Count +
%total-build-process-count%
+
+ +
+ + + + + + + +
+
+ Total
Modules
Count +
%total-modules-count%
+
+ +
+ + + + + + + +
+
+ Cumulative
Parallel Exec
Duration +
%cumulative-parallel-exec-duration%
+
+ +
+ + + + + + + +
+
+ Average
Parallel Exec
Rate +
%avg-parallel-exec-rate%
+
+ +
+ + + + + + + +
+
+ Total
Succeed | Failed
Builds +
+ %total-succeed-build-count% | %total-failed-build-count% +
+
+ +
+ + + + + + + +
+
+ Average
Cache Hit
Rate +
%avg-cache-hit-rate%
+
+ +
+ + + + + + + +
+
+ Cumulative
Dependency
Resolve +
%cumulative-dependency-resolve-duration%
+
+ +
+ + + + + + + +
+
+ Average
Overall Build
Process +
%avg-build-process-duration%
+
+ +
+ + + + + + + +
+
+ Average
Initialization
Process +
%avg-initialization-process-duration%
+
+ +
+ + + + + + + +
+
+ Average
Configuration
Process +
%avg-configuration-process-duration%
+
+ +
+ + + + + + + +
+
+ Average
Execution
Process +
%avg-execution-process-duration%
+
+
+ Cumulative Parallel Exec Duration represents the total execution of the parallel builds. +
+ Average Parallel Exec Rate represents the average parallel builds time over the report period. +
+ Cumulative Dependency Resolve represents the total dependencies resolving time. +
diff --git a/src/main/resources/cache-hit-metric-template.html b/src/main/resources/cache-hit-metric-template.html new file mode 100644 index 00000000..5a56ecd9 --- /dev/null +++ b/src/main/resources/cache-hit-metric-template.html @@ -0,0 +1,134 @@ +

Cache Hit

+

+ Gradle creates a cache for the executed task to be reused in the next incremental builds, + the more cached tasks lead to faster builds. +

+ It represents the project and modules tasks average cache hit rate (tasks run with FROM_CACHE or UP_TO_DATE). +

+
+ + +
+ + Higher is better + +
+
+
+
+ + + + + + + + %table-data% + + + + %overall-diff-rate% + +
#ModuleHitDiff Rate
Overall%overall-cache-hit%
+
+
+
+ +
+ + + + Best and Worst
+ (Green is best) +
+
+
+ Hit represents the cache usage of executed tasks within a module. +
+ Diff Rate shows the improvement in cache usage between the first and last execution over the report period. +
+ Best and Worst represents the module's most and least cache usage. +
diff --git a/src/main/resources/configuration-process-metric-template.html b/src/main/resources/configuration-process-metric-template.html new file mode 100644 index 00000000..625d6df7 --- /dev/null +++ b/src/main/resources/configuration-process-metric-template.html @@ -0,0 +1,70 @@ +

Configuration Process

+

+ Constructs and configures the task graph for the build and then determines which tasks need to run and in which + order, based on the task the user wants to run. Be careful about the tasks you register to the project and try to + make them cacheable. +

+ It shows the average configuration process time over the report period. +

+
+ + +
+ + Lower is better + +
diff --git a/src/main/resources/dependency-details-metric-template.html b/src/main/resources/dependency-details-metric-template.html new file mode 100644 index 00000000..f455f1b9 --- /dev/null +++ b/src/main/resources/dependency-details-metric-template.html @@ -0,0 +1,60 @@ +

Dependency Details

+

It represents the project(including all modules) dependencies with their sizes.

+
+
+
+ + + + + + + %table-dataset% + + + + +
#DependencySize
Total%cumulative-dependencies-size%
+ +
+
+
+ + +
+
+
diff --git a/src/main/resources/dependency-resolve-process-metric-template.html b/src/main/resources/dependency-resolve-process-metric-template.html new file mode 100644 index 00000000..7092b896 --- /dev/null +++ b/src/main/resources/dependency-resolve-process-metric-template.html @@ -0,0 +1,68 @@ +

Dependency Resolve Process

+

+ Downloading and resolving the project's dependencies is one of the configuration process' stages. + If the project has multiple third-party libraries dependencies, make sure that you have a good network speed. +

+ It represents the download(Dependency Resolve) process average duration during the report period. +

+
+ + +
+ + Lower is better + +
\ No newline at end of file diff --git a/src/main/resources/execution-process-metric-template.html b/src/main/resources/execution-process-metric-template.html new file mode 100644 index 00000000..fa873d88 --- /dev/null +++ b/src/main/resources/execution-process-metric-template.html @@ -0,0 +1,68 @@ +

Execution Process

+

+ Runs the selected tasks based on `requested tasks` task tree. Gradle executes `requested task` according to the + dependency order. +

+ It represents the Execution Process average duration during the report period. +

+
+ + +
+ + Lower is better + +
diff --git a/src/main/resources/index-template.html b/src/main/resources/index-template.html new file mode 100644 index 00000000..081ddfe7 --- /dev/null +++ b/src/main/resources/index-template.html @@ -0,0 +1,148 @@ + + + + + %root-project-name% Build Analytics Report + + + + + + + + + + + +
+ logo +

%root-project-name% Build Analytics Report

+

+ Analyze task + %task-path% + on branch + %branch% +

+

Head commit hash: %git-head-commit-hash%

+

Time period: %time-period-start% - %time-period-end%

+

Reported at: %reported-at%

+

Is on CI/CD: %is-ci%

+

Plugin version: %plugin-version%

+
+ +

ATTENTION

+ + To understand the metrics that plugin provides, It is required to understand Gradle basics and how this build + system works.
https://docs.gradle.org/current/userguide/what_is_gradle.html +
+
+ +

Things you should know

+
+

+ 1. Every process in the Gradle build system starts with a task, the `requested task` means the task you defined + to be analyzed. +
+ 2. Metrics are getting saved on daily basis and if build process executes in CI/CD, metrics will be saved in its caching database. +
+ 3. Plugin only holds the metrics results in the caching database up to one year. +
+ 4. It is recommended to keep track of the reports after important stages of your CI/CD or Local build in order to see the actual result. +
+ 5. You need to understand median/mean to be able to read the chart of metrics results. +
+ 6. Plugin runs the metrics process when `requested task` executed on branch you configured. +

+
+
+ +%build-status-metric% + +%initialization-process-metric% + +%configuration-process-metric% + +%dependency-resolve-process-metric% + +%execution-process-metric% + +%modules-execution-process-metric% + +%overall-build-process-metric% + +%modules-source-count-metric% + +%modules-source-size-metric% + +%modules-method-count-metric% + +%cache-hit-metric% + +%success-build-rate-metric% + +%modules-crash-count-metric% + +%parallel-execution-rate-metric% + +%modules-dependency-graph-metric% + +%modules-execution-timeline-metric% + +%modules-build-heatmap-metric% + +%dependency-details-metric% + +%non-cacheable-tasks-metric% + +
+
+

DISCLAIMER

+ + The report is provided "as is", without warranty of any kind, express or + implied, including but not limited to the warranties of merchantability, + fitness for a particular purpose and noninfringement. In no event shall the + authors or copyright holders be liable for any claim, damages or other + liability, whether in an action of contract, tort or otherwise, arising from, + out of or in connection with the report or the use or other dealings in the + report. + +
+
+ +
+ + + Gradle Analytics Plugin + + + Made with ๐Ÿงก for everyone, don't forget to support us โ˜๐Ÿป + + + MIT License | Copyright ยฉ๏ธ 2022 + + +
+ + diff --git a/src/main/resources/initialization-process-metric-template.html b/src/main/resources/initialization-process-metric-template.html new file mode 100644 index 00000000..f0fdc19e --- /dev/null +++ b/src/main/resources/initialization-process-metric-template.html @@ -0,0 +1,70 @@ +

Initialization Process

+

+ Gradle supports single and multi-project builds. During the initialization process, Gradle determines which + projects are going to + take part in the build, and creates a Project instance for each of these projects. By adding more projects or + modules the process will take longer. +

+ It denotes the average initialization process time over the report period. +

+
+ + +
+ + Lower is better + +
diff --git a/src/main/resources/modules-build-heatmap-template.html b/src/main/resources/modules-build-heatmap-template.html new file mode 100644 index 00000000..4df062bb --- /dev/null +++ b/src/main/resources/modules-build-heatmap-template.html @@ -0,0 +1,52 @@ +

Modules Build Heatmap

+

+ This plugin uses `Modules Cache Usage` and `Modules Dependency Graph` to generate this metric that shows how many + times a module was built during the report period. +

+ Each bar has the name of the module and the number of dependent modules, smaller warm bars lead to faster builds + as those modules with warm colors have more dependent modules. +

+ In addition, it helps to modify the modular structure by tracing the graph and finding the cause to avoid + rebuilding the modules that are most shared with others. +

+
+ + +
+ + Smaller Heat Bars Are Better + +
diff --git a/src/main/resources/modules-crash-count-metric-template.html b/src/main/resources/modules-crash-count-metric-template.html new file mode 100644 index 00000000..d188f13f --- /dev/null +++ b/src/main/resources/modules-crash-count-metric-template.html @@ -0,0 +1,46 @@ +

Modules Crash Count

+

+ It represents how many build failures happened during the `requested task` execution + caused by project modules during the report period. +

+
+ + +
+ + Smaller Bars Are Better + +
diff --git a/src/main/resources/modules-dependency-graph-metric-template.html b/src/main/resources/modules-dependency-graph-metric-template.html new file mode 100644 index 00000000..47d013d9 --- /dev/null +++ b/src/main/resources/modules-dependency-graph-metric-template.html @@ -0,0 +1,20 @@ +

Modules Dependency Graph

+

+ It represents the project module's dependency graph and their connection types. +

+ Modules have colors that warm colors have represents more dependent modules, + and It is recommended to have fewer warm color modules because by applying any change in these modules, all other + dependent modules need to rebuild and this cost + more time and resources from your machines and put the builds in queue. +

+
+ graph TB + + classDef blue fill:#99f0ff, stroke:#52cce0; + classDef yellow fill:#fcf8a2, stroke:#c4be40; + classDef orange fill:#fbc36f, stroke:#e4a13b; + classDef red fill:#ff8c7f, stroke:#e44c3b; + + %mermaid-commands% +
+
diff --git a/src/main/resources/modules-execution-process-metric-template.html b/src/main/resources/modules-execution-process-metric-template.html new file mode 100644 index 00000000..7962b38d --- /dev/null +++ b/src/main/resources/modules-execution-process-metric-template.html @@ -0,0 +1,72 @@ +

Modules Execution Process

+

It represents the (Median) process execution time of each module over the report period.

+
+ + +
+ + Lower is better + +
+
+ + + + + + + + + + + %table-data% +
#ModuleAvg DurationAvg Parallel DurationAvg Parallel RateAvg CoverageDuration Diff Rate
+
+
+ Avg Duration is the module's median average execution time. +
+ Avg Parallel Duration is the module's median average parallel execution time. +
+ Avg Parallel Rate is the module's median average parallel execution rate over the report period. +
+ Avg Coverage is the average duration rate of the module against overall build. +
+ Duration Diff Rate shows improvement percentage of the module's execution time between first and last execution over the report period. +
diff --git a/src/main/resources/modules-method-count-metric-template.html b/src/main/resources/modules-method-count-metric-template.html new file mode 100644 index 00000000..68f4a87e --- /dev/null +++ b/src/main/resources/modules-method-count-metric-template.html @@ -0,0 +1,66 @@ +

Modules Method Count

+

It represents the project and its modules source method count.

+
+
+
+ + + + + + + + + %table-data% + + + + + %total-diff-rate% + +
#ModuleCountCoverageDiff Rate
Total%total-method-count%ฮฃ
+
+
+
+ + +
+
+
+ Coverage represents the ratio of the project source methods occupied with the module source methods.
+ Diff Rate represents the change rate of source methods during the report period. +
diff --git a/src/main/resources/modules-source-count-metric-template.html b/src/main/resources/modules-source-count-metric-template.html new file mode 100644 index 00000000..9a2f8dc6 --- /dev/null +++ b/src/main/resources/modules-source-count-metric-template.html @@ -0,0 +1,69 @@ +

Modules Source Count

+

+ It represents the project and its modules source file count. (files with extension of kt, java). +

+
+
+
+ + + + + + + + + %table-data% + + + + + %total-diff-rate% + +
#ModuleCountCoverageDiff Rate
Total%total-source-count%ฮฃ
+
+
+
+ + +
+
+
+ Coverage represents the ratio of the project source occupied with the module source.
+ Diff Rate represents the change rate of source count during the report period. +
diff --git a/src/main/resources/modules-source-size-metric-template.html b/src/main/resources/modules-source-size-metric-template.html new file mode 100644 index 00000000..352c5691 --- /dev/null +++ b/src/main/resources/modules-source-size-metric-template.html @@ -0,0 +1,67 @@ +

Modules Source Size

+

It represents the project and its modules source file size.

+
+
+
+ + + + + + + + + %table-data% + + + + + %total-diff-rate% + +
#ModuleSizeCoverageDiff Rate
Total%total-source-size%kbฮฃ
+
+
+
+ + +
+
+
+ Coverage represents the ratio of the project source files size occupied with the module source files size.
+ Diff Rate represents the change rate of source files size during the report period. +
diff --git a/src/main/resources/modules-timeline-metric-template.html b/src/main/resources/modules-timeline-metric-template.html new file mode 100644 index 00000000..173aa1c8 --- /dev/null +++ b/src/main/resources/modules-timeline-metric-template.html @@ -0,0 +1,26 @@ +

Modules Execution Timeline

+

It represents the latest modules execution process timeline graph on %datetime%.

+
+ + + Gray color shows the cached tasks + +
diff --git a/src/main/resources/non-cacheable-tasks-metric-template.html b/src/main/resources/non-cacheable-tasks-metric-template.html new file mode 100644 index 00000000..d9868379 --- /dev/null +++ b/src/main/resources/non-cacheable-tasks-metric-template.html @@ -0,0 +1,46 @@ +

Non-cacheable Tasks

+

+ These tasks are executed in the `requested task` tree without being cached. Try to avoid creating tasks that are + not cacheable. Track this chart and detect tasks that are time-consuming. +

+
+ + +
+ + Smaller Bars Are Better + +
diff --git a/src/main/resources/overall-build-process-metric-template.html b/src/main/resources/overall-build-process-metric-template.html new file mode 100644 index 00000000..fe2c0090 --- /dev/null +++ b/src/main/resources/overall-build-process-metric-template.html @@ -0,0 +1,63 @@ +

Overall Build Process

+

It represents the average duration of overall build process.

+
+ + +
+ + Lower is better + +
diff --git a/src/main/resources/parallel-execution-rate-metric-template.html b/src/main/resources/parallel-execution-rate-metric-template.html new file mode 100644 index 00000000..a4039780 --- /dev/null +++ b/src/main/resources/parallel-execution-rate-metric-template.html @@ -0,0 +1,59 @@ +

Parallel Execution Rate

+

+ Gradle uses CPU cores to execute more tasks simultaneously, leading to a faster build. +

+ It represents a rate that how much time was saved in the execution of the build process with parallel execution + versus real elapsed time. +

+
+ + +
+ + Higher is better + +
diff --git a/src/main/resources/res/chart.js b/src/main/resources/res/chart.js new file mode 100644 index 00000000..b87d5ec8 --- /dev/null +++ b/src/main/resources/res/chart.js @@ -0,0 +1,11299 @@ +/*! + * Chart.js v3.8.2 + * https://www.chartjs.org + * (c) 2022 Chart.js Contributors + * Released under the MIT License + */ +!function(t, e) { + "object" == typeof exports && "undefined" != typeof module ? module.exports = e() : "function" == typeof define && define.amd ? define(e) : (t = "undefined" != typeof globalThis ? globalThis : t || self).Chart = e() +}(this, (function() { + "use strict"; + const t = "undefined" == typeof window ? function(t) { + return t() + } + : window.requestAnimationFrame; + function e(e, i, s) { + const n = s || (t=>Array.prototype.slice.call(t)); + let o = !1 + , a = []; + return function(...s) { + a = n(s), + o || (o = !0, + t.call(window, (()=>{ + o = !1, + e.apply(i, a) + } + ))) + } + } + function i(t, e) { + let i; + return function(...s) { + return e ? (clearTimeout(i), + i = setTimeout(t, e, s)) : t.apply(this, s), + e + } + } + const s = t=>"start" === t ? "left" : "end" === t ? "right" : "center" + , n = (t,e,i)=>"start" === t ? e : "end" === t ? i : (e + i) / 2 + , o = (t,e,i,s)=>t === (s ? "left" : "right") ? i : "center" === t ? (e + i) / 2 : e; + var a = new class { + constructor() { + this._request = null, + this._charts = new Map, + this._running = !1, + this._lastDate = void 0 + } + _notify(t, e, i, s) { + const n = e.listeners[s] + , o = e.duration; + n.forEach((s=>s({ + chart: t, + initial: e.initial, + numSteps: o, + currentStep: Math.min(i - e.start, o) + }))) + } + _refresh() { + this._request || (this._running = !0, + this._request = t.call(window, (()=>{ + this._update(), + this._request = null, + this._running && this._refresh() + } + ))) + } + _update(t=Date.now()) { + let e = 0; + this._charts.forEach(((i,s)=>{ + if (!i.running || !i.items.length) + return; + const n = i.items; + let o, a = n.length - 1, r = !1; + for (; a >= 0; --a) + o = n[a], + o._active ? (o._total > i.duration && (i.duration = o._total), + o.tick(t), + r = !0) : (n[a] = n[n.length - 1], + n.pop()); + r && (s.draw(), + this._notify(s, i, t, "progress")), + n.length || (i.running = !1, + this._notify(s, i, t, "complete"), + i.initial = !1), + e += n.length + } + )), + this._lastDate = t, + 0 === e && (this._running = !1) + } + _getAnims(t) { + const e = this._charts; + let i = e.get(t); + return i || (i = { + running: !1, + initial: !0, + items: [], + listeners: { + complete: [], + progress: [] + } + }, + e.set(t, i)), + i + } + listen(t, e, i) { + this._getAnims(t).listeners[e].push(i) + } + add(t, e) { + e && e.length && this._getAnims(t).items.push(...e) + } + has(t) { + return this._getAnims(t).items.length > 0 + } + start(t) { + const e = this._charts.get(t); + e && (e.running = !0, + e.start = Date.now(), + e.duration = e.items.reduce(((t,e)=>Math.max(t, e._duration)), 0), + this._refresh()) + } + running(t) { + if (!this._running) + return !1; + const e = this._charts.get(t); + return !!(e && e.running && e.items.length) + } + stop(t) { + const e = this._charts.get(t); + if (!e || !e.items.length) + return; + const i = e.items; + let s = i.length - 1; + for (; s >= 0; --s) + i[s].cancel(); + e.items = [], + this._notify(t, e, Date.now(), "complete") + } + remove(t) { + return this._charts.delete(t) + } + } + ; + /*! + * @kurkle/color v0.2.1 + * https://github.com/kurkle/color#readme + * (c) 2022 Jukka Kurkela + * Released under the MIT License + */ + function r(t) { + return t + .5 | 0 + } + const l = (t,e,i)=>Math.max(Math.min(t, i), e); + function h(t) { + return l(r(2.55 * t), 0, 255) + } + function c(t) { + return l(r(255 * t), 0, 255) + } + function d(t) { + return l(r(t / 2.55) / 100, 0, 1) + } + function u(t) { + return l(r(100 * t), 0, 100) + } + const f = { + 0: 0, + 1: 1, + 2: 2, + 3: 3, + 4: 4, + 5: 5, + 6: 6, + 7: 7, + 8: 8, + 9: 9, + A: 10, + B: 11, + C: 12, + D: 13, + E: 14, + F: 15, + a: 10, + b: 11, + c: 12, + d: 13, + e: 14, + f: 15 + } + , g = [..."0123456789ABCDEF"] + , p = t=>g[15 & t] + , m = t=>g[(240 & t) >> 4] + g[15 & t] + , b = t=>(240 & t) >> 4 == (15 & t); + function x(t) { + var e = (t=>b(t.r) && b(t.g) && b(t.b) && b(t.a))(t) ? p : m; + return t ? "#" + e(t.r) + e(t.g) + e(t.b) + ((t,e)=>t < 255 ? e(t) : "")(t.a, e) : void 0 + } + const _ = /^(hsla?|hwb|hsv)\(\s*([-+.e\d]+)(?:deg)?[\s,]+([-+.e\d]+)%[\s,]+([-+.e\d]+)%(?:[\s,]+([-+.e\d]+)(%)?)?\s*\)$/; + function y(t, e, i) { + const s = e * Math.min(i, 1 - i) + , n = (e,n=(e + t / 30) % 12)=>i - s * Math.max(Math.min(n - 3, 9 - n, 1), -1); + return [n(0), n(8), n(4)] + } + function v(t, e, i) { + const s = (s,n=(s + t / 60) % 6)=>i - i * e * Math.max(Math.min(n, 4 - n, 1), 0); + return [s(5), s(3), s(1)] + } + function w(t, e, i) { + const s = y(t, 1, .5); + let n; + for (e + i > 1 && (n = 1 / (e + i), + e *= n, + i *= n), + n = 0; n < 3; n++) + s[n] *= 1 - e - i, + s[n] += e; + return s + } + function M(t) { + const e = t.r / 255 + , i = t.g / 255 + , s = t.b / 255 + , n = Math.max(e, i, s) + , o = Math.min(e, i, s) + , a = (n + o) / 2; + let r, l, h; + return n !== o && (h = n - o, + l = a > .5 ? h / (2 - n - o) : h / (n + o), + r = function(t, e, i, s, n) { + return t === n ? (e - i) / s + (e < i ? 6 : 0) : e === n ? (i - t) / s + 2 : (t - e) / s + 4 + }(e, i, s, h, n), + r = 60 * r + .5), + [0 | r, l || 0, a] + } + function k(t, e, i, s) { + return (Array.isArray(e) ? t(e[0], e[1], e[2]) : t(e, i, s)).map(c) + } + function S(t, e, i) { + return k(y, t, e, i) + } + function P(t) { + return (t % 360 + 360) % 360 + } + function D(t) { + const e = _.exec(t); + let i, s = 255; + if (!e) + return; + e[5] !== i && (s = e[6] ? h(+e[5]) : c(+e[5])); + const n = P(+e[2]) + , o = +e[3] / 100 + , a = +e[4] / 100; + return i = "hwb" === e[1] ? function(t, e, i) { + return k(w, t, e, i) + }(n, o, a) : "hsv" === e[1] ? function(t, e, i) { + return k(v, t, e, i) + }(n, o, a) : S(n, o, a), + { + r: i[0], + g: i[1], + b: i[2], + a: s + } + } + const C = { + x: "dark", + Z: "light", + Y: "re", + X: "blu", + W: "gr", + V: "medium", + U: "slate", + A: "ee", + T: "ol", + S: "or", + B: "ra", + C: "lateg", + D: "ights", + R: "in", + Q: "turquois", + E: "hi", + P: "ro", + O: "al", + N: "le", + M: "de", + L: "yello", + F: "en", + K: "ch", + G: "arks", + H: "ea", + I: "ightg", + J: "wh" + } + , O = { + OiceXe: "f0f8ff", + antiquewEte: "faebd7", + aqua: "ffff", + aquamarRe: "7fffd4", + azuY: "f0ffff", + beige: "f5f5dc", + bisque: "ffe4c4", + black: "0", + blanKedOmond: "ffebcd", + Xe: "ff", + XeviTet: "8a2be2", + bPwn: "a52a2a", + burlywood: "deb887", + caMtXe: "5f9ea0", + KartYuse: "7fff00", + KocTate: "d2691e", + cSO: "ff7f50", + cSnflowerXe: "6495ed", + cSnsilk: "fff8dc", + crimson: "dc143c", + cyan: "ffff", + xXe: "8b", + xcyan: "8b8b", + xgTMnPd: "b8860b", + xWay: "a9a9a9", + xgYF: "6400", + xgYy: "a9a9a9", + xkhaki: "bdb76b", + xmagFta: "8b008b", + xTivegYF: "556b2f", + xSange: "ff8c00", + xScEd: "9932cc", + xYd: "8b0000", + xsOmon: "e9967a", + xsHgYF: "8fbc8f", + xUXe: "483d8b", + xUWay: "2f4f4f", + xUgYy: "2f4f4f", + xQe: "ced1", + xviTet: "9400d3", + dAppRk: "ff1493", + dApskyXe: "bfff", + dimWay: "696969", + dimgYy: "696969", + dodgerXe: "1e90ff", + fiYbrick: "b22222", + flSOwEte: "fffaf0", + foYstWAn: "228b22", + fuKsia: "ff00ff", + gaRsbSo: "dcdcdc", + ghostwEte: "f8f8ff", + gTd: "ffd700", + gTMnPd: "daa520", + Way: "808080", + gYF: "8000", + gYFLw: "adff2f", + gYy: "808080", + honeyMw: "f0fff0", + hotpRk: "ff69b4", + RdianYd: "cd5c5c", + Rdigo: "4b0082", + ivSy: "fffff0", + khaki: "f0e68c", + lavFMr: "e6e6fa", + lavFMrXsh: "fff0f5", + lawngYF: "7cfc00", + NmoncEffon: "fffacd", + ZXe: "add8e6", + ZcSO: "f08080", + Zcyan: "e0ffff", + ZgTMnPdLw: "fafad2", + ZWay: "d3d3d3", + ZgYF: "90ee90", + ZgYy: "d3d3d3", + ZpRk: "ffb6c1", + ZsOmon: "ffa07a", + ZsHgYF: "20b2aa", + ZskyXe: "87cefa", + ZUWay: "778899", + ZUgYy: "778899", + ZstAlXe: "b0c4de", + ZLw: "ffffe0", + lime: "ff00", + limegYF: "32cd32", + lRF: "faf0e6", + magFta: "ff00ff", + maPon: "800000", + VaquamarRe: "66cdaa", + VXe: "cd", + VScEd: "ba55d3", + VpurpN: "9370db", + VsHgYF: "3cb371", + VUXe: "7b68ee", + VsprRggYF: "fa9a", + VQe: "48d1cc", + VviTetYd: "c71585", + midnightXe: "191970", + mRtcYam: "f5fffa", + mistyPse: "ffe4e1", + moccasR: "ffe4b5", + navajowEte: "ffdead", + navy: "80", + Tdlace: "fdf5e6", + Tive: "808000", + TivedBb: "6b8e23", + Sange: "ffa500", + SangeYd: "ff4500", + ScEd: "da70d6", + pOegTMnPd: "eee8aa", + pOegYF: "98fb98", + pOeQe: "afeeee", + pOeviTetYd: "db7093", + papayawEp: "ffefd5", + pHKpuff: "ffdab9", + peru: "cd853f", + pRk: "ffc0cb", + plum: "dda0dd", + powMrXe: "b0e0e6", + purpN: "800080", + YbeccapurpN: "663399", + Yd: "ff0000", + Psybrown: "bc8f8f", + PyOXe: "4169e1", + saddNbPwn: "8b4513", + sOmon: "fa8072", + sandybPwn: "f4a460", + sHgYF: "2e8b57", + sHshell: "fff5ee", + siFna: "a0522d", + silver: "c0c0c0", + skyXe: "87ceeb", + UXe: "6a5acd", + UWay: "708090", + UgYy: "708090", + snow: "fffafa", + sprRggYF: "ff7f", + stAlXe: "4682b4", + tan: "d2b48c", + teO: "8080", + tEstN: "d8bfd8", + tomato: "ff6347", + Qe: "40e0d0", + viTet: "ee82ee", + JHt: "f5deb3", + wEte: "ffffff", + wEtesmoke: "f5f5f5", + Lw: "ffff00", + LwgYF: "9acd32" + }; + let A; + function T(t) { + A || (A = function() { + const t = {} + , e = Object.keys(O) + , i = Object.keys(C); + let s, n, o, a, r; + for (s = 0; s < e.length; s++) { + for (a = r = e[s], + n = 0; n < i.length; n++) + o = i[n], + r = r.replace(o, C[o]); + o = parseInt(O[a], 16), + t[r] = [o >> 16 & 255, o >> 8 & 255, 255 & o] + } + return t + }(), + A.transparent = [0, 0, 0, 0]); + const e = A[t.toLowerCase()]; + return e && { + r: e[0], + g: e[1], + b: e[2], + a: 4 === e.length ? e[3] : 255 + } + } + const L = /^rgba?\(\s*([-+.\d]+)(%)?[\s,]+([-+.e\d]+)(%)?[\s,]+([-+.e\d]+)(%)?(?:[\s,/]+([-+.e\d]+)(%)?)?\s*\)$/; + const R = t=>t <= .0031308 ? 12.92 * t : 1.055 * Math.pow(t, 1 / 2.4) - .055 + , E = t=>t <= .04045 ? t / 12.92 : Math.pow((t + .055) / 1.055, 2.4); + function I(t, e, i) { + if (t) { + let s = M(t); + s[e] = Math.max(0, Math.min(s[e] + s[e] * i, 0 === e ? 360 : 1)), + s = S(s), + t.r = s[0], + t.g = s[1], + t.b = s[2] + } + } + function z(t, e) { + return t ? Object.assign(e || {}, t) : t + } + function F(t) { + var e = { + r: 0, + g: 0, + b: 0, + a: 255 + }; + return Array.isArray(t) ? t.length >= 3 && (e = { + r: t[0], + g: t[1], + b: t[2], + a: 255 + }, + t.length > 3 && (e.a = c(t[3]))) : (e = z(t, { + r: 0, + g: 0, + b: 0, + a: 1 + })).a = c(e.a), + e + } + function B(t) { + return "r" === t.charAt(0) ? function(t) { + const e = L.exec(t); + let i, s, n, o = 255; + if (e) { + if (e[7] !== i) { + const t = +e[7]; + o = e[8] ? h(t) : l(255 * t, 0, 255) + } + return i = +e[1], + s = +e[3], + n = +e[5], + i = 255 & (e[2] ? h(i) : l(i, 0, 255)), + s = 255 & (e[4] ? h(s) : l(s, 0, 255)), + n = 255 & (e[6] ? h(n) : l(n, 0, 255)), + { + r: i, + g: s, + b: n, + a: o + } + } + }(t) : D(t) + } + class V { + constructor(t) { + if (t instanceof V) + return t; + const e = typeof t; + let i; + var s, n, o; + "object" === e ? i = F(t) : "string" === e && (o = (s = t).length, + "#" === s[0] && (4 === o || 5 === o ? n = { + r: 255 & 17 * f[s[1]], + g: 255 & 17 * f[s[2]], + b: 255 & 17 * f[s[3]], + a: 5 === o ? 17 * f[s[4]] : 255 + } : 7 !== o && 9 !== o || (n = { + r: f[s[1]] << 4 | f[s[2]], + g: f[s[3]] << 4 | f[s[4]], + b: f[s[5]] << 4 | f[s[6]], + a: 9 === o ? f[s[7]] << 4 | f[s[8]] : 255 + })), + i = n || T(t) || B(t)), + this._rgb = i, + this._valid = !!i + } + get valid() { + return this._valid + } + get rgb() { + var t = z(this._rgb); + return t && (t.a = d(t.a)), + t + } + set rgb(t) { + this._rgb = F(t) + } + rgbString() { + return this._valid ? (t = this._rgb) && (t.a < 255 ? `rgba(${t.r}, ${t.g}, ${t.b}, ${d(t.a)})` : `rgb(${t.r}, ${t.g}, ${t.b})`) : void 0; + var t + } + hexString() { + return this._valid ? x(this._rgb) : void 0 + } + hslString() { + return this._valid ? function(t) { + if (!t) + return; + const e = M(t) + , i = e[0] + , s = u(e[1]) + , n = u(e[2]); + return t.a < 255 ? `hsla(${i}, ${s}%, ${n}%, ${d(t.a)})` : `hsl(${i}, ${s}%, ${n}%)` + }(this._rgb) : void 0 + } + mix(t, e) { + if (t) { + const i = this.rgb + , s = t.rgb; + let n; + const o = e === n ? .5 : e + , a = 2 * o - 1 + , r = i.a - s.a + , l = ((a * r == -1 ? a : (a + r) / (1 + a * r)) + 1) / 2; + n = 1 - l, + i.r = 255 & l * i.r + n * s.r + .5, + i.g = 255 & l * i.g + n * s.g + .5, + i.b = 255 & l * i.b + n * s.b + .5, + i.a = o * i.a + (1 - o) * s.a, + this.rgb = i + } + return this + } + interpolate(t, e) { + return t && (this._rgb = function(t, e, i) { + const s = E(d(t.r)) + , n = E(d(t.g)) + , o = E(d(t.b)); + return { + r: c(R(s + i * (E(d(e.r)) - s))), + g: c(R(n + i * (E(d(e.g)) - n))), + b: c(R(o + i * (E(d(e.b)) - o))), + a: t.a + i * (e.a - t.a) + } + }(this._rgb, t._rgb, e)), + this + } + clone() { + return new V(this.rgb) + } + alpha(t) { + return this._rgb.a = c(t), + this + } + clearer(t) { + return this._rgb.a *= 1 - t, + this + } + greyscale() { + const t = this._rgb + , e = r(.3 * t.r + .59 * t.g + .11 * t.b); + return t.r = t.g = t.b = e, + this + } + opaquer(t) { + return this._rgb.a *= 1 + t, + this + } + negate() { + const t = this._rgb; + return t.r = 255 - t.r, + t.g = 255 - t.g, + t.b = 255 - t.b, + this + } + lighten(t) { + return I(this._rgb, 2, t), + this + } + darken(t) { + return I(this._rgb, 2, -t), + this + } + saturate(t) { + return I(this._rgb, 1, t), + this + } + desaturate(t) { + return I(this._rgb, 1, -t), + this + } + rotate(t) { + return function(t, e) { + var i = M(t); + i[0] = P(i[0] + e), + i = S(i), + t.r = i[0], + t.g = i[1], + t.b = i[2] + }(this._rgb, t), + this + } + } + function W(t) { + return new V(t) + } + function N(t) { + if (t && "object" == typeof t) { + const e = t.toString(); + return "[object CanvasPattern]" === e || "[object CanvasGradient]" === e + } + return !1 + } + function j(t) { + return N(t) ? t : W(t) + } + function H(t) { + return N(t) ? t : W(t).saturate(.5).darken(.1).hexString() + } + function $() {} + const Y = function() { + let t = 0; + return function() { + return t++ + } + }(); + function U(t) { + return null == t + } + function X(t) { + if (Array.isArray && Array.isArray(t)) + return !0; + const e = Object.prototype.toString.call(t); + return "[object" === e.slice(0, 7) && "Array]" === e.slice(-6) + } + function q(t) { + return null !== t && "[object Object]" === Object.prototype.toString.call(t) + } + const K = t=>("number" == typeof t || t instanceof Number) && isFinite(+t); + function G(t, e) { + return K(t) ? t : e + } + function Z(t, e) { + return void 0 === t ? e : t + } + const J = (t,e)=>"string" == typeof t && t.endsWith("%") ? parseFloat(t) / 100 : t / e + , Q = (t,e)=>"string" == typeof t && t.endsWith("%") ? parseFloat(t) / 100 * e : +t; + function tt(t, e, i) { + if (t && "function" == typeof t.call) + return t.apply(i, e) + } + function et(t, e, i, s) { + let n, o, a; + if (X(t)) + if (o = t.length, + s) + for (n = o - 1; n >= 0; n--) + e.call(i, t[n], n); + else + for (n = 0; n < o; n++) + e.call(i, t[n], n); + else if (q(t)) + for (a = Object.keys(t), + o = a.length, + n = 0; n < o; n++) + e.call(i, t[a[n]], a[n]) + } + function it(t, e) { + let i, s, n, o; + if (!t || !e || t.length !== e.length) + return !1; + for (i = 0, + s = t.length; i < s; ++i) + if (n = t[i], + o = e[i], + n.datasetIndex !== o.datasetIndex || n.index !== o.index) + return !1; + return !0 + } + function st(t) { + if (X(t)) + return t.map(st); + if (q(t)) { + const e = Object.create(null) + , i = Object.keys(t) + , s = i.length; + let n = 0; + for (; n < s; ++n) + e[i[n]] = st(t[i[n]]); + return e + } + return t + } + function nt(t) { + return -1 === ["__proto__", "prototype", "constructor"].indexOf(t) + } + function ot(t, e, i, s) { + if (!nt(t)) + return; + const n = e[t] + , o = i[t]; + q(n) && q(o) ? at(n, o, s) : e[t] = st(o) + } + function at(t, e, i) { + const s = X(e) ? e : [e] + , n = s.length; + if (!q(t)) + return t; + const o = (i = i || {}).merger || ot; + for (let a = 0; a < n; ++a) { + if (!q(e = s[a])) + continue; + const n = Object.keys(e); + for (let s = 0, a = n.length; s < a; ++s) + o(n[s], t, e, i) + } + return t + } + function rt(t, e) { + return at(t, e, { + merger: lt + }) + } + function lt(t, e, i) { + if (!nt(t)) + return; + const s = e[t] + , n = i[t]; + q(s) && q(n) ? rt(s, n) : Object.prototype.hasOwnProperty.call(e, t) || (e[t] = st(n)) + } + function ht(t, e) { + const i = t.indexOf(".", e); + return -1 === i ? t.length : i + } + function ct(t, e) { + if ("" === e) + return t; + let i = 0 + , s = ht(e, i); + for (; t && s > i; ) + t = t[e.slice(i, s)], + i = s + 1, + s = ht(e, i); + return t + } + function dt(t) { + return t.charAt(0).toUpperCase() + t.slice(1) + } + const ut = t=>void 0 !== t + , ft = t=>"function" == typeof t + , gt = (t,e)=>{ + if (t.size !== e.size) + return !1; + for (const i of t) + if (!e.has(i)) + return !1; + return !0 + } + ; + function pt(t) { + return "mouseup" === t.type || "click" === t.type || "contextmenu" === t.type + } + const mt = Object.create(null) + , bt = Object.create(null); + function xt(t, e) { + if (!e) + return t; + const i = e.split("."); + for (let e = 0, s = i.length; e < s; ++e) { + const s = i[e]; + t = t[s] || (t[s] = Object.create(null)) + } + return t + } + function _t(t, e, i) { + return "string" == typeof e ? at(xt(t, e), i) : at(xt(t, ""), e) + } + var yt = new class { + constructor(t) { + this.animation = void 0, + this.backgroundColor = "rgba(0,0,0,0.1)", + this.borderColor = "rgba(0,0,0,0.1)", + this.color = "#666", + this.datasets = {}, + this.devicePixelRatio = t=>t.chart.platform.getDevicePixelRatio(), + this.elements = {}, + this.events = ["mousemove", "mouseout", "click", "touchstart", "touchmove"], + this.font = { + family: "'Helvetica Neue', 'Helvetica', 'Arial', sans-serif", + size: 12, + style: "normal", + lineHeight: 1.2, + weight: null + }, + this.hover = {}, + this.hoverBackgroundColor = (t,e)=>H(e.backgroundColor), + this.hoverBorderColor = (t,e)=>H(e.borderColor), + this.hoverColor = (t,e)=>H(e.color), + this.indexAxis = "x", + this.interaction = { + mode: "nearest", + intersect: !0, + includeInvisible: !1 + }, + this.maintainAspectRatio = !0, + this.onHover = null, + this.onClick = null, + this.parsing = !0, + this.plugins = {}, + this.responsive = !0, + this.scale = void 0, + this.scales = {}, + this.showLine = !0, + this.drawActiveElementsOnTop = !0, + this.describe(t) + } + set(t, e) { + return _t(this, t, e) + } + get(t) { + return xt(this, t) + } + describe(t, e) { + return _t(bt, t, e) + } + override(t, e) { + return _t(mt, t, e) + } + route(t, e, i, s) { + const n = xt(this, t) + , o = xt(this, i) + , a = "_" + e; + Object.defineProperties(n, { + [a]: { + value: n[e], + writable: !0 + }, + [e]: { + enumerable: !0, + get() { + const t = this[a] + , e = o[s]; + return q(t) ? Object.assign({}, e, t) : Z(t, e) + }, + set(t) { + this[a] = t + } + } + }) + } + } + ({ + _scriptable: t=>!t.startsWith("on"), + _indexable: t=>"events" !== t, + hover: { + _fallback: "interaction" + }, + interaction: { + _scriptable: !1, + _indexable: !1 + } + }); + function vt(t, e, i) { + i = i || (i=>t[i] < e); + let s, n = t.length - 1, o = 0; + for (; n - o > 1; ) + s = o + n >> 1, + i(s) ? o = s : n = s; + return { + lo: o, + hi: n + } + } + const wt = (t,e,i)=>vt(t, i, (s=>t[s][e] < i)) + , Mt = (t,e,i)=>vt(t, i, (s=>t[s][e] >= i)); + function kt(t, e, i) { + let s = 0 + , n = t.length; + for (; s < n && t[s] < e; ) + s++; + for (; n > s && t[n - 1] > i; ) + n--; + return s > 0 || n < t.length ? t.slice(s, n) : t + } + const St = ["push", "pop", "shift", "splice", "unshift"]; + function Pt(t, e) { + t._chartjs ? t._chartjs.listeners.push(e) : (Object.defineProperty(t, "_chartjs", { + configurable: !0, + enumerable: !1, + value: { + listeners: [e] + } + }), + St.forEach((e=>{ + const i = "_onData" + dt(e) + , s = t[e]; + Object.defineProperty(t, e, { + configurable: !0, + enumerable: !1, + value(...e) { + const n = s.apply(this, e); + return t._chartjs.listeners.forEach((t=>{ + "function" == typeof t[i] && t[i](...e) + } + )), + n + } + }) + } + ))) + } + function Dt(t, e) { + const i = t._chartjs; + if (!i) + return; + const s = i.listeners + , n = s.indexOf(e); + -1 !== n && s.splice(n, 1), + s.length > 0 || (St.forEach((e=>{ + delete t[e] + } + )), + delete t._chartjs) + } + function Ct(t) { + const e = new Set; + let i, s; + for (i = 0, + s = t.length; i < s; ++i) + e.add(t[i]); + return e.size === s ? t : Array.from(e) + } + const Ot = Math.PI + , At = 2 * Ot + , Tt = At + Ot + , Lt = Number.POSITIVE_INFINITY + , Rt = Ot / 180 + , Et = Ot / 2 + , It = Ot / 4 + , zt = 2 * Ot / 3 + , Ft = Math.log10 + , Bt = Math.sign; + function Vt(t) { + const e = Math.round(t); + t = jt(t, e, t / 1e3) ? e : t; + const i = Math.pow(10, Math.floor(Ft(t))) + , s = t / i; + return (s <= 1 ? 1 : s <= 2 ? 2 : s <= 5 ? 5 : 10) * i + } + function Wt(t) { + const e = [] + , i = Math.sqrt(t); + let s; + for (s = 1; s < i; s++) + t % s == 0 && (e.push(s), + e.push(t / s)); + return i === (0 | i) && e.push(i), + e.sort(((t,e)=>t - e)).pop(), + e + } + function Nt(t) { + return !isNaN(parseFloat(t)) && isFinite(t) + } + function jt(t, e, i) { + return Math.abs(t - e) < i + } + function Ht(t, e) { + const i = Math.round(t); + return i - e <= t && i + e >= t + } + function $t(t, e, i) { + let s, n, o; + for (s = 0, + n = t.length; s < n; s++) + o = t[s][i], + isNaN(o) || (e.min = Math.min(e.min, o), + e.max = Math.max(e.max, o)) + } + function Yt(t) { + return t * (Ot / 180) + } + function Ut(t) { + return t * (180 / Ot) + } + function Xt(t) { + if (!K(t)) + return; + let e = 1 + , i = 0; + for (; Math.round(t * e) / e !== t; ) + e *= 10, + i++; + return i + } + function qt(t, e) { + const i = e.x - t.x + , s = e.y - t.y + , n = Math.sqrt(i * i + s * s); + let o = Math.atan2(s, i); + return o < -.5 * Ot && (o += At), + { + angle: o, + distance: n + } + } + function Kt(t, e) { + return Math.sqrt(Math.pow(e.x - t.x, 2) + Math.pow(e.y - t.y, 2)) + } + function Gt(t, e) { + return (t - e + Tt) % At - Ot + } + function Zt(t) { + return (t % At + At) % At + } + function Jt(t, e, i, s) { + const n = Zt(t) + , o = Zt(e) + , a = Zt(i) + , r = Zt(o - n) + , l = Zt(a - n) + , h = Zt(n - o) + , c = Zt(n - a); + return n === o || n === a || s && o === a || r > l && h < c + } + function Qt(t, e, i) { + return Math.max(e, Math.min(i, t)) + } + function te(t) { + return Qt(t, -32768, 32767) + } + function ee(t, e, i, s=1e-6) { + return t >= Math.min(e, i) - s && t <= Math.max(e, i) + s + } + function ie() { + return "undefined" != typeof window && "undefined" != typeof document + } + function se(t) { + let e = t.parentNode; + return e && "[object ShadowRoot]" === e.toString() && (e = e.host), + e + } + function ne(t, e, i) { + let s; + return "string" == typeof t ? (s = parseInt(t, 10), + -1 !== t.indexOf("%") && (s = s / 100 * e.parentNode[i])) : s = t, + s + } + const oe = t=>window.getComputedStyle(t, null); + function ae(t, e) { + return oe(t).getPropertyValue(e) + } + const re = ["top", "right", "bottom", "left"]; + function le(t, e, i) { + const s = {}; + i = i ? "-" + i : ""; + for (let n = 0; n < 4; n++) { + const o = re[n]; + s[o] = parseFloat(t[e + "-" + o + i]) || 0 + } + return s.width = s.left + s.right, + s.height = s.top + s.bottom, + s + } + function he(t, e) { + if ("native"in t) + return t; + const {canvas: i, currentDevicePixelRatio: s} = e + , n = oe(i) + , o = "border-box" === n.boxSizing + , a = le(n, "padding") + , r = le(n, "border", "width") + , {x: l, y: h, box: c} = function(t, e) { + const i = t.touches + , s = i && i.length ? i[0] : t + , {offsetX: n, offsetY: o} = s; + let a, r, l = !1; + if (((t,e,i)=>(t > 0 || e > 0) && (!i || !i.shadowRoot))(n, o, t.target)) + a = n, + r = o; + else { + const t = e.getBoundingClientRect(); + a = s.clientX - t.left, + r = s.clientY - t.top, + l = !0 + } + return { + x: a, + y: r, + box: l + } + }(t, i) + , d = a.left + (c && r.left) + , u = a.top + (c && r.top); + let {width: f, height: g} = e; + return o && (f -= a.width + r.width, + g -= a.height + r.height), + { + x: Math.round((l - d) / f * i.width / s), + y: Math.round((h - u) / g * i.height / s) + } + } + const ce = t=>Math.round(10 * t) / 10; + function de(t, e, i, s) { + const n = oe(t) + , o = le(n, "margin") + , a = ne(n.maxWidth, t, "clientWidth") || Lt + , r = ne(n.maxHeight, t, "clientHeight") || Lt + , l = function(t, e, i) { + let s, n; + if (void 0 === e || void 0 === i) { + const o = se(t); + if (o) { + const t = o.getBoundingClientRect() + , a = oe(o) + , r = le(a, "border", "width") + , l = le(a, "padding"); + e = t.width - l.width - r.width, + i = t.height - l.height - r.height, + s = ne(a.maxWidth, o, "clientWidth"), + n = ne(a.maxHeight, o, "clientHeight") + } else + e = t.clientWidth, + i = t.clientHeight + } + return { + width: e, + height: i, + maxWidth: s || Lt, + maxHeight: n || Lt + } + }(t, e, i); + let {width: h, height: c} = l; + if ("content-box" === n.boxSizing) { + const t = le(n, "border", "width") + , e = le(n, "padding"); + h -= e.width + t.width, + c -= e.height + t.height + } + return h = Math.max(0, h - o.width), + c = Math.max(0, s ? Math.floor(h / s) : c - o.height), + h = ce(Math.min(h, a, l.maxWidth)), + c = ce(Math.min(c, r, l.maxHeight)), + h && !c && (c = ce(h / 2)), + { + width: h, + height: c + } + } + function ue(t, e, i) { + const s = e || 1 + , n = Math.floor(t.height * s) + , o = Math.floor(t.width * s); + t.height = n / s, + t.width = o / s; + const a = t.canvas; + return a.style && (i || !a.style.height && !a.style.width) && (a.style.height = `${t.height}px`, + a.style.width = `${t.width}px`), + (t.currentDevicePixelRatio !== s || a.height !== n || a.width !== o) && (t.currentDevicePixelRatio = s, + a.height = n, + a.width = o, + t.ctx.setTransform(s, 0, 0, s, 0, 0), + !0) + } + const fe = function() { + let t = !1; + try { + const e = { + get passive() { + return t = !0, + !1 + } + }; + window.addEventListener("test", null, e), + window.removeEventListener("test", null, e) + } catch (t) {} + return t + }(); + function ge(t, e) { + const i = ae(t, e) + , s = i && i.match(/^(\d+)(\.\d+)?px$/); + return s ? +s[1] : void 0 + } + function pe(t) { + return !t || U(t.size) || U(t.family) ? null : (t.style ? t.style + " " : "") + (t.weight ? t.weight + " " : "") + t.size + "px " + t.family + } + function me(t, e, i, s, n) { + let o = e[n]; + return o || (o = e[n] = t.measureText(n).width, + i.push(n)), + o > s && (s = o), + s + } + function be(t, e, i, s) { + let n = (s = s || {}).data = s.data || {} + , o = s.garbageCollect = s.garbageCollect || []; + s.font !== e && (n = s.data = {}, + o = s.garbageCollect = [], + s.font = e), + t.save(), + t.font = e; + let a = 0; + const r = i.length; + let l, h, c, d, u; + for (l = 0; l < r; l++) + if (d = i[l], + null != d && !0 !== X(d)) + a = me(t, n, o, a, d); + else if (X(d)) + for (h = 0, + c = d.length; h < c; h++) + u = d[h], + null == u || X(u) || (a = me(t, n, o, a, u)); + t.restore(); + const f = o.length / 2; + if (f > i.length) { + for (l = 0; l < f; l++) + delete n[o[l]]; + o.splice(0, f) + } + return a + } + function xe(t, e, i) { + const s = t.currentDevicePixelRatio + , n = 0 !== i ? Math.max(i / 2, .5) : 0; + return Math.round((e - n) * s) / s + n + } + function _e(t, e) { + (e = e || t.getContext("2d")).save(), + e.resetTransform(), + e.clearRect(0, 0, t.width, t.height), + e.restore() + } + function ye(t, e, i, s) { + ve(t, e, i, s, null) + } + function ve(t, e, i, s, n) { + let o, a, r, l, h, c; + const d = e.pointStyle + , u = e.rotation + , f = e.radius; + let g = (u || 0) * Rt; + if (d && "object" == typeof d && (o = d.toString(), + "[object HTMLImageElement]" === o || "[object HTMLCanvasElement]" === o)) + return t.save(), + t.translate(i, s), + t.rotate(g), + t.drawImage(d, -d.width / 2, -d.height / 2, d.width, d.height), + void t.restore(); + if (!(isNaN(f) || f <= 0)) { + switch (t.beginPath(), + d) { + default: + n ? t.ellipse(i, s, n / 2, f, 0, 0, At) : t.arc(i, s, f, 0, At), + t.closePath(); + break; + case "triangle": + t.moveTo(i + Math.sin(g) * f, s - Math.cos(g) * f), + g += zt, + t.lineTo(i + Math.sin(g) * f, s - Math.cos(g) * f), + g += zt, + t.lineTo(i + Math.sin(g) * f, s - Math.cos(g) * f), + t.closePath(); + break; + case "rectRounded": + h = .516 * f, + l = f - h, + a = Math.cos(g + It) * l, + r = Math.sin(g + It) * l, + t.arc(i - a, s - r, h, g - Ot, g - Et), + t.arc(i + r, s - a, h, g - Et, g), + t.arc(i + a, s + r, h, g, g + Et), + t.arc(i - r, s + a, h, g + Et, g + Ot), + t.closePath(); + break; + case "rect": + if (!u) { + l = Math.SQRT1_2 * f, + c = n ? n / 2 : l, + t.rect(i - c, s - l, 2 * c, 2 * l); + break + } + g += It; + case "rectRot": + a = Math.cos(g) * f, + r = Math.sin(g) * f, + t.moveTo(i - a, s - r), + t.lineTo(i + r, s - a), + t.lineTo(i + a, s + r), + t.lineTo(i - r, s + a), + t.closePath(); + break; + case "crossRot": + g += It; + case "cross": + a = Math.cos(g) * f, + r = Math.sin(g) * f, + t.moveTo(i - a, s - r), + t.lineTo(i + a, s + r), + t.moveTo(i + r, s - a), + t.lineTo(i - r, s + a); + break; + case "star": + a = Math.cos(g) * f, + r = Math.sin(g) * f, + t.moveTo(i - a, s - r), + t.lineTo(i + a, s + r), + t.moveTo(i + r, s - a), + t.lineTo(i - r, s + a), + g += It, + a = Math.cos(g) * f, + r = Math.sin(g) * f, + t.moveTo(i - a, s - r), + t.lineTo(i + a, s + r), + t.moveTo(i + r, s - a), + t.lineTo(i - r, s + a); + break; + case "line": + a = n ? n / 2 : Math.cos(g) * f, + r = Math.sin(g) * f, + t.moveTo(i - a, s - r), + t.lineTo(i + a, s + r); + break; + case "dash": + t.moveTo(i, s), + t.lineTo(i + Math.cos(g) * f, s + Math.sin(g) * f) + } + t.fill(), + e.borderWidth > 0 && t.stroke() + } + } + function we(t, e, i) { + return i = i || .5, + !e || t && t.x > e.left - i && t.x < e.right + i && t.y > e.top - i && t.y < e.bottom + i + } + function Me(t, e) { + t.save(), + t.beginPath(), + t.rect(e.left, e.top, e.right - e.left, e.bottom - e.top), + t.clip() + } + function ke(t) { + t.restore() + } + function Se(t, e, i, s, n) { + if (!e) + return t.lineTo(i.x, i.y); + if ("middle" === n) { + const s = (e.x + i.x) / 2; + t.lineTo(s, e.y), + t.lineTo(s, i.y) + } else + "after" === n != !!s ? t.lineTo(e.x, i.y) : t.lineTo(i.x, e.y); + t.lineTo(i.x, i.y) + } + function Pe(t, e, i, s) { + if (!e) + return t.lineTo(i.x, i.y); + t.bezierCurveTo(s ? e.cp1x : e.cp2x, s ? e.cp1y : e.cp2y, s ? i.cp2x : i.cp1x, s ? i.cp2y : i.cp1y, i.x, i.y) + } + function De(t, e, i, s, n, o={}) { + const a = X(e) ? e : [e] + , r = o.strokeWidth > 0 && "" !== o.strokeColor; + let l, h; + for (t.save(), + t.font = n.string, + function(t, e) { + e.translation && t.translate(e.translation[0], e.translation[1]); + U(e.rotation) || t.rotate(e.rotation); + e.color && (t.fillStyle = e.color); + e.textAlign && (t.textAlign = e.textAlign); + e.textBaseline && (t.textBaseline = e.textBaseline) + }(t, o), + l = 0; l < a.length; ++l) + h = a[l], + r && (o.strokeColor && (t.strokeStyle = o.strokeColor), + U(o.strokeWidth) || (t.lineWidth = o.strokeWidth), + t.strokeText(h, i, s, o.maxWidth)), + t.fillText(h, i, s, o.maxWidth), + Ce(t, i, s, h, o), + s += n.lineHeight; + t.restore() + } + function Ce(t, e, i, s, n) { + if (n.strikethrough || n.underline) { + const o = t.measureText(s) + , a = e - o.actualBoundingBoxLeft + , r = e + o.actualBoundingBoxRight + , l = i - o.actualBoundingBoxAscent + , h = i + o.actualBoundingBoxDescent + , c = n.strikethrough ? (l + h) / 2 : h; + t.strokeStyle = t.fillStyle, + t.beginPath(), + t.lineWidth = n.decorationWidth || 2, + t.moveTo(a, c), + t.lineTo(r, c), + t.stroke() + } + } + function Oe(t, e) { + const {x: i, y: s, w: n, h: o, radius: a} = e; + t.arc(i + a.topLeft, s + a.topLeft, a.topLeft, -Et, Ot, !0), + t.lineTo(i, s + o - a.bottomLeft), + t.arc(i + a.bottomLeft, s + o - a.bottomLeft, a.bottomLeft, Ot, Et, !0), + t.lineTo(i + n - a.bottomRight, s + o), + t.arc(i + n - a.bottomRight, s + o - a.bottomRight, a.bottomRight, Et, 0, !0), + t.lineTo(i + n, s + a.topRight), + t.arc(i + n - a.topRight, s + a.topRight, a.topRight, 0, -Et, !0), + t.lineTo(i + a.topLeft, s) + } + function Ae(t, e=[""], i=t, s, n=(()=>t[0])) { + ut(s) || (s = Ne("_fallback", t)); + const o = { + [Symbol.toStringTag]: "Object", + _cacheable: !0, + _scopes: t, + _rootScopes: i, + _fallback: s, + _getTarget: n, + override: n=>Ae([n, ...t], e, i, s) + }; + return new Proxy(o,{ + deleteProperty: (e,i)=>(delete e[i], + delete e._keys, + delete t[0][i], + !0), + get: (i,s)=>Ie(i, s, (()=>function(t, e, i, s) { + let n; + for (const o of e) + if (n = Ne(Re(o, t), i), + ut(n)) + return Ee(t, n) ? Ve(i, s, t, n) : n + }(s, e, t, i))), + getOwnPropertyDescriptor: (t,e)=>Reflect.getOwnPropertyDescriptor(t._scopes[0], e), + getPrototypeOf: ()=>Reflect.getPrototypeOf(t[0]), + has: (t,e)=>je(t).includes(e), + ownKeys: t=>je(t), + set(t, e, i) { + const s = t._storage || (t._storage = n()); + return t[e] = s[e] = i, + delete t._keys, + !0 + } + }) + } + function Te(t, e, i, s) { + const n = { + _cacheable: !1, + _proxy: t, + _context: e, + _subProxy: i, + _stack: new Set, + _descriptors: Le(t, s), + setContext: e=>Te(t, e, i, s), + override: n=>Te(t.override(n), e, i, s) + }; + return new Proxy(n,{ + deleteProperty: (e,i)=>(delete e[i], + delete t[i], + !0), + get: (t,e,i)=>Ie(t, e, (()=>function(t, e, i) { + const {_proxy: s, _context: n, _subProxy: o, _descriptors: a} = t; + let r = s[e]; + ft(r) && a.isScriptable(e) && (r = function(t, e, i, s) { + const {_proxy: n, _context: o, _subProxy: a, _stack: r} = i; + if (r.has(t)) + throw new Error("Recursion detected: " + Array.from(r).join("->") + "->" + t); + r.add(t), + e = e(o, a || s), + r.delete(t), + Ee(t, e) && (e = Ve(n._scopes, n, t, e)); + return e + }(e, r, t, i)); + X(r) && r.length && (r = function(t, e, i, s) { + const {_proxy: n, _context: o, _subProxy: a, _descriptors: r} = i; + if (ut(o.index) && s(t)) + e = e[o.index % e.length]; + else if (q(e[0])) { + const i = e + , s = n._scopes.filter((t=>t !== i)); + e = []; + for (const l of i) { + const i = Ve(s, n, t, l); + e.push(Te(i, o, a && a[t], r)) + } + } + return e + }(e, r, t, a.isIndexable)); + Ee(e, r) && (r = Te(r, n, o && o[e], a)); + return r + }(t, e, i))), + getOwnPropertyDescriptor: (e,i)=>e._descriptors.allKeys ? Reflect.has(t, i) ? { + enumerable: !0, + configurable: !0 + } : void 0 : Reflect.getOwnPropertyDescriptor(t, i), + getPrototypeOf: ()=>Reflect.getPrototypeOf(t), + has: (e,i)=>Reflect.has(t, i), + ownKeys: ()=>Reflect.ownKeys(t), + set: (e,i,s)=>(t[i] = s, + delete e[i], + !0) + }) + } + function Le(t, e={ + scriptable: !0, + indexable: !0 + }) { + const {_scriptable: i=e.scriptable, _indexable: s=e.indexable, _allKeys: n=e.allKeys} = t; + return { + allKeys: n, + scriptable: i, + indexable: s, + isScriptable: ft(i) ? i : ()=>i, + isIndexable: ft(s) ? s : ()=>s + } + } + const Re = (t,e)=>t ? t + dt(e) : e + , Ee = (t,e)=>q(e) && "adapters" !== t && (null === Object.getPrototypeOf(e) || e.constructor === Object); + function Ie(t, e, i) { + if (Object.prototype.hasOwnProperty.call(t, e)) + return t[e]; + const s = i(); + return t[e] = s, + s + } + function ze(t, e, i) { + return ft(t) ? t(e, i) : t + } + const Fe = (t,e)=>!0 === t ? e : "string" == typeof t ? ct(e, t) : void 0; + function Be(t, e, i, s, n) { + for (const o of e) { + const e = Fe(i, o); + if (e) { + t.add(e); + const o = ze(e._fallback, i, n); + if (ut(o) && o !== i && o !== s) + return o + } else if (!1 === e && ut(s) && i !== s) + return null + } + return !1 + } + function Ve(t, e, i, s) { + const n = e._rootScopes + , o = ze(e._fallback, i, s) + , a = [...t, ...n] + , r = new Set; + r.add(s); + let l = We(r, a, i, o || i, s); + return null !== l && ((!ut(o) || o === i || (l = We(r, a, o, l, s), + null !== l)) && Ae(Array.from(r), [""], n, o, (()=>function(t, e, i) { + const s = t._getTarget(); + e in s || (s[e] = {}); + const n = s[e]; + if (X(n) && q(i)) + return i; + return n + }(e, i, s)))) + } + function We(t, e, i, s, n) { + for (; i; ) + i = Be(t, e, i, s, n); + return i + } + function Ne(t, e) { + for (const i of e) { + if (!i) + continue; + const e = i[t]; + if (ut(e)) + return e + } + } + function je(t) { + let e = t._keys; + return e || (e = t._keys = function(t) { + const e = new Set; + for (const i of t) + for (const t of Object.keys(i).filter((t=>!t.startsWith("_")))) + e.add(t); + return Array.from(e) + }(t._scopes)), + e + } + function He(t, e, i, s) { + const {iScale: n} = t + , {key: o="r"} = this._parsing + , a = new Array(s); + let r, l, h, c; + for (r = 0, + l = s; r < l; ++r) + h = r + i, + c = e[h], + a[r] = { + r: n.parse(ct(c, o), h) + }; + return a + } + const $e = Number.EPSILON || 1e-14 + , Ye = (t,e)=>e < t.length && !t[e].skip && t[e] + , Ue = t=>"x" === t ? "y" : "x"; + function Xe(t, e, i, s) { + const n = t.skip ? e : t + , o = e + , a = i.skip ? e : i + , r = Kt(o, n) + , l = Kt(a, o); + let h = r / (r + l) + , c = l / (r + l); + h = isNaN(h) ? 0 : h, + c = isNaN(c) ? 0 : c; + const d = s * h + , u = s * c; + return { + previous: { + x: o.x - d * (a.x - n.x), + y: o.y - d * (a.y - n.y) + }, + next: { + x: o.x + u * (a.x - n.x), + y: o.y + u * (a.y - n.y) + } + } + } + function qe(t, e="x") { + const i = Ue(e) + , s = t.length + , n = Array(s).fill(0) + , o = Array(s); + let a, r, l, h = Ye(t, 0); + for (a = 0; a < s; ++a) + if (r = l, + l = h, + h = Ye(t, a + 1), + l) { + if (h) { + const t = h[e] - l[e]; + n[a] = 0 !== t ? (h[i] - l[i]) / t : 0 + } + o[a] = r ? h ? Bt(n[a - 1]) !== Bt(n[a]) ? 0 : (n[a - 1] + n[a]) / 2 : n[a - 1] : n[a] + } + !function(t, e, i) { + const s = t.length; + let n, o, a, r, l, h = Ye(t, 0); + for (let c = 0; c < s - 1; ++c) + l = h, + h = Ye(t, c + 1), + l && h && (jt(e[c], 0, $e) ? i[c] = i[c + 1] = 0 : (n = i[c] / e[c], + o = i[c + 1] / e[c], + r = Math.pow(n, 2) + Math.pow(o, 2), + r <= 9 || (a = 3 / Math.sqrt(r), + i[c] = n * a * e[c], + i[c + 1] = o * a * e[c]))) + }(t, n, o), + function(t, e, i="x") { + const s = Ue(i) + , n = t.length; + let o, a, r, l = Ye(t, 0); + for (let h = 0; h < n; ++h) { + if (a = r, + r = l, + l = Ye(t, h + 1), + !r) + continue; + const n = r[i] + , c = r[s]; + a && (o = (n - a[i]) / 3, + r[`cp1${i}`] = n - o, + r[`cp1${s}`] = c - o * e[h]), + l && (o = (l[i] - n) / 3, + r[`cp2${i}`] = n + o, + r[`cp2${s}`] = c + o * e[h]) + } + }(t, o, e) + } + function Ke(t, e, i) { + return Math.max(Math.min(t, i), e) + } + function Ge(t, e, i, s, n) { + let o, a, r, l; + if (e.spanGaps && (t = t.filter((t=>!t.skip))), + "monotone" === e.cubicInterpolationMode) + qe(t, n); + else { + let i = s ? t[t.length - 1] : t[0]; + for (o = 0, + a = t.length; o < a; ++o) + r = t[o], + l = Xe(i, r, t[Math.min(o + 1, a - (s ? 0 : 1)) % a], e.tension), + r.cp1x = l.previous.x, + r.cp1y = l.previous.y, + r.cp2x = l.next.x, + r.cp2y = l.next.y, + i = r + } + e.capBezierPoints && function(t, e) { + let i, s, n, o, a, r = we(t[0], e); + for (i = 0, + s = t.length; i < s; ++i) + a = o, + o = r, + r = i < s - 1 && we(t[i + 1], e), + o && (n = t[i], + a && (n.cp1x = Ke(n.cp1x, e.left, e.right), + n.cp1y = Ke(n.cp1y, e.top, e.bottom)), + r && (n.cp2x = Ke(n.cp2x, e.left, e.right), + n.cp2y = Ke(n.cp2y, e.top, e.bottom))) + }(t, i) + } + const Ze = t=>0 === t || 1 === t + , Je = (t,e,i)=>-Math.pow(2, 10 * (t -= 1)) * Math.sin((t - e) * At / i) + , Qe = (t,e,i)=>Math.pow(2, -10 * t) * Math.sin((t - e) * At / i) + 1 + , ti = { + linear: t=>t, + easeInQuad: t=>t * t, + easeOutQuad: t=>-t * (t - 2), + easeInOutQuad: t=>(t /= .5) < 1 ? .5 * t * t : -.5 * (--t * (t - 2) - 1), + easeInCubic: t=>t * t * t, + easeOutCubic: t=>(t -= 1) * t * t + 1, + easeInOutCubic: t=>(t /= .5) < 1 ? .5 * t * t * t : .5 * ((t -= 2) * t * t + 2), + easeInQuart: t=>t * t * t * t, + easeOutQuart: t=>-((t -= 1) * t * t * t - 1), + easeInOutQuart: t=>(t /= .5) < 1 ? .5 * t * t * t * t : -.5 * ((t -= 2) * t * t * t - 2), + easeInQuint: t=>t * t * t * t * t, + easeOutQuint: t=>(t -= 1) * t * t * t * t + 1, + easeInOutQuint: t=>(t /= .5) < 1 ? .5 * t * t * t * t * t : .5 * ((t -= 2) * t * t * t * t + 2), + easeInSine: t=>1 - Math.cos(t * Et), + easeOutSine: t=>Math.sin(t * Et), + easeInOutSine: t=>-.5 * (Math.cos(Ot * t) - 1), + easeInExpo: t=>0 === t ? 0 : Math.pow(2, 10 * (t - 1)), + easeOutExpo: t=>1 === t ? 1 : 1 - Math.pow(2, -10 * t), + easeInOutExpo: t=>Ze(t) ? t : t < .5 ? .5 * Math.pow(2, 10 * (2 * t - 1)) : .5 * (2 - Math.pow(2, -10 * (2 * t - 1))), + easeInCirc: t=>t >= 1 ? t : -(Math.sqrt(1 - t * t) - 1), + easeOutCirc: t=>Math.sqrt(1 - (t -= 1) * t), + easeInOutCirc: t=>(t /= .5) < 1 ? -.5 * (Math.sqrt(1 - t * t) - 1) : .5 * (Math.sqrt(1 - (t -= 2) * t) + 1), + easeInElastic: t=>Ze(t) ? t : Je(t, .075, .3), + easeOutElastic: t=>Ze(t) ? t : Qe(t, .075, .3), + easeInOutElastic(t) { + const e = .1125; + return Ze(t) ? t : t < .5 ? .5 * Je(2 * t, e, .45) : .5 + .5 * Qe(2 * t - 1, e, .45) + }, + easeInBack(t) { + const e = 1.70158; + return t * t * ((e + 1) * t - e) + }, + easeOutBack(t) { + const e = 1.70158; + return (t -= 1) * t * ((e + 1) * t + e) + 1 + }, + easeInOutBack(t) { + let e = 1.70158; + return (t /= .5) < 1 ? t * t * ((1 + (e *= 1.525)) * t - e) * .5 : .5 * ((t -= 2) * t * ((1 + (e *= 1.525)) * t + e) + 2) + }, + easeInBounce: t=>1 - ti.easeOutBounce(1 - t), + easeOutBounce(t) { + const e = 7.5625 + , i = 2.75; + return t < 1 / i ? e * t * t : t < 2 / i ? e * (t -= 1.5 / i) * t + .75 : t < 2.5 / i ? e * (t -= 2.25 / i) * t + .9375 : e * (t -= 2.625 / i) * t + .984375 + }, + easeInOutBounce: t=>t < .5 ? .5 * ti.easeInBounce(2 * t) : .5 * ti.easeOutBounce(2 * t - 1) + .5 + }; + function ei(t, e, i, s) { + return { + x: t.x + i * (e.x - t.x), + y: t.y + i * (e.y - t.y) + } + } + function ii(t, e, i, s) { + return { + x: t.x + i * (e.x - t.x), + y: "middle" === s ? i < .5 ? t.y : e.y : "after" === s ? i < 1 ? t.y : e.y : i > 0 ? e.y : t.y + } + } + function si(t, e, i, s) { + const n = { + x: t.cp2x, + y: t.cp2y + } + , o = { + x: e.cp1x, + y: e.cp1y + } + , a = ei(t, n, i) + , r = ei(n, o, i) + , l = ei(o, e, i) + , h = ei(a, r, i) + , c = ei(r, l, i); + return ei(h, c, i) + } + const ni = new Map; + function oi(t, e, i) { + return function(t, e) { + e = e || {}; + const i = t + JSON.stringify(e); + let s = ni.get(i); + return s || (s = new Intl.NumberFormat(t,e), + ni.set(i, s)), + s + }(e, i).format(t) + } + const ai = new RegExp(/^(normal|(\d+(?:\.\d+)?)(px|em|%)?)$/) + , ri = new RegExp(/^(normal|italic|initial|inherit|unset|(oblique( -?[0-9]?[0-9]deg)?))$/); + function li(t, e) { + const i = ("" + t).match(ai); + if (!i || "normal" === i[1]) + return 1.2 * e; + switch (t = +i[2], + i[3]) { + case "px": + return t; + case "%": + t /= 100 + } + return e * t + } + function hi(t, e) { + const i = {} + , s = q(e) + , n = s ? Object.keys(e) : e + , o = q(t) ? s ? i=>Z(t[i], t[e[i]]) : e=>t[e] : ()=>t; + for (const t of n) + i[t] = +o(t) || 0; + return i + } + function ci(t) { + return hi(t, { + top: "y", + right: "x", + bottom: "y", + left: "x" + }) + } + function di(t) { + return hi(t, ["topLeft", "topRight", "bottomLeft", "bottomRight"]) + } + function ui(t) { + const e = ci(t); + return e.width = e.left + e.right, + e.height = e.top + e.bottom, + e + } + function fi(t, e) { + t = t || {}, + e = e || yt.font; + let i = Z(t.size, e.size); + "string" == typeof i && (i = parseInt(i, 10)); + let s = Z(t.style, e.style); + s && !("" + s).match(ri) && (console.warn('Invalid font style specified: "' + s + '"'), + s = ""); + const n = { + family: Z(t.family, e.family), + lineHeight: li(Z(t.lineHeight, e.lineHeight), i), + size: i, + style: s, + weight: Z(t.weight, e.weight), + string: "" + }; + return n.string = pe(n), + n + } + function gi(t, e, i, s) { + let n, o, a, r = !0; + for (n = 0, + o = t.length; n < o; ++n) + if (a = t[n], + void 0 !== a && (void 0 !== e && "function" == typeof a && (a = a(e), + r = !1), + void 0 !== i && X(a) && (a = a[i % a.length], + r = !1), + void 0 !== a)) + return s && !r && (s.cacheable = !1), + a + } + function pi(t, e, i) { + const {min: s, max: n} = t + , o = Q(e, (n - s) / 2) + , a = (t,e)=>i && 0 === t ? 0 : t + e; + return { + min: a(s, -Math.abs(o)), + max: a(n, o) + } + } + function mi(t, e) { + return Object.assign(Object.create(t), e) + } + function bi(t, e, i) { + return t ? function(t, e) { + return { + x: i=>t + t + e - i, + setWidth(t) { + e = t + }, + textAlign: t=>"center" === t ? t : "right" === t ? "left" : "right", + xPlus: (t,e)=>t - e, + leftForLtr: (t,e)=>t - e + } + }(e, i) : { + x: t=>t, + setWidth(t) {}, + textAlign: t=>t, + xPlus: (t,e)=>t + e, + leftForLtr: (t,e)=>t + } + } + function xi(t, e) { + let i, s; + "ltr" !== e && "rtl" !== e || (i = t.canvas.style, + s = [i.getPropertyValue("direction"), i.getPropertyPriority("direction")], + i.setProperty("direction", e, "important"), + t.prevTextDirection = s) + } + function _i(t, e) { + void 0 !== e && (delete t.prevTextDirection, + t.canvas.style.setProperty("direction", e[0], e[1])) + } + function yi(t) { + return "angle" === t ? { + between: Jt, + compare: Gt, + normalize: Zt + } : { + between: ee, + compare: (t,e)=>t - e, + normalize: t=>t + } + } + function vi({start: t, end: e, count: i, loop: s, style: n}) { + return { + start: t % i, + end: e % i, + loop: s && (e - t + 1) % i == 0, + style: n + } + } + function wi(t, e, i) { + if (!i) + return [t]; + const {property: s, start: n, end: o} = i + , a = e.length + , {compare: r, between: l, normalize: h} = yi(s) + , {start: c, end: d, loop: u, style: f} = function(t, e, i) { + const {property: s, start: n, end: o} = i + , {between: a, normalize: r} = yi(s) + , l = e.length; + let h, c, {start: d, end: u, loop: f} = t; + if (f) { + for (d += l, + u += l, + h = 0, + c = l; h < c && a(r(e[d % l][s]), n, o); ++h) + d--, + u--; + d %= l, + u %= l + } + return u < d && (u += l), + { + start: d, + end: u, + loop: f, + style: t.style + } + }(t, e, i) + , g = []; + let p, m, b, x = !1, _ = null; + const y = ()=>x || l(n, b, p) && 0 !== r(n, b) + , v = ()=>!x || 0 === r(o, p) || l(o, b, p); + for (let t = c, i = c; t <= d; ++t) + m = e[t % a], + m.skip || (p = h(m[s]), + p !== b && (x = l(p, n, o), + null === _ && y() && (_ = 0 === r(p, n) ? t : i), + null !== _ && v() && (g.push(vi({ + start: _, + end: t, + loop: u, + count: a, + style: f + })), + _ = null), + i = t, + b = p)); + return null !== _ && g.push(vi({ + start: _, + end: d, + loop: u, + count: a, + style: f + })), + g + } + function Mi(t, e) { + const i = [] + , s = t.segments; + for (let n = 0; n < s.length; n++) { + const o = wi(s[n], t.points, e); + o.length && i.push(...o) + } + return i + } + function ki(t, e) { + const i = t.points + , s = t.options.spanGaps + , n = i.length; + if (!n) + return []; + const o = !!t._loop + , {start: a, end: r} = function(t, e, i, s) { + let n = 0 + , o = e - 1; + if (i && !s) + for (; n < e && !t[n].skip; ) + n++; + for (; n < e && t[n].skip; ) + n++; + for (n %= e, + i && (o += n); o > n && t[o % e].skip; ) + o--; + return o %= e, + { + start: n, + end: o + } + }(i, n, o, s); + if (!0 === s) + return Si(t, [{ + start: a, + end: r, + loop: o + }], i, e); + return Si(t, function(t, e, i, s) { + const n = t.length + , o = []; + let a, r = e, l = t[e]; + for (a = e + 1; a <= i; ++a) { + const i = t[a % n]; + i.skip || i.stop ? l.skip || (s = !1, + o.push({ + start: e % n, + end: (a - 1) % n, + loop: s + }), + e = r = i.stop ? a : null) : (r = a, + l.skip && (e = a)), + l = i + } + return null !== r && o.push({ + start: e % n, + end: r % n, + loop: s + }), + o + }(i, a, r < a ? r + n : r, !!t._fullLoop && 0 === a && r === n - 1), i, e) + } + function Si(t, e, i, s) { + return s && s.setContext && i ? function(t, e, i, s) { + const n = t._chart.getContext() + , o = Pi(t.options) + , {_datasetIndex: a, options: {spanGaps: r}} = t + , l = i.length + , h = []; + let c = o + , d = e[0].start + , u = d; + function f(t, e, s, n) { + const o = r ? -1 : 1; + if (t !== e) { + for (t += l; i[t % l].skip; ) + t -= o; + for (; i[e % l].skip; ) + e += o; + t % l != e % l && (h.push({ + start: t % l, + end: e % l, + loop: s, + style: n + }), + c = n, + d = e % l) + } + } + for (const t of e) { + d = r ? d : t.start; + let e, o = i[d % l]; + for (u = d + 1; u <= t.end; u++) { + const r = i[u % l]; + e = Pi(s.setContext(mi(n, { + type: "segment", + p0: o, + p1: r, + p0DataIndex: (u - 1) % l, + p1DataIndex: u % l, + datasetIndex: a + }))), + Di(e, c) && f(d, u - 1, t.loop, c), + o = r, + c = e + } + d < u - 1 && f(d, u - 1, t.loop, c) + } + return h + }(t, e, i, s) : e + } + function Pi(t) { + return { + backgroundColor: t.backgroundColor, + borderCapStyle: t.borderCapStyle, + borderDash: t.borderDash, + borderDashOffset: t.borderDashOffset, + borderJoinStyle: t.borderJoinStyle, + borderWidth: t.borderWidth, + borderColor: t.borderColor + } + } + function Di(t, e) { + return e && JSON.stringify(t) !== JSON.stringify(e) + } + var Ci = Object.freeze({ + __proto__: null, + easingEffects: ti, + isPatternOrGradient: N, + color: j, + getHoverColor: H, + noop: $, + uid: Y, + isNullOrUndef: U, + isArray: X, + isObject: q, + isFinite: K, + finiteOrDefault: G, + valueOrDefault: Z, + toPercentage: J, + toDimension: Q, + callback: tt, + each: et, + _elementsEqual: it, + clone: st, + _merger: ot, + merge: at, + mergeIf: rt, + _mergerIf: lt, + _deprecated: function(t, e, i, s) { + void 0 !== e && console.warn(t + ': "' + i + '" is deprecated. Please use "' + s + '" instead') + }, + resolveObjectKey: ct, + _capitalize: dt, + defined: ut, + isFunction: ft, + setsEqual: gt, + _isClickEvent: pt, + toFontString: pe, + _measureText: me, + _longestText: be, + _alignPixel: xe, + clearCanvas: _e, + drawPoint: ye, + drawPointLegend: ve, + _isPointInArea: we, + clipArea: Me, + unclipArea: ke, + _steppedLineTo: Se, + _bezierCurveTo: Pe, + renderText: De, + addRoundedRectPath: Oe, + _lookup: vt, + _lookupByKey: wt, + _rlookupByKey: Mt, + _filterBetween: kt, + listenArrayEvents: Pt, + unlistenArrayEvents: Dt, + _arrayUnique: Ct, + _createResolver: Ae, + _attachContext: Te, + _descriptors: Le, + _parseObjectDataRadialScale: He, + splineCurve: Xe, + splineCurveMonotone: qe, + _updateBezierControlPoints: Ge, + _isDomSupported: ie, + _getParentNode: se, + getStyle: ae, + getRelativePosition: he, + getMaximumSize: de, + retinaScale: ue, + supportsEventListenerOptions: fe, + readUsedSize: ge, + fontString: function(t, e, i) { + return e + " " + t + "px " + i + }, + requestAnimFrame: t, + throttled: e, + debounce: i, + _toLeftRightCenter: s, + _alignStartEnd: n, + _textX: o, + _pointInLine: ei, + _steppedInterpolation: ii, + _bezierInterpolation: si, + formatNumber: oi, + toLineHeight: li, + _readValueToProps: hi, + toTRBL: ci, + toTRBLCorners: di, + toPadding: ui, + toFont: fi, + resolve: gi, + _addGrace: pi, + createContext: mi, + PI: Ot, + TAU: At, + PITAU: Tt, + INFINITY: Lt, + RAD_PER_DEG: Rt, + HALF_PI: Et, + QUARTER_PI: It, + TWO_THIRDS_PI: zt, + log10: Ft, + sign: Bt, + niceNum: Vt, + _factorize: Wt, + isNumber: Nt, + almostEquals: jt, + almostWhole: Ht, + _setMinAndMaxByKey: $t, + toRadians: Yt, + toDegrees: Ut, + _decimalPlaces: Xt, + getAngleFromPoint: qt, + distanceBetweenPoints: Kt, + _angleDiff: Gt, + _normalizeAngle: Zt, + _angleBetween: Jt, + _limitValue: Qt, + _int16Range: te, + _isBetween: ee, + getRtlAdapter: bi, + overrideTextDirection: xi, + restoreTextDirection: _i, + _boundSegment: wi, + _boundSegments: Mi, + _computeSegments: ki + }); + function Oi(t, e, i, s) { + const {controller: n, data: o, _sorted: a} = t + , r = n._cachedMeta.iScale; + if (r && e === r.axis && "r" !== e && a && o.length) { + const t = r._reversePixels ? Mt : wt; + if (!s) + return t(o, e, i); + if (n._sharedOptions) { + const s = o[0] + , n = "function" == typeof s.getRange && s.getRange(e); + if (n) { + const s = t(o, e, i - n) + , a = t(o, e, i + n); + return { + lo: s.lo, + hi: a.hi + } + } + } + } + return { + lo: 0, + hi: o.length - 1 + } + } + function Ai(t, e, i, s, n) { + const o = t.getSortedVisibleDatasetMetas() + , a = i[e]; + for (let t = 0, i = o.length; t < i; ++t) { + const {index: i, data: r} = o[t] + , {lo: l, hi: h} = Oi(o[t], e, a, n); + for (let t = l; t <= h; ++t) { + const e = r[t]; + e.skip || s(e, i, t) + } + } + } + function Ti(t, e, i, s, n) { + const o = []; + if (!n && !t.isPointInArea(e)) + return o; + return Ai(t, i, e, (function(i, a, r) { + (n || we(i, t.chartArea, 0)) && i.inRange(e.x, e.y, s) && o.push({ + element: i, + datasetIndex: a, + index: r + }) + } + ), !0), + o + } + function Li(t, e, i, s, n, o) { + let a = []; + const r = function(t) { + const e = -1 !== t.indexOf("x") + , i = -1 !== t.indexOf("y"); + return function(t, s) { + const n = e ? Math.abs(t.x - s.x) : 0 + , o = i ? Math.abs(t.y - s.y) : 0; + return Math.sqrt(Math.pow(n, 2) + Math.pow(o, 2)) + } + }(i); + let l = Number.POSITIVE_INFINITY; + return Ai(t, i, e, (function(i, h, c) { + const d = i.inRange(e.x, e.y, n); + if (s && !d) + return; + const u = i.getCenterPoint(n); + if (!(!!o || t.isPointInArea(u)) && !d) + return; + const f = r(e, u); + f < l ? (a = [{ + element: i, + datasetIndex: h, + index: c + }], + l = f) : f === l && a.push({ + element: i, + datasetIndex: h, + index: c + }) + } + )), + a + } + function Ri(t, e, i, s, n, o) { + return o || t.isPointInArea(e) ? "r" !== i || s ? Li(t, e, i, s, n, o) : function(t, e, i, s) { + let n = []; + return Ai(t, i, e, (function(t, i, o) { + const {startAngle: a, endAngle: r} = t.getProps(["startAngle", "endAngle"], s) + , {angle: l} = qt(t, { + x: e.x, + y: e.y + }); + Jt(l, a, r) && n.push({ + element: t, + datasetIndex: i, + index: o + }) + } + )), + n + }(t, e, i, n) : [] + } + function Ei(t, e, i, s, n) { + const o = [] + , a = "x" === i ? "inXRange" : "inYRange"; + let r = !1; + return Ai(t, i, e, ((t,s,l)=>{ + t[a](e[i], n) && (o.push({ + element: t, + datasetIndex: s, + index: l + }), + r = r || t.inRange(e.x, e.y, n)) + } + )), + s && !r ? [] : o + } + var Ii = { + evaluateInteractionItems: Ai, + modes: { + index(t, e, i, s) { + const n = he(e, t) + , o = i.axis || "x" + , a = i.includeInvisible || !1 + , r = i.intersect ? Ti(t, n, o, s, a) : Ri(t, n, o, !1, s, a) + , l = []; + return r.length ? (t.getSortedVisibleDatasetMetas().forEach((t=>{ + const e = r[0].index + , i = t.data[e]; + i && !i.skip && l.push({ + element: i, + datasetIndex: t.index, + index: e + }) + } + )), + l) : [] + }, + dataset(t, e, i, s) { + const n = he(e, t) + , o = i.axis || "xy" + , a = i.includeInvisible || !1; + let r = i.intersect ? Ti(t, n, o, s, a) : Ri(t, n, o, !1, s, a); + if (r.length > 0) { + const e = r[0].datasetIndex + , i = t.getDatasetMeta(e).data; + r = []; + for (let t = 0; t < i.length; ++t) + r.push({ + element: i[t], + datasetIndex: e, + index: t + }) + } + return r + }, + point: (t,e,i,s)=>Ti(t, he(e, t), i.axis || "xy", s, i.includeInvisible || !1), + nearest(t, e, i, s) { + const n = he(e, t) + , o = i.axis || "xy" + , a = i.includeInvisible || !1; + return Ri(t, n, o, i.intersect, s, a) + }, + x: (t,e,i,s)=>Ei(t, he(e, t), "x", i.intersect, s), + y: (t,e,i,s)=>Ei(t, he(e, t), "y", i.intersect, s) + } + }; + const zi = ["left", "top", "right", "bottom"]; + function Fi(t, e) { + return t.filter((t=>t.pos === e)) + } + function Bi(t, e) { + return t.filter((t=>-1 === zi.indexOf(t.pos) && t.box.axis === e)) + } + function Vi(t, e) { + return t.sort(((t,i)=>{ + const s = e ? i : t + , n = e ? t : i; + return s.weight === n.weight ? s.index - n.index : s.weight - n.weight + } + )) + } + function Wi(t, e) { + const i = function(t) { + const e = {}; + for (const i of t) { + const {stack: t, pos: s, stackWeight: n} = i; + if (!t || !zi.includes(s)) + continue; + const o = e[t] || (e[t] = { + count: 0, + placed: 0, + weight: 0, + size: 0 + }); + o.count++, + o.weight += n + } + return e + }(t) + , {vBoxMaxWidth: s, hBoxMaxHeight: n} = e; + let o, a, r; + for (o = 0, + a = t.length; o < a; ++o) { + r = t[o]; + const {fullSize: a} = r.box + , l = i[r.stack] + , h = l && r.stackWeight / l.weight; + r.horizontal ? (r.width = h ? h * s : a && e.availableWidth, + r.height = n) : (r.width = s, + r.height = h ? h * n : a && e.availableHeight) + } + return i + } + function Ni(t, e, i, s) { + return Math.max(t[i], e[i]) + Math.max(t[s], e[s]) + } + function ji(t, e) { + t.top = Math.max(t.top, e.top), + t.left = Math.max(t.left, e.left), + t.bottom = Math.max(t.bottom, e.bottom), + t.right = Math.max(t.right, e.right) + } + function Hi(t, e, i, s) { + const {pos: n, box: o} = i + , a = t.maxPadding; + if (!q(n)) { + i.size && (t[n] -= i.size); + const e = s[i.stack] || { + size: 0, + count: 1 + }; + e.size = Math.max(e.size, i.horizontal ? o.height : o.width), + i.size = e.size / e.count, + t[n] += i.size + } + o.getPadding && ji(a, o.getPadding()); + const r = Math.max(0, e.outerWidth - Ni(a, t, "left", "right")) + , l = Math.max(0, e.outerHeight - Ni(a, t, "top", "bottom")) + , h = r !== t.w + , c = l !== t.h; + return t.w = r, + t.h = l, + i.horizontal ? { + same: h, + other: c + } : { + same: c, + other: h + } + } + function $i(t, e) { + const i = e.maxPadding; + function s(t) { + const s = { + left: 0, + top: 0, + right: 0, + bottom: 0 + }; + return t.forEach((t=>{ + s[t] = Math.max(e[t], i[t]) + } + )), + s + } + return s(t ? ["left", "right"] : ["top", "bottom"]) + } + function Yi(t, e, i, s) { + const n = []; + let o, a, r, l, h, c; + for (o = 0, + a = t.length, + h = 0; o < a; ++o) { + r = t[o], + l = r.box, + l.update(r.width || e.w, r.height || e.h, $i(r.horizontal, e)); + const {same: a, other: d} = Hi(e, i, r, s); + h |= a && n.length, + c = c || d, + l.fullSize || n.push(r) + } + return h && Yi(n, e, i, s) || c + } + function Ui(t, e, i, s, n) { + t.top = i, + t.left = e, + t.right = e + s, + t.bottom = i + n, + t.width = s, + t.height = n + } + function Xi(t, e, i, s) { + const n = i.padding; + let {x: o, y: a} = e; + for (const r of t) { + const t = r.box + , l = s[r.stack] || { + count: 1, + placed: 0, + weight: 1 + } + , h = r.stackWeight / l.weight || 1; + if (r.horizontal) { + const s = e.w * h + , o = l.size || t.height; + ut(l.start) && (a = l.start), + t.fullSize ? Ui(t, n.left, a, i.outerWidth - n.right - n.left, o) : Ui(t, e.left + l.placed, a, s, o), + l.start = a, + l.placed += s, + a = t.bottom + } else { + const s = e.h * h + , a = l.size || t.width; + ut(l.start) && (o = l.start), + t.fullSize ? Ui(t, o, n.top, a, i.outerHeight - n.bottom - n.top) : Ui(t, o, e.top + l.placed, a, s), + l.start = o, + l.placed += s, + o = t.right + } + } + e.x = o, + e.y = a + } + yt.set("layout", { + autoPadding: !0, + padding: { + top: 0, + right: 0, + bottom: 0, + left: 0 + } + }); + var qi = { + addBox(t, e) { + t.boxes || (t.boxes = []), + e.fullSize = e.fullSize || !1, + e.position = e.position || "top", + e.weight = e.weight || 0, + e._layers = e._layers || function() { + return [{ + z: 0, + draw(t) { + e.draw(t) + } + }] + } + , + t.boxes.push(e) + }, + removeBox(t, e) { + const i = t.boxes ? t.boxes.indexOf(e) : -1; + -1 !== i && t.boxes.splice(i, 1) + }, + configure(t, e, i) { + e.fullSize = i.fullSize, + e.position = i.position, + e.weight = i.weight + }, + update(t, e, i, s) { + if (!t) + return; + const n = ui(t.options.layout.padding) + , o = Math.max(e - n.width, 0) + , a = Math.max(i - n.height, 0) + , r = function(t) { + const e = function(t) { + const e = []; + let i, s, n, o, a, r; + for (i = 0, + s = (t || []).length; i < s; ++i) + n = t[i], + ({position: o, options: {stack: a, stackWeight: r=1}} = n), + e.push({ + index: i, + box: n, + pos: o, + horizontal: n.isHorizontal(), + weight: n.weight, + stack: a && o + a, + stackWeight: r + }); + return e + }(t) + , i = Vi(e.filter((t=>t.box.fullSize)), !0) + , s = Vi(Fi(e, "left"), !0) + , n = Vi(Fi(e, "right")) + , o = Vi(Fi(e, "top"), !0) + , a = Vi(Fi(e, "bottom")) + , r = Bi(e, "x") + , l = Bi(e, "y"); + return { + fullSize: i, + leftAndTop: s.concat(o), + rightAndBottom: n.concat(l).concat(a).concat(r), + chartArea: Fi(e, "chartArea"), + vertical: s.concat(n).concat(l), + horizontal: o.concat(a).concat(r) + } + }(t.boxes) + , l = r.vertical + , h = r.horizontal; + et(t.boxes, (t=>{ + "function" == typeof t.beforeLayout && t.beforeLayout() + } + )); + const c = l.reduce(((t,e)=>e.box.options && !1 === e.box.options.display ? t : t + 1), 0) || 1 + , d = Object.freeze({ + outerWidth: e, + outerHeight: i, + padding: n, + availableWidth: o, + availableHeight: a, + vBoxMaxWidth: o / 2 / c, + hBoxMaxHeight: a / 2 + }) + , u = Object.assign({}, n); + ji(u, ui(s)); + const f = Object.assign({ + maxPadding: u, + w: o, + h: a, + x: n.left, + y: n.top + }, n) + , g = Wi(l.concat(h), d); + Yi(r.fullSize, f, d, g), + Yi(l, f, d, g), + Yi(h, f, d, g) && Yi(l, f, d, g), + function(t) { + const e = t.maxPadding; + function i(i) { + const s = Math.max(e[i] - t[i], 0); + return t[i] += s, + s + } + t.y += i("top"), + t.x += i("left"), + i("right"), + i("bottom") + }(f), + Xi(r.leftAndTop, f, d, g), + f.x += f.w, + f.y += f.h, + Xi(r.rightAndBottom, f, d, g), + t.chartArea = { + left: f.left, + top: f.top, + right: f.left + f.w, + bottom: f.top + f.h, + height: f.h, + width: f.w + }, + et(r.chartArea, (e=>{ + const i = e.box; + Object.assign(i, t.chartArea), + i.update(f.w, f.h, { + left: 0, + top: 0, + right: 0, + bottom: 0 + }) + } + )) + } + }; + class Ki { + acquireContext(t, e) {} + releaseContext(t) { + return !1 + } + addEventListener(t, e, i) {} + removeEventListener(t, e, i) {} + getDevicePixelRatio() { + return 1 + } + getMaximumSize(t, e, i, s) { + return e = Math.max(0, e || t.width), + i = i || t.height, + { + width: e, + height: Math.max(0, s ? Math.floor(e / s) : i) + } + } + isAttached(t) { + return !0 + } + updateConfig(t) {} + } + class Gi extends Ki { + acquireContext(t) { + return t && t.getContext && t.getContext("2d") || null + } + updateConfig(t) { + t.options.animation = !1 + } + } + const Zi = { + touchstart: "mousedown", + touchmove: "mousemove", + touchend: "mouseup", + pointerenter: "mouseenter", + pointerdown: "mousedown", + pointermove: "mousemove", + pointerup: "mouseup", + pointerleave: "mouseout", + pointerout: "mouseout" + } + , Ji = t=>null === t || "" === t; + const Qi = !!fe && { + passive: !0 + }; + function ts(t, e, i) { + t.canvas.removeEventListener(e, i, Qi) + } + function es(t, e) { + for (const i of t) + if (i === e || i.contains(e)) + return !0 + } + function is(t, e, i) { + const s = t.canvas + , n = new MutationObserver((t=>{ + let e = !1; + for (const i of t) + e = e || es(i.addedNodes, s), + e = e && !es(i.removedNodes, s); + e && i() + } + )); + return n.observe(document, { + childList: !0, + subtree: !0 + }), + n + } + function ss(t, e, i) { + const s = t.canvas + , n = new MutationObserver((t=>{ + let e = !1; + for (const i of t) + e = e || es(i.removedNodes, s), + e = e && !es(i.addedNodes, s); + e && i() + } + )); + return n.observe(document, { + childList: !0, + subtree: !0 + }), + n + } + const ns = new Map; + let os = 0; + function as() { + const t = window.devicePixelRatio; + t !== os && (os = t, + ns.forEach(((e,i)=>{ + i.currentDevicePixelRatio !== t && e() + } + ))) + } + function rs(t, i, s) { + const n = t.canvas + , o = n && se(n); + if (!o) + return; + const a = e(((t,e)=>{ + const i = o.clientWidth; + s(t, e), + i < o.clientWidth && s() + } + ), window) + , r = new ResizeObserver((t=>{ + const e = t[0] + , i = e.contentRect.width + , s = e.contentRect.height; + 0 === i && 0 === s || a(i, s) + } + )); + return r.observe(o), + function(t, e) { + ns.size || window.addEventListener("resize", as), + ns.set(t, e) + }(t, a), + r + } + function ls(t, e, i) { + i && i.disconnect(), + "resize" === e && function(t) { + ns.delete(t), + ns.size || window.removeEventListener("resize", as) + }(t) + } + function hs(t, i, s) { + const n = t.canvas + , o = e((e=>{ + null !== t.ctx && s(function(t, e) { + const i = Zi[t.type] || t.type + , {x: s, y: n} = he(t, e); + return { + type: i, + chart: e, + native: t, + x: void 0 !== s ? s : null, + y: void 0 !== n ? n : null + } + }(e, t)) + } + ), t, (t=>{ + const e = t[0]; + return [e, e.offsetX, e.offsetY] + } + )); + return function(t, e, i) { + t.addEventListener(e, i, Qi) + }(n, i, o), + o + } + class cs extends Ki { + acquireContext(t, e) { + const i = t && t.getContext && t.getContext("2d"); + return i && i.canvas === t ? (function(t, e) { + const i = t.style + , s = t.getAttribute("height") + , n = t.getAttribute("width"); + if (t.$chartjs = { + initial: { + height: s, + width: n, + style: { + display: i.display, + height: i.height, + width: i.width + } + } + }, + i.display = i.display || "block", + i.boxSizing = i.boxSizing || "border-box", + Ji(n)) { + const e = ge(t, "width"); + void 0 !== e && (t.width = e) + } + if (Ji(s)) + if ("" === t.style.height) + t.height = t.width / (e || 2); + else { + const e = ge(t, "height"); + void 0 !== e && (t.height = e) + } + }(t, e), + i) : null + } + releaseContext(t) { + const e = t.canvas; + if (!e.$chartjs) + return !1; + const i = e.$chartjs.initial; + ["height", "width"].forEach((t=>{ + const s = i[t]; + U(s) ? e.removeAttribute(t) : e.setAttribute(t, s) + } + )); + const s = i.style || {}; + return Object.keys(s).forEach((t=>{ + e.style[t] = s[t] + } + )), + e.width = e.width, + delete e.$chartjs, + !0 + } + addEventListener(t, e, i) { + this.removeEventListener(t, e); + const s = t.$proxies || (t.$proxies = {}) + , n = { + attach: is, + detach: ss, + resize: rs + }[e] || hs; + s[e] = n(t, e, i) + } + removeEventListener(t, e) { + const i = t.$proxies || (t.$proxies = {}) + , s = i[e]; + if (!s) + return; + ({ + attach: ls, + detach: ls, + resize: ls + }[e] || ts)(t, e, s), + i[e] = void 0 + } + getDevicePixelRatio() { + return window.devicePixelRatio + } + getMaximumSize(t, e, i, s) { + return de(t, e, i, s) + } + isAttached(t) { + const e = se(t); + return !(!e || !e.isConnected) + } + } + function ds(t) { + return !ie() || "undefined" != typeof OffscreenCanvas && t instanceof OffscreenCanvas ? Gi : cs + } + var us = Object.freeze({ + __proto__: null, + _detectPlatform: ds, + BasePlatform: Ki, + BasicPlatform: Gi, + DomPlatform: cs + }); + const fs = "transparent" + , gs = { + boolean: (t,e,i)=>i > .5 ? e : t, + color(t, e, i) { + const s = j(t || fs) + , n = s.valid && j(e || fs); + return n && n.valid ? n.mix(s, i).hexString() : e + }, + number: (t,e,i)=>t + (e - t) * i + }; + class ps { + constructor(t, e, i, s) { + const n = e[i]; + s = gi([t.to, s, n, t.from]); + const o = gi([t.from, n, s]); + this._active = !0, + this._fn = t.fn || gs[t.type || typeof o], + this._easing = ti[t.easing] || ti.linear, + this._start = Math.floor(Date.now() + (t.delay || 0)), + this._duration = this._total = Math.floor(t.duration), + this._loop = !!t.loop, + this._target = e, + this._prop = i, + this._from = o, + this._to = s, + this._promises = void 0 + } + active() { + return this._active + } + update(t, e, i) { + if (this._active) { + this._notify(!1); + const s = this._target[this._prop] + , n = i - this._start + , o = this._duration - n; + this._start = i, + this._duration = Math.floor(Math.max(o, t.duration)), + this._total += n, + this._loop = !!t.loop, + this._to = gi([t.to, e, s, t.from]), + this._from = gi([t.from, s, e]) + } + } + cancel() { + this._active && (this.tick(Date.now()), + this._active = !1, + this._notify(!1)) + } + tick(t) { + const e = t - this._start + , i = this._duration + , s = this._prop + , n = this._from + , o = this._loop + , a = this._to; + let r; + if (this._active = n !== a && (o || e < i), + !this._active) + return this._target[s] = a, + void this._notify(!0); + e < 0 ? this._target[s] = n : (r = e / i % 2, + r = o && r > 1 ? 2 - r : r, + r = this._easing(Math.min(1, Math.max(0, r))), + this._target[s] = this._fn(n, a, r)) + } + wait() { + const t = this._promises || (this._promises = []); + return new Promise(((e,i)=>{ + t.push({ + res: e, + rej: i + }) + } + )) + } + _notify(t) { + const e = t ? "res" : "rej" + , i = this._promises || []; + for (let t = 0; t < i.length; t++) + i[t][e]() + } + } + yt.set("animation", { + delay: void 0, + duration: 1e3, + easing: "easeOutQuart", + fn: void 0, + from: void 0, + loop: void 0, + to: void 0, + type: void 0 + }); + const ms = Object.keys(yt.animation); + yt.describe("animation", { + _fallback: !1, + _indexable: !1, + _scriptable: t=>"onProgress" !== t && "onComplete" !== t && "fn" !== t + }), + yt.set("animations", { + colors: { + type: "color", + properties: ["color", "borderColor", "backgroundColor"] + }, + numbers: { + type: "number", + properties: ["x", "y", "borderWidth", "radius", "tension"] + } + }), + yt.describe("animations", { + _fallback: "animation" + }), + yt.set("transitions", { + active: { + animation: { + duration: 400 + } + }, + resize: { + animation: { + duration: 0 + } + }, + show: { + animations: { + colors: { + from: "transparent" + }, + visible: { + type: "boolean", + duration: 0 + } + } + }, + hide: { + animations: { + colors: { + to: "transparent" + }, + visible: { + type: "boolean", + easing: "linear", + fn: t=>0 | t + } + } + } + }); + class bs { + constructor(t, e) { + this._chart = t, + this._properties = new Map, + this.configure(e) + } + configure(t) { + if (!q(t)) + return; + const e = this._properties; + Object.getOwnPropertyNames(t).forEach((i=>{ + const s = t[i]; + if (!q(s)) + return; + const n = {}; + for (const t of ms) + n[t] = s[t]; + (X(s.properties) && s.properties || [i]).forEach((t=>{ + t !== i && e.has(t) || e.set(t, n) + } + )) + } + )) + } + _animateOptions(t, e) { + const i = e.options + , s = function(t, e) { + if (!e) + return; + let i = t.options; + if (!i) + return void (t.options = e); + i.$shared && (t.options = i = Object.assign({}, i, { + $shared: !1, + $animations: {} + })); + return i + }(t, i); + if (!s) + return []; + const n = this._createAnimations(s, i); + return i.$shared && function(t, e) { + const i = [] + , s = Object.keys(e); + for (let e = 0; e < s.length; e++) { + const n = t[s[e]]; + n && n.active() && i.push(n.wait()) + } + return Promise.all(i) + }(t.options.$animations, i).then((()=>{ + t.options = i + } + ), (()=>{} + )), + n + } + _createAnimations(t, e) { + const i = this._properties + , s = [] + , n = t.$animations || (t.$animations = {}) + , o = Object.keys(e) + , a = Date.now(); + let r; + for (r = o.length - 1; r >= 0; --r) { + const l = o[r]; + if ("$" === l.charAt(0)) + continue; + if ("options" === l) { + s.push(...this._animateOptions(t, e)); + continue + } + const h = e[l]; + let c = n[l]; + const d = i.get(l); + if (c) { + if (d && c.active()) { + c.update(d, h, a); + continue + } + c.cancel() + } + d && d.duration ? (n[l] = c = new ps(d,t,l,h), + s.push(c)) : t[l] = h + } + return s + } + update(t, e) { + if (0 === this._properties.size) + return void Object.assign(t, e); + const i = this._createAnimations(t, e); + return i.length ? (a.add(this._chart, i), + !0) : void 0 + } + } + function xs(t, e) { + const i = t && t.options || {} + , s = i.reverse + , n = void 0 === i.min ? e : 0 + , o = void 0 === i.max ? e : 0; + return { + start: s ? o : n, + end: s ? n : o + } + } + function _s(t, e) { + const i = [] + , s = t._getSortedDatasetMetas(e); + let n, o; + for (n = 0, + o = s.length; n < o; ++n) + i.push(s[n].index); + return i + } + function ys(t, e, i, s={}) { + const n = t.keys + , o = "single" === s.mode; + let a, r, l, h; + if (null !== e) { + for (a = 0, + r = n.length; a < r; ++a) { + if (l = +n[a], + l === i) { + if (s.all) + continue; + break + } + h = t.values[l], + K(h) && (o || 0 === e || Bt(e) === Bt(h)) && (e += h) + } + return e + } + } + function vs(t, e) { + const i = t && t.options.stacked; + return i || void 0 === i && void 0 !== e.stack + } + function ws(t, e, i) { + const s = t[e] || (t[e] = {}); + return s[i] || (s[i] = {}) + } + function Ms(t, e, i, s) { + for (const n of e.getMatchingVisibleMetas(s).reverse()) { + const e = t[n.index]; + if (i && e > 0 || !i && e < 0) + return n.index + } + return null + } + function ks(t, e) { + const {chart: i, _cachedMeta: s} = t + , n = i._stacks || (i._stacks = {}) + , {iScale: o, vScale: a, index: r} = s + , l = o.axis + , h = a.axis + , c = function(t, e, i) { + return `${t.id}.${e.id}.${i.stack || i.type}` + }(o, a, s) + , d = e.length; + let u; + for (let t = 0; t < d; ++t) { + const i = e[t] + , {[l]: o, [h]: d} = i; + u = (i._stacks || (i._stacks = {}))[h] = ws(n, c, o), + u[r] = d, + u._top = Ms(u, a, !0, s.type), + u._bottom = Ms(u, a, !1, s.type) + } + } + function Ss(t, e) { + const i = t.scales; + return Object.keys(i).filter((t=>i[t].axis === e)).shift() + } + function Ps(t, e) { + const i = t.controller.index + , s = t.vScale && t.vScale.axis; + if (s) { + e = e || t._parsed; + for (const t of e) { + const e = t._stacks; + if (!e || void 0 === e[s] || void 0 === e[s][i]) + return; + delete e[s][i] + } + } + } + const Ds = t=>"reset" === t || "none" === t + , Cs = (t,e)=>e ? t : Object.assign({}, t); + class Os { + constructor(t, e) { + this.chart = t, + this._ctx = t.ctx, + this.index = e, + this._cachedDataOpts = {}, + this._cachedMeta = this.getMeta(), + this._type = this._cachedMeta.type, + this.options = void 0, + this._parsing = !1, + this._data = void 0, + this._objectData = void 0, + this._sharedOptions = void 0, + this._drawStart = void 0, + this._drawCount = void 0, + this.enableOptionSharing = !1, + this.supportsDecimation = !1, + this.$context = void 0, + this._syncList = [], + this.initialize() + } + initialize() { + const t = this._cachedMeta; + this.configure(), + this.linkScales(), + t._stacked = vs(t.vScale, t), + this.addElements() + } + updateIndex(t) { + this.index !== t && Ps(this._cachedMeta), + this.index = t + } + linkScales() { + const t = this.chart + , e = this._cachedMeta + , i = this.getDataset() + , s = (t,e,i,s)=>"x" === t ? e : "r" === t ? s : i + , n = e.xAxisID = Z(i.xAxisID, Ss(t, "x")) + , o = e.yAxisID = Z(i.yAxisID, Ss(t, "y")) + , a = e.rAxisID = Z(i.rAxisID, Ss(t, "r")) + , r = e.indexAxis + , l = e.iAxisID = s(r, n, o, a) + , h = e.vAxisID = s(r, o, n, a); + e.xScale = this.getScaleForId(n), + e.yScale = this.getScaleForId(o), + e.rScale = this.getScaleForId(a), + e.iScale = this.getScaleForId(l), + e.vScale = this.getScaleForId(h) + } + getDataset() { + return this.chart.data.datasets[this.index] + } + getMeta() { + return this.chart.getDatasetMeta(this.index) + } + getScaleForId(t) { + return this.chart.scales[t] + } + _getOtherScale(t) { + const e = this._cachedMeta; + return t === e.iScale ? e.vScale : e.iScale + } + reset() { + this._update("reset") + } + _destroy() { + const t = this._cachedMeta; + this._data && Dt(this._data, this), + t._stacked && Ps(t) + } + _dataCheck() { + const t = this.getDataset() + , e = t.data || (t.data = []) + , i = this._data; + if (q(e)) + this._data = function(t) { + const e = Object.keys(t) + , i = new Array(e.length); + let s, n, o; + for (s = 0, + n = e.length; s < n; ++s) + o = e[s], + i[s] = { + x: o, + y: t[o] + }; + return i + }(e); + else if (i !== e) { + if (i) { + Dt(i, this); + const t = this._cachedMeta; + Ps(t), + t._parsed = [] + } + e && Object.isExtensible(e) && Pt(e, this), + this._syncList = [], + this._data = e + } + } + addElements() { + const t = this._cachedMeta; + this._dataCheck(), + this.datasetElementType && (t.dataset = new this.datasetElementType) + } + buildOrUpdateElements(t) { + const e = this._cachedMeta + , i = this.getDataset(); + let s = !1; + this._dataCheck(); + const n = e._stacked; + e._stacked = vs(e.vScale, e), + e.stack !== i.stack && (s = !0, + Ps(e), + e.stack = i.stack), + this._resyncElements(t), + (s || n !== e._stacked) && ks(this, e._parsed) + } + configure() { + const t = this.chart.config + , e = t.datasetScopeKeys(this._type) + , i = t.getOptionScopes(this.getDataset(), e, !0); + this.options = t.createResolver(i, this.getContext()), + this._parsing = this.options.parsing, + this._cachedDataOpts = {} + } + parse(t, e) { + const {_cachedMeta: i, _data: s} = this + , {iScale: n, _stacked: o} = i + , a = n.axis; + let r, l, h, c = 0 === t && e === s.length || i._sorted, d = t > 0 && i._parsed[t - 1]; + if (!1 === this._parsing) + i._parsed = s, + i._sorted = !0, + h = s; + else { + h = X(s[t]) ? this.parseArrayData(i, s, t, e) : q(s[t]) ? this.parseObjectData(i, s, t, e) : this.parsePrimitiveData(i, s, t, e); + const n = ()=>null === l[a] || d && l[a] < d[a]; + for (r = 0; r < e; ++r) + i._parsed[r + t] = l = h[r], + c && (n() && (c = !1), + d = l); + i._sorted = c + } + o && ks(this, h) + } + parsePrimitiveData(t, e, i, s) { + const {iScale: n, vScale: o} = t + , a = n.axis + , r = o.axis + , l = n.getLabels() + , h = n === o + , c = new Array(s); + let d, u, f; + for (d = 0, + u = s; d < u; ++d) + f = d + i, + c[d] = { + [a]: h || n.parse(l[f], f), + [r]: o.parse(e[f], f) + }; + return c + } + parseArrayData(t, e, i, s) { + const {xScale: n, yScale: o} = t + , a = new Array(s); + let r, l, h, c; + for (r = 0, + l = s; r < l; ++r) + h = r + i, + c = e[h], + a[r] = { + x: n.parse(c[0], h), + y: o.parse(c[1], h) + }; + return a + } + parseObjectData(t, e, i, s) { + const {xScale: n, yScale: o} = t + , {xAxisKey: a="x", yAxisKey: r="y"} = this._parsing + , l = new Array(s); + let h, c, d, u; + for (h = 0, + c = s; h < c; ++h) + d = h + i, + u = e[d], + l[h] = { + x: n.parse(ct(u, a), d), + y: o.parse(ct(u, r), d) + }; + return l + } + getParsed(t) { + return this._cachedMeta._parsed[t] + } + getDataElement(t) { + return this._cachedMeta.data[t] + } + applyStack(t, e, i) { + const s = this.chart + , n = this._cachedMeta + , o = e[t.axis]; + return ys({ + keys: _s(s, !0), + values: e._stacks[t.axis] + }, o, n.index, { + mode: i + }) + } + updateRangeFromParsed(t, e, i, s) { + const n = i[e.axis]; + let o = null === n ? NaN : n; + const a = s && i._stacks[e.axis]; + s && a && (s.values = a, + o = ys(s, n, this._cachedMeta.index)), + t.min = Math.min(t.min, o), + t.max = Math.max(t.max, o) + } + getMinMax(t, e) { + const i = this._cachedMeta + , s = i._parsed + , n = i._sorted && t === i.iScale + , o = s.length + , a = this._getOtherScale(t) + , r = ((t,e,i)=>t && !e.hidden && e._stacked && { + keys: _s(i, !0), + values: null + })(e, i, this.chart) + , l = { + min: Number.POSITIVE_INFINITY, + max: Number.NEGATIVE_INFINITY + } + , {min: h, max: c} = function(t) { + const {min: e, max: i, minDefined: s, maxDefined: n} = t.getUserBounds(); + return { + min: s ? e : Number.NEGATIVE_INFINITY, + max: n ? i : Number.POSITIVE_INFINITY + } + }(a); + let d, u; + function f() { + u = s[d]; + const e = u[a.axis]; + return !K(u[t.axis]) || h > e || c < e + } + for (d = 0; d < o && (f() || (this.updateRangeFromParsed(l, t, u, r), + !n)); ++d) + ; + if (n) + for (d = o - 1; d >= 0; --d) + if (!f()) { + this.updateRangeFromParsed(l, t, u, r); + break + } + return l + } + getAllParsedValues(t) { + const e = this._cachedMeta._parsed + , i = []; + let s, n, o; + for (s = 0, + n = e.length; s < n; ++s) + o = e[s][t.axis], + K(o) && i.push(o); + return i + } + getMaxOverflow() { + return !1 + } + getLabelAndValue(t) { + const e = this._cachedMeta + , i = e.iScale + , s = e.vScale + , n = this.getParsed(t); + return { + label: i ? "" + i.getLabelForValue(n[i.axis]) : "", + value: s ? "" + s.getLabelForValue(n[s.axis]) : "" + } + } + _update(t) { + const e = this._cachedMeta; + this.update(t || "default"), + e._clip = function(t) { + let e, i, s, n; + return q(t) ? (e = t.top, + i = t.right, + s = t.bottom, + n = t.left) : e = i = s = n = t, + { + top: e, + right: i, + bottom: s, + left: n, + disabled: !1 === t + } + }(Z(this.options.clip, function(t, e, i) { + if (!1 === i) + return !1; + const s = xs(t, i) + , n = xs(e, i); + return { + top: n.end, + right: s.end, + bottom: n.start, + left: s.start + } + }(e.xScale, e.yScale, this.getMaxOverflow()))) + } + update(t) {} + draw() { + const t = this._ctx + , e = this.chart + , i = this._cachedMeta + , s = i.data || [] + , n = e.chartArea + , o = [] + , a = this._drawStart || 0 + , r = this._drawCount || s.length - a + , l = this.options.drawActiveElementsOnTop; + let h; + for (i.dataset && i.dataset.draw(t, n, a, r), + h = a; h < a + r; ++h) { + const e = s[h]; + e.hidden || (e.active && l ? o.push(e) : e.draw(t, n)) + } + for (h = 0; h < o.length; ++h) + o[h].draw(t, n) + } + getStyle(t, e) { + const i = e ? "active" : "default"; + return void 0 === t && this._cachedMeta.dataset ? this.resolveDatasetElementOptions(i) : this.resolveDataElementOptions(t || 0, i) + } + getContext(t, e, i) { + const s = this.getDataset(); + let n; + if (t >= 0 && t < this._cachedMeta.data.length) { + const e = this._cachedMeta.data[t]; + n = e.$context || (e.$context = function(t, e, i) { + return mi(t, { + active: !1, + dataIndex: e, + parsed: void 0, + raw: void 0, + element: i, + index: e, + mode: "default", + type: "data" + }) + }(this.getContext(), t, e)), + n.parsed = this.getParsed(t), + n.raw = s.data[t], + n.index = n.dataIndex = t + } else + n = this.$context || (this.$context = function(t, e) { + return mi(t, { + active: !1, + dataset: void 0, + datasetIndex: e, + index: e, + mode: "default", + type: "dataset" + }) + }(this.chart.getContext(), this.index)), + n.dataset = s, + n.index = n.datasetIndex = this.index; + return n.active = !!e, + n.mode = i, + n + } + resolveDatasetElementOptions(t) { + return this._resolveElementOptions(this.datasetElementType.id, t) + } + resolveDataElementOptions(t, e) { + return this._resolveElementOptions(this.dataElementType.id, e, t) + } + _resolveElementOptions(t, e="default", i) { + const s = "active" === e + , n = this._cachedDataOpts + , o = t + "-" + e + , a = n[o] + , r = this.enableOptionSharing && ut(i); + if (a) + return Cs(a, r); + const l = this.chart.config + , h = l.datasetElementScopeKeys(this._type, t) + , c = s ? [`${t}Hover`, "hover", t, ""] : [t, ""] + , d = l.getOptionScopes(this.getDataset(), h) + , u = Object.keys(yt.elements[t]) + , f = l.resolveNamedOptions(d, u, (()=>this.getContext(i, s)), c); + return f.$shared && (f.$shared = r, + n[o] = Object.freeze(Cs(f, r))), + f + } + _resolveAnimations(t, e, i) { + const s = this.chart + , n = this._cachedDataOpts + , o = `animation-${e}` + , a = n[o]; + if (a) + return a; + let r; + if (!1 !== s.options.animation) { + const s = this.chart.config + , n = s.datasetAnimationScopeKeys(this._type, e) + , o = s.getOptionScopes(this.getDataset(), n); + r = s.createResolver(o, this.getContext(t, i, e)) + } + const l = new bs(s,r && r.animations); + return r && r._cacheable && (n[o] = Object.freeze(l)), + l + } + getSharedOptions(t) { + if (t.$shared) + return this._sharedOptions || (this._sharedOptions = Object.assign({}, t)) + } + includeOptions(t, e) { + return !e || Ds(t) || this.chart._animationsDisabled + } + _getSharedOptions(t, e) { + const i = this.resolveDataElementOptions(t, e) + , s = this._sharedOptions + , n = this.getSharedOptions(i) + , o = this.includeOptions(e, n) || n !== s; + return this.updateSharedOptions(n, e, i), + { + sharedOptions: n, + includeOptions: o + } + } + updateElement(t, e, i, s) { + Ds(s) ? Object.assign(t, i) : this._resolveAnimations(e, s).update(t, i) + } + updateSharedOptions(t, e, i) { + t && !Ds(e) && this._resolveAnimations(void 0, e).update(t, i) + } + _setStyle(t, e, i, s) { + t.active = s; + const n = this.getStyle(e, s); + this._resolveAnimations(e, i, s).update(t, { + options: !s && this.getSharedOptions(n) || n + }) + } + removeHoverStyle(t, e, i) { + this._setStyle(t, i, "active", !1) + } + setHoverStyle(t, e, i) { + this._setStyle(t, i, "active", !0) + } + _removeDatasetHoverStyle() { + const t = this._cachedMeta.dataset; + t && this._setStyle(t, void 0, "active", !1) + } + _setDatasetHoverStyle() { + const t = this._cachedMeta.dataset; + t && this._setStyle(t, void 0, "active", !0) + } + _resyncElements(t) { + const e = this._data + , i = this._cachedMeta.data; + for (const [t,e,i] of this._syncList) + this[t](e, i); + this._syncList = []; + const s = i.length + , n = e.length + , o = Math.min(n, s); + o && this.parse(0, o), + n > s ? this._insertElements(s, n - s, t) : n < s && this._removeElements(n, s - n) + } + _insertElements(t, e, i=!0) { + const s = this._cachedMeta + , n = s.data + , o = t + e; + let a; + const r = t=>{ + for (t.length += e, + a = t.length - 1; a >= o; a--) + t[a] = t[a - e] + } + ; + for (r(n), + a = t; a < o; ++a) + n[a] = new this.dataElementType; + this._parsing && r(s._parsed), + this.parse(t, e), + i && this.updateElements(n, t, e, "reset") + } + updateElements(t, e, i, s) {} + _removeElements(t, e) { + const i = this._cachedMeta; + if (this._parsing) { + const s = i._parsed.splice(t, e); + i._stacked && Ps(i, s) + } + i.data.splice(t, e) + } + _sync(t) { + if (this._parsing) + this._syncList.push(t); + else { + const [e,i,s] = t; + this[e](i, s) + } + this.chart._dataChanges.push([this.index, ...t]) + } + _onDataPush() { + const t = arguments.length; + this._sync(["_insertElements", this.getDataset().data.length - t, t]) + } + _onDataPop() { + this._sync(["_removeElements", this._cachedMeta.data.length - 1, 1]) + } + _onDataShift() { + this._sync(["_removeElements", 0, 1]) + } + _onDataSplice(t, e) { + e && this._sync(["_removeElements", t, e]); + const i = arguments.length - 2; + i && this._sync(["_insertElements", t, i]) + } + _onDataUnshift() { + this._sync(["_insertElements", 0, arguments.length]) + } + } + Os.defaults = {}, + Os.prototype.datasetElementType = null, + Os.prototype.dataElementType = null; + class As { + constructor() { + this.x = void 0, + this.y = void 0, + this.active = !1, + this.options = void 0, + this.$animations = void 0 + } + tooltipPosition(t) { + const {x: e, y: i} = this.getProps(["x", "y"], t); + return { + x: e, + y: i + } + } + hasValue() { + return Nt(this.x) && Nt(this.y) + } + getProps(t, e) { + const i = this.$animations; + if (!e || !i) + return this; + const s = {}; + return t.forEach((t=>{ + s[t] = i[t] && i[t].active() ? i[t]._to : this[t] + } + )), + s + } + } + As.defaults = {}, + As.defaultRoutes = void 0; + const Ts = { + values: t=>X(t) ? t : "" + t, + numeric(t, e, i) { + if (0 === t) + return "0"; + const s = this.chart.options.locale; + let n, o = t; + if (i.length > 1) { + const e = Math.max(Math.abs(i[0].value), Math.abs(i[i.length - 1].value)); + (e < 1e-4 || e > 1e15) && (n = "scientific"), + o = function(t, e) { + let i = e.length > 3 ? e[2].value - e[1].value : e[1].value - e[0].value; + Math.abs(i) >= 1 && t !== Math.floor(t) && (i = t - Math.floor(t)); + return i + }(t, i) + } + const a = Ft(Math.abs(o)) + , r = Math.max(Math.min(-1 * Math.floor(a), 20), 0) + , l = { + notation: n, + minimumFractionDigits: r, + maximumFractionDigits: r + }; + return Object.assign(l, this.options.ticks.format), + oi(t, s, l) + }, + logarithmic(t, e, i) { + if (0 === t) + return "0"; + const s = t / Math.pow(10, Math.floor(Ft(t))); + return 1 === s || 2 === s || 5 === s ? Ts.numeric.call(this, t, e, i) : "" + } + }; + var Ls = { + formatters: Ts + }; + function Rs(t, e) { + const i = t.options.ticks + , s = i.maxTicksLimit || function(t) { + const e = t.options.offset + , i = t._tickSize() + , s = t._length / i + (e ? 0 : 1) + , n = t._maxLength / i; + return Math.floor(Math.min(s, n)) + }(t) + , n = i.major.enabled ? function(t) { + const e = []; + let i, s; + for (i = 0, + s = t.length; i < s; i++) + t[i].major && e.push(i); + return e + }(e) : [] + , o = n.length + , a = n[0] + , r = n[o - 1] + , l = []; + if (o > s) + return function(t, e, i, s) { + let n, o = 0, a = i[0]; + for (s = Math.ceil(s), + n = 0; n < t.length; n++) + n === a && (e.push(t[n]), + o++, + a = i[o * s]) + }(e, l, n, o / s), + l; + const h = function(t, e, i) { + const s = function(t) { + const e = t.length; + let i, s; + if (e < 2) + return !1; + for (s = t[0], + i = 1; i < e; ++i) + if (t[i] - t[i - 1] !== s) + return !1; + return s + }(t) + , n = e.length / i; + if (!s) + return Math.max(n, 1); + const o = Wt(s); + for (let t = 0, e = o.length - 1; t < e; t++) { + const e = o[t]; + if (e > n) + return e + } + return Math.max(n, 1) + }(n, e, s); + if (o > 0) { + let t, i; + const s = o > 1 ? Math.round((r - a) / (o - 1)) : null; + for (Es(e, l, h, U(s) ? 0 : a - s, a), + t = 0, + i = o - 1; t < i; t++) + Es(e, l, h, n[t], n[t + 1]); + return Es(e, l, h, r, U(s) ? e.length : r + s), + l + } + return Es(e, l, h), + l + } + function Es(t, e, i, s, n) { + const o = Z(s, 0) + , a = Math.min(Z(n, t.length), t.length); + let r, l, h, c = 0; + for (i = Math.ceil(i), + n && (r = n - s, + i = r / Math.floor(r / i)), + h = o; h < 0; ) + c++, + h = Math.round(o + c * i); + for (l = Math.max(o, 0); l < a; l++) + l === h && (e.push(t[l]), + c++, + h = Math.round(o + c * i)) + } + yt.set("scale", { + display: !0, + offset: !1, + reverse: !1, + beginAtZero: !1, + bounds: "ticks", + grace: 0, + grid: { + display: !0, + lineWidth: 1, + drawBorder: !0, + drawOnChartArea: !0, + drawTicks: !0, + tickLength: 8, + tickWidth: (t,e)=>e.lineWidth, + tickColor: (t,e)=>e.color, + offset: !1, + borderDash: [], + borderDashOffset: 0, + borderWidth: 1 + }, + title: { + display: !1, + text: "", + padding: { + top: 4, + bottom: 4 + } + }, + ticks: { + minRotation: 0, + maxRotation: 50, + mirror: !1, + textStrokeWidth: 0, + textStrokeColor: "", + padding: 3, + display: !0, + autoSkip: !0, + autoSkipPadding: 3, + labelOffset: 0, + callback: Ls.formatters.values, + minor: {}, + major: {}, + align: "center", + crossAlign: "near", + showLabelBackdrop: !1, + backdropColor: "rgba(255, 255, 255, 0.75)", + backdropPadding: 2 + } + }), + yt.route("scale.ticks", "color", "", "color"), + yt.route("scale.grid", "color", "", "borderColor"), + yt.route("scale.grid", "borderColor", "", "borderColor"), + yt.route("scale.title", "color", "", "color"), + yt.describe("scale", { + _fallback: !1, + _scriptable: t=>!t.startsWith("before") && !t.startsWith("after") && "callback" !== t && "parser" !== t, + _indexable: t=>"borderDash" !== t && "tickBorderDash" !== t + }), + yt.describe("scales", { + _fallback: "scale" + }), + yt.describe("scale.ticks", { + _scriptable: t=>"backdropPadding" !== t && "callback" !== t, + _indexable: t=>"backdropPadding" !== t + }); + const Is = (t,e,i)=>"top" === e || "left" === e ? t[e] + i : t[e] - i; + function zs(t, e) { + const i = [] + , s = t.length / e + , n = t.length; + let o = 0; + for (; o < n; o += s) + i.push(t[Math.floor(o)]); + return i + } + function Fs(t, e, i) { + const s = t.ticks.length + , n = Math.min(e, s - 1) + , o = t._startPixel + , a = t._endPixel + , r = 1e-6; + let l, h = t.getPixelForTick(n); + if (!(i && (l = 1 === s ? Math.max(h - o, a - h) : 0 === e ? (t.getPixelForTick(1) - h) / 2 : (h - t.getPixelForTick(n - 1)) / 2, + h += n < e ? l : -l, + h < o - r || h > a + r))) + return h + } + function Bs(t) { + return t.drawTicks ? t.tickLength : 0 + } + function Vs(t, e) { + if (!t.display) + return 0; + const i = fi(t.font, e) + , s = ui(t.padding); + return (X(t.text) ? t.text.length : 1) * i.lineHeight + s.height + } + function Ws(t, e, i) { + let n = s(t); + return (i && "right" !== e || !i && "right" === e) && (n = (t=>"left" === t ? "right" : "right" === t ? "left" : t)(n)), + n + } + class Ns extends As { + constructor(t) { + super(), + this.id = t.id, + this.type = t.type, + this.options = void 0, + this.ctx = t.ctx, + this.chart = t.chart, + this.top = void 0, + this.bottom = void 0, + this.left = void 0, + this.right = void 0, + this.width = void 0, + this.height = void 0, + this._margins = { + left: 0, + right: 0, + top: 0, + bottom: 0 + }, + this.maxWidth = void 0, + this.maxHeight = void 0, + this.paddingTop = void 0, + this.paddingBottom = void 0, + this.paddingLeft = void 0, + this.paddingRight = void 0, + this.axis = void 0, + this.labelRotation = void 0, + this.min = void 0, + this.max = void 0, + this._range = void 0, + this.ticks = [], + this._gridLineItems = null, + this._labelItems = null, + this._labelSizes = null, + this._length = 0, + this._maxLength = 0, + this._longestTextCache = {}, + this._startPixel = void 0, + this._endPixel = void 0, + this._reversePixels = !1, + this._userMax = void 0, + this._userMin = void 0, + this._suggestedMax = void 0, + this._suggestedMin = void 0, + this._ticksLength = 0, + this._borderValue = 0, + this._cache = {}, + this._dataLimitsCached = !1, + this.$context = void 0 + } + init(t) { + this.options = t.setContext(this.getContext()), + this.axis = t.axis, + this._userMin = this.parse(t.min), + this._userMax = this.parse(t.max), + this._suggestedMin = this.parse(t.suggestedMin), + this._suggestedMax = this.parse(t.suggestedMax) + } + parse(t, e) { + return t + } + getUserBounds() { + let {_userMin: t, _userMax: e, _suggestedMin: i, _suggestedMax: s} = this; + return t = G(t, Number.POSITIVE_INFINITY), + e = G(e, Number.NEGATIVE_INFINITY), + i = G(i, Number.POSITIVE_INFINITY), + s = G(s, Number.NEGATIVE_INFINITY), + { + min: G(t, i), + max: G(e, s), + minDefined: K(t), + maxDefined: K(e) + } + } + getMinMax(t) { + let e, {min: i, max: s, minDefined: n, maxDefined: o} = this.getUserBounds(); + if (n && o) + return { + min: i, + max: s + }; + const a = this.getMatchingVisibleMetas(); + for (let r = 0, l = a.length; r < l; ++r) + e = a[r].controller.getMinMax(this, t), + n || (i = Math.min(i, e.min)), + o || (s = Math.max(s, e.max)); + return i = o && i > s ? s : i, + s = n && i > s ? i : s, + { + min: G(i, G(s, i)), + max: G(s, G(i, s)) + } + } + getPadding() { + return { + left: this.paddingLeft || 0, + top: this.paddingTop || 0, + right: this.paddingRight || 0, + bottom: this.paddingBottom || 0 + } + } + getTicks() { + return this.ticks + } + getLabels() { + const t = this.chart.data; + return this.options.labels || (this.isHorizontal() ? t.xLabels : t.yLabels) || t.labels || [] + } + beforeLayout() { + this._cache = {}, + this._dataLimitsCached = !1 + } + beforeUpdate() { + tt(this.options.beforeUpdate, [this]) + } + update(t, e, i) { + const {beginAtZero: s, grace: n, ticks: o} = this.options + , a = o.sampleSize; + this.beforeUpdate(), + this.maxWidth = t, + this.maxHeight = e, + this._margins = i = Object.assign({ + left: 0, + right: 0, + top: 0, + bottom: 0 + }, i), + this.ticks = null, + this._labelSizes = null, + this._gridLineItems = null, + this._labelItems = null, + this.beforeSetDimensions(), + this.setDimensions(), + this.afterSetDimensions(), + this._maxLength = this.isHorizontal() ? this.width + i.left + i.right : this.height + i.top + i.bottom, + this._dataLimitsCached || (this.beforeDataLimits(), + this.determineDataLimits(), + this.afterDataLimits(), + this._range = pi(this, n, s), + this._dataLimitsCached = !0), + this.beforeBuildTicks(), + this.ticks = this.buildTicks() || [], + this.afterBuildTicks(); + const r = a < this.ticks.length; + this._convertTicksToLabels(r ? zs(this.ticks, a) : this.ticks), + this.configure(), + this.beforeCalculateLabelRotation(), + this.calculateLabelRotation(), + this.afterCalculateLabelRotation(), + o.display && (o.autoSkip || "auto" === o.source) && (this.ticks = Rs(this, this.ticks), + this._labelSizes = null, + this.afterAutoSkip()), + r && this._convertTicksToLabels(this.ticks), + this.beforeFit(), + this.fit(), + this.afterFit(), + this.afterUpdate() + } + configure() { + let t, e, i = this.options.reverse; + this.isHorizontal() ? (t = this.left, + e = this.right) : (t = this.top, + e = this.bottom, + i = !i), + this._startPixel = t, + this._endPixel = e, + this._reversePixels = i, + this._length = e - t, + this._alignToPixels = this.options.alignToPixels + } + afterUpdate() { + tt(this.options.afterUpdate, [this]) + } + beforeSetDimensions() { + tt(this.options.beforeSetDimensions, [this]) + } + setDimensions() { + this.isHorizontal() ? (this.width = this.maxWidth, + this.left = 0, + this.right = this.width) : (this.height = this.maxHeight, + this.top = 0, + this.bottom = this.height), + this.paddingLeft = 0, + this.paddingTop = 0, + this.paddingRight = 0, + this.paddingBottom = 0 + } + afterSetDimensions() { + tt(this.options.afterSetDimensions, [this]) + } + _callHooks(t) { + this.chart.notifyPlugins(t, this.getContext()), + tt(this.options[t], [this]) + } + beforeDataLimits() { + this._callHooks("beforeDataLimits") + } + determineDataLimits() {} + afterDataLimits() { + this._callHooks("afterDataLimits") + } + beforeBuildTicks() { + this._callHooks("beforeBuildTicks") + } + buildTicks() { + return [] + } + afterBuildTicks() { + this._callHooks("afterBuildTicks") + } + beforeTickToLabelConversion() { + tt(this.options.beforeTickToLabelConversion, [this]) + } + generateTickLabels(t) { + const e = this.options.ticks; + let i, s, n; + for (i = 0, + s = t.length; i < s; i++) + n = t[i], + n.label = tt(e.callback, [n.value, i, t], this) + } + afterTickToLabelConversion() { + tt(this.options.afterTickToLabelConversion, [this]) + } + beforeCalculateLabelRotation() { + tt(this.options.beforeCalculateLabelRotation, [this]) + } + calculateLabelRotation() { + const t = this.options + , e = t.ticks + , i = this.ticks.length + , s = e.minRotation || 0 + , n = e.maxRotation; + let o, a, r, l = s; + if (!this._isVisible() || !e.display || s >= n || i <= 1 || !this.isHorizontal()) + return void (this.labelRotation = s); + const h = this._getLabelSizes() + , c = h.widest.width + , d = h.highest.height + , u = Qt(this.chart.width - c, 0, this.maxWidth); + o = t.offset ? this.maxWidth / i : u / (i - 1), + c + 6 > o && (o = u / (i - (t.offset ? .5 : 1)), + a = this.maxHeight - Bs(t.grid) - e.padding - Vs(t.title, this.chart.options.font), + r = Math.sqrt(c * c + d * d), + l = Ut(Math.min(Math.asin(Qt((h.highest.height + 6) / o, -1, 1)), Math.asin(Qt(a / r, -1, 1)) - Math.asin(Qt(d / r, -1, 1)))), + l = Math.max(s, Math.min(n, l))), + this.labelRotation = l + } + afterCalculateLabelRotation() { + tt(this.options.afterCalculateLabelRotation, [this]) + } + afterAutoSkip() {} + beforeFit() { + tt(this.options.beforeFit, [this]) + } + fit() { + const t = { + width: 0, + height: 0 + } + , {chart: e, options: {ticks: i, title: s, grid: n}} = this + , o = this._isVisible() + , a = this.isHorizontal(); + if (o) { + const o = Vs(s, e.options.font); + if (a ? (t.width = this.maxWidth, + t.height = Bs(n) + o) : (t.height = this.maxHeight, + t.width = Bs(n) + o), + i.display && this.ticks.length) { + const {first: e, last: s, widest: n, highest: o} = this._getLabelSizes() + , r = 2 * i.padding + , l = Yt(this.labelRotation) + , h = Math.cos(l) + , c = Math.sin(l); + if (a) { + const e = i.mirror ? 0 : c * n.width + h * o.height; + t.height = Math.min(this.maxHeight, t.height + e + r) + } else { + const e = i.mirror ? 0 : h * n.width + c * o.height; + t.width = Math.min(this.maxWidth, t.width + e + r) + } + this._calculatePadding(e, s, c, h) + } + } + this._handleMargins(), + a ? (this.width = this._length = e.width - this._margins.left - this._margins.right, + this.height = t.height) : (this.width = t.width, + this.height = this._length = e.height - this._margins.top - this._margins.bottom) + } + _calculatePadding(t, e, i, s) { + const {ticks: {align: n, padding: o}, position: a} = this.options + , r = 0 !== this.labelRotation + , l = "top" !== a && "x" === this.axis; + if (this.isHorizontal()) { + const a = this.getPixelForTick(0) - this.left + , h = this.right - this.getPixelForTick(this.ticks.length - 1); + let c = 0 + , d = 0; + r ? l ? (c = s * t.width, + d = i * e.height) : (c = i * t.height, + d = s * e.width) : "start" === n ? d = e.width : "end" === n ? c = t.width : "inner" !== n && (c = t.width / 2, + d = e.width / 2), + this.paddingLeft = Math.max((c - a + o) * this.width / (this.width - a), 0), + this.paddingRight = Math.max((d - h + o) * this.width / (this.width - h), 0) + } else { + let i = e.height / 2 + , s = t.height / 2; + "start" === n ? (i = 0, + s = t.height) : "end" === n && (i = e.height, + s = 0), + this.paddingTop = i + o, + this.paddingBottom = s + o + } + } + _handleMargins() { + this._margins && (this._margins.left = Math.max(this.paddingLeft, this._margins.left), + this._margins.top = Math.max(this.paddingTop, this._margins.top), + this._margins.right = Math.max(this.paddingRight, this._margins.right), + this._margins.bottom = Math.max(this.paddingBottom, this._margins.bottom)) + } + afterFit() { + tt(this.options.afterFit, [this]) + } + isHorizontal() { + const {axis: t, position: e} = this.options; + return "top" === e || "bottom" === e || "x" === t + } + isFullSize() { + return this.options.fullSize + } + _convertTicksToLabels(t) { + let e, i; + for (this.beforeTickToLabelConversion(), + this.generateTickLabels(t), + e = 0, + i = t.length; e < i; e++) + U(t[e].label) && (t.splice(e, 1), + i--, + e--); + this.afterTickToLabelConversion() + } + _getLabelSizes() { + let t = this._labelSizes; + if (!t) { + const e = this.options.ticks.sampleSize; + let i = this.ticks; + e < i.length && (i = zs(i, e)), + this._labelSizes = t = this._computeLabelSizes(i, i.length) + } + return t + } + _computeLabelSizes(t, e) { + const {ctx: i, _longestTextCache: s} = this + , n = [] + , o = []; + let a, r, l, h, c, d, u, f, g, p, m, b = 0, x = 0; + for (a = 0; a < e; ++a) { + if (h = t[a].label, + c = this._resolveTickFontOptions(a), + i.font = d = c.string, + u = s[d] = s[d] || { + data: {}, + gc: [] + }, + f = c.lineHeight, + g = p = 0, + U(h) || X(h)) { + if (X(h)) + for (r = 0, + l = h.length; r < l; ++r) + m = h[r], + U(m) || X(m) || (g = me(i, u.data, u.gc, g, m), + p += f) + } else + g = me(i, u.data, u.gc, g, h), + p = f; + n.push(g), + o.push(p), + b = Math.max(g, b), + x = Math.max(p, x) + } + !function(t, e) { + et(t, (t=>{ + const i = t.gc + , s = i.length / 2; + let n; + if (s > e) { + for (n = 0; n < s; ++n) + delete t.data[i[n]]; + i.splice(0, s) + } + } + )) + }(s, e); + const _ = n.indexOf(b) + , y = o.indexOf(x) + , v = t=>({ + width: n[t] || 0, + height: o[t] || 0 + }); + return { + first: v(0), + last: v(e - 1), + widest: v(_), + highest: v(y), + widths: n, + heights: o + } + } + getLabelForValue(t) { + return t + } + getPixelForValue(t, e) { + return NaN + } + getValueForPixel(t) {} + getPixelForTick(t) { + const e = this.ticks; + return t < 0 || t > e.length - 1 ? null : this.getPixelForValue(e[t].value) + } + getPixelForDecimal(t) { + this._reversePixels && (t = 1 - t); + const e = this._startPixel + t * this._length; + return te(this._alignToPixels ? xe(this.chart, e, 0) : e) + } + getDecimalForPixel(t) { + const e = (t - this._startPixel) / this._length; + return this._reversePixels ? 1 - e : e + } + getBasePixel() { + return this.getPixelForValue(this.getBaseValue()) + } + getBaseValue() { + const {min: t, max: e} = this; + return t < 0 && e < 0 ? e : t > 0 && e > 0 ? t : 0 + } + getContext(t) { + const e = this.ticks || []; + if (t >= 0 && t < e.length) { + const i = e[t]; + return i.$context || (i.$context = function(t, e, i) { + return mi(t, { + tick: i, + index: e, + type: "tick" + }) + }(this.getContext(), t, i)) + } + return this.$context || (this.$context = mi(this.chart.getContext(), { + scale: this, + type: "scale" + })) + } + _tickSize() { + const t = this.options.ticks + , e = Yt(this.labelRotation) + , i = Math.abs(Math.cos(e)) + , s = Math.abs(Math.sin(e)) + , n = this._getLabelSizes() + , o = t.autoSkipPadding || 0 + , a = n ? n.widest.width + o : 0 + , r = n ? n.highest.height + o : 0; + return this.isHorizontal() ? r * i > a * s ? a / i : r / s : r * s < a * i ? r / i : a / s + } + _isVisible() { + const t = this.options.display; + return "auto" !== t ? !!t : this.getMatchingVisibleMetas().length > 0 + } + _computeGridLineItems(t) { + const e = this.axis + , i = this.chart + , s = this.options + , {grid: n, position: o} = s + , a = n.offset + , r = this.isHorizontal() + , l = this.ticks.length + (a ? 1 : 0) + , h = Bs(n) + , c = [] + , d = n.setContext(this.getContext()) + , u = d.drawBorder ? d.borderWidth : 0 + , f = u / 2 + , g = function(t) { + return xe(i, t, u) + }; + let p, m, b, x, _, y, v, w, M, k, S, P; + if ("top" === o) + p = g(this.bottom), + y = this.bottom - h, + w = p - f, + k = g(t.top) + f, + P = t.bottom; + else if ("bottom" === o) + p = g(this.top), + k = t.top, + P = g(t.bottom) - f, + y = p + f, + w = this.top + h; + else if ("left" === o) + p = g(this.right), + _ = this.right - h, + v = p - f, + M = g(t.left) + f, + S = t.right; + else if ("right" === o) + p = g(this.left), + M = t.left, + S = g(t.right) - f, + _ = p + f, + v = this.left + h; + else if ("x" === e) { + if ("center" === o) + p = g((t.top + t.bottom) / 2 + .5); + else if (q(o)) { + const t = Object.keys(o)[0] + , e = o[t]; + p = g(this.chart.scales[t].getPixelForValue(e)) + } + k = t.top, + P = t.bottom, + y = p + f, + w = y + h + } else if ("y" === e) { + if ("center" === o) + p = g((t.left + t.right) / 2); + else if (q(o)) { + const t = Object.keys(o)[0] + , e = o[t]; + p = g(this.chart.scales[t].getPixelForValue(e)) + } + _ = p - f, + v = _ - h, + M = t.left, + S = t.right + } + const D = Z(s.ticks.maxTicksLimit, l) + , C = Math.max(1, Math.ceil(l / D)); + for (m = 0; m < l; m += C) { + const t = n.setContext(this.getContext(m)) + , e = t.lineWidth + , s = t.color + , o = n.borderDash || [] + , l = t.borderDashOffset + , h = t.tickWidth + , d = t.tickColor + , u = t.tickBorderDash || [] + , f = t.tickBorderDashOffset; + b = Fs(this, m, a), + void 0 !== b && (x = xe(i, b, e), + r ? _ = v = M = S = x : y = w = k = P = x, + c.push({ + tx1: _, + ty1: y, + tx2: v, + ty2: w, + x1: M, + y1: k, + x2: S, + y2: P, + width: e, + color: s, + borderDash: o, + borderDashOffset: l, + tickWidth: h, + tickColor: d, + tickBorderDash: u, + tickBorderDashOffset: f + })) + } + return this._ticksLength = l, + this._borderValue = p, + c + } + _computeLabelItems(t) { + const e = this.axis + , i = this.options + , {position: s, ticks: n} = i + , o = this.isHorizontal() + , a = this.ticks + , {align: r, crossAlign: l, padding: h, mirror: c} = n + , d = Bs(i.grid) + , u = d + h + , f = c ? -h : u + , g = -Yt(this.labelRotation) + , p = []; + let m, b, x, _, y, v, w, M, k, S, P, D, C = "middle"; + if ("top" === s) + v = this.bottom - f, + w = this._getXAxisLabelAlignment(); + else if ("bottom" === s) + v = this.top + f, + w = this._getXAxisLabelAlignment(); + else if ("left" === s) { + const t = this._getYAxisLabelAlignment(d); + w = t.textAlign, + y = t.x + } else if ("right" === s) { + const t = this._getYAxisLabelAlignment(d); + w = t.textAlign, + y = t.x + } else if ("x" === e) { + if ("center" === s) + v = (t.top + t.bottom) / 2 + u; + else if (q(s)) { + const t = Object.keys(s)[0] + , e = s[t]; + v = this.chart.scales[t].getPixelForValue(e) + u + } + w = this._getXAxisLabelAlignment() + } else if ("y" === e) { + if ("center" === s) + y = (t.left + t.right) / 2 - u; + else if (q(s)) { + const t = Object.keys(s)[0] + , e = s[t]; + y = this.chart.scales[t].getPixelForValue(e) + } + w = this._getYAxisLabelAlignment(d).textAlign + } + "y" === e && ("start" === r ? C = "top" : "end" === r && (C = "bottom")); + const O = this._getLabelSizes(); + for (m = 0, + b = a.length; m < b; ++m) { + x = a[m], + _ = x.label; + const t = n.setContext(this.getContext(m)); + M = this.getPixelForTick(m) + n.labelOffset, + k = this._resolveTickFontOptions(m), + S = k.lineHeight, + P = X(_) ? _.length : 1; + const e = P / 2 + , i = t.color + , r = t.textStrokeColor + , h = t.textStrokeWidth; + let d, u = w; + if (o ? (y = M, + "inner" === w && (u = m === b - 1 ? this.options.reverse ? "left" : "right" : 0 === m ? this.options.reverse ? "right" : "left" : "center"), + D = "top" === s ? "near" === l || 0 !== g ? -P * S + S / 2 : "center" === l ? -O.highest.height / 2 - e * S + S : -O.highest.height + S / 2 : "near" === l || 0 !== g ? S / 2 : "center" === l ? O.highest.height / 2 - e * S : O.highest.height - P * S, + c && (D *= -1)) : (v = M, + D = (1 - P) * S / 2), + t.showLabelBackdrop) { + const e = ui(t.backdropPadding) + , i = O.heights[m] + , s = O.widths[m]; + let n = v + D - e.top + , o = y - e.left; + switch (C) { + case "middle": + n -= i / 2; + break; + case "bottom": + n -= i + } + switch (w) { + case "center": + o -= s / 2; + break; + case "right": + o -= s + } + d = { + left: o, + top: n, + width: s + e.width, + height: i + e.height, + color: t.backdropColor + } + } + p.push({ + rotation: g, + label: _, + font: k, + color: i, + strokeColor: r, + strokeWidth: h, + textOffset: D, + textAlign: u, + textBaseline: C, + translation: [y, v], + backdrop: d + }) + } + return p + } + _getXAxisLabelAlignment() { + const {position: t, ticks: e} = this.options; + if (-Yt(this.labelRotation)) + return "top" === t ? "left" : "right"; + let i = "center"; + return "start" === e.align ? i = "left" : "end" === e.align ? i = "right" : "inner" === e.align && (i = "inner"), + i + } + _getYAxisLabelAlignment(t) { + const {position: e, ticks: {crossAlign: i, mirror: s, padding: n}} = this.options + , o = t + n + , a = this._getLabelSizes().widest.width; + let r, l; + return "left" === e ? s ? (l = this.right + n, + "near" === i ? r = "left" : "center" === i ? (r = "center", + l += a / 2) : (r = "right", + l += a)) : (l = this.right - o, + "near" === i ? r = "right" : "center" === i ? (r = "center", + l -= a / 2) : (r = "left", + l = this.left)) : "right" === e ? s ? (l = this.left + n, + "near" === i ? r = "right" : "center" === i ? (r = "center", + l -= a / 2) : (r = "left", + l -= a)) : (l = this.left + o, + "near" === i ? r = "left" : "center" === i ? (r = "center", + l += a / 2) : (r = "right", + l = this.right)) : r = "right", + { + textAlign: r, + x: l + } + } + _computeLabelArea() { + if (this.options.ticks.mirror) + return; + const t = this.chart + , e = this.options.position; + return "left" === e || "right" === e ? { + top: 0, + left: this.left, + bottom: t.height, + right: this.right + } : "top" === e || "bottom" === e ? { + top: this.top, + left: 0, + bottom: this.bottom, + right: t.width + } : void 0 + } + drawBackground() { + const {ctx: t, options: {backgroundColor: e}, left: i, top: s, width: n, height: o} = this; + e && (t.save(), + t.fillStyle = e, + t.fillRect(i, s, n, o), + t.restore()) + } + getLineWidthForValue(t) { + const e = this.options.grid; + if (!this._isVisible() || !e.display) + return 0; + const i = this.ticks.findIndex((e=>e.value === t)); + if (i >= 0) { + return e.setContext(this.getContext(i)).lineWidth + } + return 0 + } + drawGrid(t) { + const e = this.options.grid + , i = this.ctx + , s = this._gridLineItems || (this._gridLineItems = this._computeGridLineItems(t)); + let n, o; + const a = (t,e,s)=>{ + s.width && s.color && (i.save(), + i.lineWidth = s.width, + i.strokeStyle = s.color, + i.setLineDash(s.borderDash || []), + i.lineDashOffset = s.borderDashOffset, + i.beginPath(), + i.moveTo(t.x, t.y), + i.lineTo(e.x, e.y), + i.stroke(), + i.restore()) + } + ; + if (e.display) + for (n = 0, + o = s.length; n < o; ++n) { + const t = s[n]; + e.drawOnChartArea && a({ + x: t.x1, + y: t.y1 + }, { + x: t.x2, + y: t.y2 + }, t), + e.drawTicks && a({ + x: t.tx1, + y: t.ty1 + }, { + x: t.tx2, + y: t.ty2 + }, { + color: t.tickColor, + width: t.tickWidth, + borderDash: t.tickBorderDash, + borderDashOffset: t.tickBorderDashOffset + }) + } + } + drawBorder() { + const {chart: t, ctx: e, options: {grid: i}} = this + , s = i.setContext(this.getContext()) + , n = i.drawBorder ? s.borderWidth : 0; + if (!n) + return; + const o = i.setContext(this.getContext(0)).lineWidth + , a = this._borderValue; + let r, l, h, c; + this.isHorizontal() ? (r = xe(t, this.left, n) - n / 2, + l = xe(t, this.right, o) + o / 2, + h = c = a) : (h = xe(t, this.top, n) - n / 2, + c = xe(t, this.bottom, o) + o / 2, + r = l = a), + e.save(), + e.lineWidth = s.borderWidth, + e.strokeStyle = s.borderColor, + e.beginPath(), + e.moveTo(r, h), + e.lineTo(l, c), + e.stroke(), + e.restore() + } + drawLabels(t) { + if (!this.options.ticks.display) + return; + const e = this.ctx + , i = this._computeLabelArea(); + i && Me(e, i); + const s = this._labelItems || (this._labelItems = this._computeLabelItems(t)); + let n, o; + for (n = 0, + o = s.length; n < o; ++n) { + const t = s[n] + , i = t.font + , o = t.label; + t.backdrop && (e.fillStyle = t.backdrop.color, + e.fillRect(t.backdrop.left, t.backdrop.top, t.backdrop.width, t.backdrop.height)), + De(e, o, 0, t.textOffset, i, t) + } + i && ke(e) + } + drawTitle() { + const {ctx: t, options: {position: e, title: i, reverse: s}} = this; + if (!i.display) + return; + const o = fi(i.font) + , a = ui(i.padding) + , r = i.align; + let l = o.lineHeight / 2; + "bottom" === e || "center" === e || q(e) ? (l += a.bottom, + X(i.text) && (l += o.lineHeight * (i.text.length - 1))) : l += a.top; + const {titleX: h, titleY: c, maxWidth: d, rotation: u} = function(t, e, i, s) { + const {top: o, left: a, bottom: r, right: l, chart: h} = t + , {chartArea: c, scales: d} = h; + let u, f, g, p = 0; + const m = r - o + , b = l - a; + if (t.isHorizontal()) { + if (f = n(s, a, l), + q(i)) { + const t = Object.keys(i)[0] + , s = i[t]; + g = d[t].getPixelForValue(s) + m - e + } else + g = "center" === i ? (c.bottom + c.top) / 2 + m - e : Is(t, i, e); + u = l - a + } else { + if (q(i)) { + const t = Object.keys(i)[0] + , s = i[t]; + f = d[t].getPixelForValue(s) - b + e + } else + f = "center" === i ? (c.left + c.right) / 2 - b + e : Is(t, i, e); + g = n(s, r, o), + p = "left" === i ? -Et : Et + } + return { + titleX: f, + titleY: g, + maxWidth: u, + rotation: p + } + }(this, l, e, r); + De(t, i.text, 0, 0, o, { + color: i.color, + maxWidth: d, + rotation: u, + textAlign: Ws(r, e, s), + textBaseline: "middle", + translation: [h, c] + }) + } + draw(t) { + this._isVisible() && (this.drawBackground(), + this.drawGrid(t), + this.drawBorder(), + this.drawTitle(), + this.drawLabels(t)) + } + _layers() { + const t = this.options + , e = t.ticks && t.ticks.z || 0 + , i = Z(t.grid && t.grid.z, -1); + return this._isVisible() && this.draw === Ns.prototype.draw ? [{ + z: i, + draw: t=>{ + this.drawBackground(), + this.drawGrid(t), + this.drawTitle() + } + }, { + z: i + 1, + draw: ()=>{ + this.drawBorder() + } + }, { + z: e, + draw: t=>{ + this.drawLabels(t) + } + }] : [{ + z: e, + draw: t=>{ + this.draw(t) + } + }] + } + getMatchingVisibleMetas(t) { + const e = this.chart.getSortedVisibleDatasetMetas() + , i = this.axis + "AxisID" + , s = []; + let n, o; + for (n = 0, + o = e.length; n < o; ++n) { + const o = e[n]; + o[i] !== this.id || t && o.type !== t || s.push(o) + } + return s + } + _resolveTickFontOptions(t) { + return fi(this.options.ticks.setContext(this.getContext(t)).font) + } + _maxDigits() { + const t = this._resolveTickFontOptions(0).lineHeight; + return (this.isHorizontal() ? this.width : this.height) / t + } + } + class js { + constructor(t, e, i) { + this.type = t, + this.scope = e, + this.override = i, + this.items = Object.create(null) + } + isForType(t) { + return Object.prototype.isPrototypeOf.call(this.type.prototype, t.prototype) + } + register(t) { + const e = Object.getPrototypeOf(t); + let i; + (function(t) { + return "id"in t && "defaults"in t + } + )(e) && (i = this.register(e)); + const s = this.items + , n = t.id + , o = this.scope + "." + n; + if (!n) + throw new Error("class does not have id: " + t); + return n in s || (s[n] = t, + function(t, e, i) { + const s = at(Object.create(null), [i ? yt.get(i) : {}, yt.get(e), t.defaults]); + yt.set(e, s), + t.defaultRoutes && function(t, e) { + Object.keys(e).forEach((i=>{ + const s = i.split(".") + , n = s.pop() + , o = [t].concat(s).join(".") + , a = e[i].split(".") + , r = a.pop() + , l = a.join("."); + yt.route(o, n, l, r) + } + )) + }(e, t.defaultRoutes); + t.descriptors && yt.describe(e, t.descriptors) + }(t, o, i), + this.override && yt.override(t.id, t.overrides)), + o + } + get(t) { + return this.items[t] + } + unregister(t) { + const e = this.items + , i = t.id + , s = this.scope; + i in e && delete e[i], + s && i in yt[s] && (delete yt[s][i], + this.override && delete mt[i]) + } + } + var Hs = new class { + constructor() { + this.controllers = new js(Os,"datasets",!0), + this.elements = new js(As,"elements"), + this.plugins = new js(Object,"plugins"), + this.scales = new js(Ns,"scales"), + this._typedRegistries = [this.controllers, this.scales, this.elements] + } + add(...t) { + this._each("register", t) + } + remove(...t) { + this._each("unregister", t) + } + addControllers(...t) { + this._each("register", t, this.controllers) + } + addElements(...t) { + this._each("register", t, this.elements) + } + addPlugins(...t) { + this._each("register", t, this.plugins) + } + addScales(...t) { + this._each("register", t, this.scales) + } + getController(t) { + return this._get(t, this.controllers, "controller") + } + getElement(t) { + return this._get(t, this.elements, "element") + } + getPlugin(t) { + return this._get(t, this.plugins, "plugin") + } + getScale(t) { + return this._get(t, this.scales, "scale") + } + removeControllers(...t) { + this._each("unregister", t, this.controllers) + } + removeElements(...t) { + this._each("unregister", t, this.elements) + } + removePlugins(...t) { + this._each("unregister", t, this.plugins) + } + removeScales(...t) { + this._each("unregister", t, this.scales) + } + _each(t, e, i) { + [...e].forEach((e=>{ + const s = i || this._getRegistryForType(e); + i || s.isForType(e) || s === this.plugins && e.id ? this._exec(t, s, e) : et(e, (e=>{ + const s = i || this._getRegistryForType(e); + this._exec(t, s, e) + } + )) + } + )) + } + _exec(t, e, i) { + const s = dt(t); + tt(i["before" + s], [], i), + e[t](i), + tt(i["after" + s], [], i) + } + _getRegistryForType(t) { + for (let e = 0; e < this._typedRegistries.length; e++) { + const i = this._typedRegistries[e]; + if (i.isForType(t)) + return i + } + return this.plugins + } + _get(t, e, i) { + const s = e.get(t); + if (void 0 === s) + throw new Error('"' + t + '" is not a registered ' + i + "."); + return s + } + } + ; + class $s { + constructor() { + this._init = [] + } + notify(t, e, i, s) { + "beforeInit" === e && (this._init = this._createDescriptors(t, !0), + this._notify(this._init, t, "install")); + const n = s ? this._descriptors(t).filter(s) : this._descriptors(t) + , o = this._notify(n, t, e, i); + return "afterDestroy" === e && (this._notify(n, t, "stop"), + this._notify(this._init, t, "uninstall")), + o + } + _notify(t, e, i, s) { + s = s || {}; + for (const n of t) { + const t = n.plugin; + if (!1 === tt(t[i], [e, s, n.options], t) && s.cancelable) + return !1 + } + return !0 + } + invalidate() { + U(this._cache) || (this._oldCache = this._cache, + this._cache = void 0) + } + _descriptors(t) { + if (this._cache) + return this._cache; + const e = this._cache = this._createDescriptors(t); + return this._notifyStateChanges(t), + e + } + _createDescriptors(t, e) { + const i = t && t.config + , s = Z(i.options && i.options.plugins, {}) + , n = function(t) { + const e = {} + , i = [] + , s = Object.keys(Hs.plugins.items); + for (let t = 0; t < s.length; t++) + i.push(Hs.getPlugin(s[t])); + const n = t.plugins || []; + for (let t = 0; t < n.length; t++) { + const s = n[t]; + -1 === i.indexOf(s) && (i.push(s), + e[s.id] = !0) + } + return { + plugins: i, + localIds: e + } + }(i); + return !1 !== s || e ? function(t, {plugins: e, localIds: i}, s, n) { + const o = [] + , a = t.getContext(); + for (const r of e) { + const e = r.id + , l = Ys(s[e], n); + null !== l && o.push({ + plugin: r, + options: Us(t.config, { + plugin: r, + local: i[e] + }, l, a) + }) + } + return o + }(t, n, s, e) : [] + } + _notifyStateChanges(t) { + const e = this._oldCache || [] + , i = this._cache + , s = (t,e)=>t.filter((t=>!e.some((e=>t.plugin.id === e.plugin.id)))); + this._notify(s(e, i), t, "stop"), + this._notify(s(i, e), t, "start") + } + } + function Ys(t, e) { + return e || !1 !== t ? !0 === t ? {} : t : null + } + function Us(t, {plugin: e, local: i}, s, n) { + const o = t.pluginScopeKeys(e) + , a = t.getOptionScopes(s, o); + return i && e.defaults && a.push(e.defaults), + t.createResolver(a, n, [""], { + scriptable: !1, + indexable: !1, + allKeys: !0 + }) + } + function Xs(t, e) { + const i = yt.datasets[t] || {}; + return ((e.datasets || {})[t] || {}).indexAxis || e.indexAxis || i.indexAxis || "x" + } + function qs(t, e) { + return "x" === t || "y" === t ? t : e.axis || ("top" === (i = e.position) || "bottom" === i ? "x" : "left" === i || "right" === i ? "y" : void 0) || t.charAt(0).toLowerCase(); + var i + } + function Ks(t) { + const e = t.options || (t.options = {}); + e.plugins = Z(e.plugins, {}), + e.scales = function(t, e) { + const i = mt[t.type] || { + scales: {} + } + , s = e.scales || {} + , n = Xs(t.type, e) + , o = Object.create(null) + , a = Object.create(null); + return Object.keys(s).forEach((t=>{ + const e = s[t]; + if (!q(e)) + return console.error(`Invalid scale configuration for scale: ${t}`); + if (e._proxy) + return console.warn(`Ignoring resolver passed as options for scale: ${t}`); + const r = qs(t, e) + , l = function(t, e) { + return t === e ? "_index_" : "_value_" + }(r, n) + , h = i.scales || {}; + o[r] = o[r] || t, + a[t] = rt(Object.create(null), [{ + axis: r + }, e, h[r], h[l]]) + } + )), + t.data.datasets.forEach((i=>{ + const n = i.type || t.type + , r = i.indexAxis || Xs(n, e) + , l = (mt[n] || {}).scales || {}; + Object.keys(l).forEach((t=>{ + const e = function(t, e) { + let i = t; + return "_index_" === t ? i = e : "_value_" === t && (i = "x" === e ? "y" : "x"), + i + }(t, r) + , n = i[e + "AxisID"] || o[e] || e; + a[n] = a[n] || Object.create(null), + rt(a[n], [{ + axis: e + }, s[n], l[t]]) + } + )) + } + )), + Object.keys(a).forEach((t=>{ + const e = a[t]; + rt(e, [yt.scales[e.type], yt.scale]) + } + )), + a + }(t, e) + } + function Gs(t) { + return (t = t || {}).datasets = t.datasets || [], + t.labels = t.labels || [], + t + } + const Zs = new Map + , Js = new Set; + function Qs(t, e) { + let i = Zs.get(t); + return i || (i = e(), + Zs.set(t, i), + Js.add(i)), + i + } + const tn = (t,e,i)=>{ + const s = ct(e, i); + void 0 !== s && t.add(s) + } + ; + class en { + constructor(t) { + this._config = function(t) { + return (t = t || {}).data = Gs(t.data), + Ks(t), + t + }(t), + this._scopeCache = new Map, + this._resolverCache = new Map + } + get platform() { + return this._config.platform + } + get type() { + return this._config.type + } + set type(t) { + this._config.type = t + } + get data() { + return this._config.data + } + set data(t) { + this._config.data = Gs(t) + } + get options() { + return this._config.options + } + set options(t) { + this._config.options = t + } + get plugins() { + return this._config.plugins + } + update() { + const t = this._config; + this.clearCache(), + Ks(t) + } + clearCache() { + this._scopeCache.clear(), + this._resolverCache.clear() + } + datasetScopeKeys(t) { + return Qs(t, (()=>[[`datasets.${t}`, ""]])) + } + datasetAnimationScopeKeys(t, e) { + return Qs(`${t}.transition.${e}`, (()=>[[`datasets.${t}.transitions.${e}`, `transitions.${e}`], [`datasets.${t}`, ""]])) + } + datasetElementScopeKeys(t, e) { + return Qs(`${t}-${e}`, (()=>[[`datasets.${t}.elements.${e}`, `datasets.${t}`, `elements.${e}`, ""]])) + } + pluginScopeKeys(t) { + const e = t.id; + return Qs(`${this.type}-plugin-${e}`, (()=>[[`plugins.${e}`, ...t.additionalOptionScopes || []]])) + } + _cachedScopes(t, e) { + const i = this._scopeCache; + let s = i.get(t); + return s && !e || (s = new Map, + i.set(t, s)), + s + } + getOptionScopes(t, e, i) { + const {options: s, type: n} = this + , o = this._cachedScopes(t, i) + , a = o.get(e); + if (a) + return a; + const r = new Set; + e.forEach((e=>{ + t && (r.add(t), + e.forEach((e=>tn(r, t, e)))), + e.forEach((t=>tn(r, s, t))), + e.forEach((t=>tn(r, mt[n] || {}, t))), + e.forEach((t=>tn(r, yt, t))), + e.forEach((t=>tn(r, bt, t))) + } + )); + const l = Array.from(r); + return 0 === l.length && l.push(Object.create(null)), + Js.has(e) && o.set(e, l), + l + } + chartOptionScopes() { + const {options: t, type: e} = this; + return [t, mt[e] || {}, yt.datasets[e] || {}, { + type: e + }, yt, bt] + } + resolveNamedOptions(t, e, i, s=[""]) { + const n = { + $shared: !0 + } + , {resolver: o, subPrefixes: a} = sn(this._resolverCache, t, s); + let r = o; + if (function(t, e) { + const {isScriptable: i, isIndexable: s} = Le(t); + for (const n of e) { + const e = i(n) + , o = s(n) + , a = (o || e) && t[n]; + if (e && (ft(a) || nn(a)) || o && X(a)) + return !0 + } + return !1 + }(o, e)) { + n.$shared = !1; + r = Te(o, i = ft(i) ? i() : i, this.createResolver(t, i, a)) + } + for (const t of e) + n[t] = r[t]; + return n + } + createResolver(t, e, i=[""], s) { + const {resolver: n} = sn(this._resolverCache, t, i); + return q(e) ? Te(n, e, void 0, s) : n + } + } + function sn(t, e, i) { + let s = t.get(e); + s || (s = new Map, + t.set(e, s)); + const n = i.join(); + let o = s.get(n); + if (!o) { + o = { + resolver: Ae(e, i), + subPrefixes: i.filter((t=>!t.toLowerCase().includes("hover"))) + }, + s.set(n, o) + } + return o + } + const nn = t=>q(t) && Object.getOwnPropertyNames(t).reduce(((e,i)=>e || ft(t[i])), !1); + const on = ["top", "bottom", "left", "right", "chartArea"]; + function an(t, e) { + return "top" === t || "bottom" === t || -1 === on.indexOf(t) && "x" === e + } + function rn(t, e) { + return function(i, s) { + return i[t] === s[t] ? i[e] - s[e] : i[t] - s[t] + } + } + function ln(t) { + const e = t.chart + , i = e.options.animation; + e.notifyPlugins("afterRender"), + tt(i && i.onComplete, [t], e) + } + function hn(t) { + const e = t.chart + , i = e.options.animation; + tt(i && i.onProgress, [t], e) + } + function cn(t) { + return ie() && "string" == typeof t ? t = document.getElementById(t) : t && t.length && (t = t[0]), + t && t.canvas && (t = t.canvas), + t + } + const dn = {} + , un = t=>{ + const e = cn(t); + return Object.values(dn).filter((t=>t.canvas === e)).pop() + } + ; + function fn(t, e, i) { + const s = Object.keys(t); + for (const n of s) { + const s = +n; + if (s >= e) { + const o = t[n]; + delete t[n], + (i > 0 || s > e) && (t[s + i] = o) + } + } + } + class gn { + constructor(t, e) { + const s = this.config = new en(e) + , n = cn(t) + , o = un(n); + if (o) + throw new Error("Canvas is already in use. Chart with ID '" + o.id + "' must be destroyed before the canvas with ID '" + o.canvas.id + "' can be reused."); + const r = s.createResolver(s.chartOptionScopes(), this.getContext()); + this.platform = new (s.platform || ds(n)), + this.platform.updateConfig(s); + const l = this.platform.acquireContext(n, r.aspectRatio) + , h = l && l.canvas + , c = h && h.height + , d = h && h.width; + this.id = Y(), + this.ctx = l, + this.canvas = h, + this.width = d, + this.height = c, + this._options = r, + this._aspectRatio = this.aspectRatio, + this._layers = [], + this._metasets = [], + this._stacks = void 0, + this.boxes = [], + this.currentDevicePixelRatio = void 0, + this.chartArea = void 0, + this._active = [], + this._lastEvent = void 0, + this._listeners = {}, + this._responsiveListeners = void 0, + this._sortedMetasets = [], + this.scales = {}, + this._plugins = new $s, + this.$proxies = {}, + this._hiddenIndices = {}, + this.attached = !1, + this._animationsDisabled = void 0, + this.$context = void 0, + this._doResize = i((t=>this.update(t)), r.resizeDelay || 0), + this._dataChanges = [], + dn[this.id] = this, + l && h ? (a.listen(this, "complete", ln), + a.listen(this, "progress", hn), + this._initialize(), + this.attached && this.update()) : console.error("Failed to create chart: can't acquire context from the given item") + } + get aspectRatio() { + const {options: {aspectRatio: t, maintainAspectRatio: e}, width: i, height: s, _aspectRatio: n} = this; + return U(t) ? e && n ? n : s ? i / s : null : t + } + get data() { + return this.config.data + } + set data(t) { + this.config.data = t + } + get options() { + return this._options + } + set options(t) { + this.config.options = t + } + _initialize() { + return this.notifyPlugins("beforeInit"), + this.options.responsive ? this.resize() : ue(this, this.options.devicePixelRatio), + this.bindEvents(), + this.notifyPlugins("afterInit"), + this + } + clear() { + return _e(this.canvas, this.ctx), + this + } + stop() { + return a.stop(this), + this + } + resize(t, e) { + a.running(this) ? this._resizeBeforeDraw = { + width: t, + height: e + } : this._resize(t, e) + } + _resize(t, e) { + const i = this.options + , s = this.canvas + , n = i.maintainAspectRatio && this.aspectRatio + , o = this.platform.getMaximumSize(s, t, e, n) + , a = i.devicePixelRatio || this.platform.getDevicePixelRatio() + , r = this.width ? "resize" : "attach"; + this.width = o.width, + this.height = o.height, + this._aspectRatio = this.aspectRatio, + ue(this, a, !0) && (this.notifyPlugins("resize", { + size: o + }), + tt(i.onResize, [this, o], this), + this.attached && this._doResize(r) && this.render()) + } + ensureScalesHaveIDs() { + et(this.options.scales || {}, ((t,e)=>{ + t.id = e + } + )) + } + buildOrUpdateScales() { + const t = this.options + , e = t.scales + , i = this.scales + , s = Object.keys(i).reduce(((t,e)=>(t[e] = !1, + t)), {}); + let n = []; + e && (n = n.concat(Object.keys(e).map((t=>{ + const i = e[t] + , s = qs(t, i) + , n = "r" === s + , o = "x" === s; + return { + options: i, + dposition: n ? "chartArea" : o ? "bottom" : "left", + dtype: n ? "radialLinear" : o ? "category" : "linear" + } + } + )))), + et(n, (e=>{ + const n = e.options + , o = n.id + , a = qs(o, n) + , r = Z(n.type, e.dtype); + void 0 !== n.position && an(n.position, a) === an(e.dposition) || (n.position = e.dposition), + s[o] = !0; + let l = null; + if (o in i && i[o].type === r) + l = i[o]; + else { + l = new (Hs.getScale(r))({ + id: o, + type: r, + ctx: this.ctx, + chart: this + }), + i[l.id] = l + } + l.init(n, t) + } + )), + et(s, ((t,e)=>{ + t || delete i[e] + } + )), + et(i, (t=>{ + qi.configure(this, t, t.options), + qi.addBox(this, t) + } + )) + } + _updateMetasets() { + const t = this._metasets + , e = this.data.datasets.length + , i = t.length; + if (t.sort(((t,e)=>t.index - e.index)), + i > e) { + for (let t = e; t < i; ++t) + this._destroyDatasetMeta(t); + t.splice(e, i - e) + } + this._sortedMetasets = t.slice(0).sort(rn("order", "index")) + } + _removeUnreferencedMetasets() { + const {_metasets: t, data: {datasets: e}} = this; + t.length > e.length && delete this._stacks, + t.forEach(((t,i)=>{ + 0 === e.filter((e=>e === t._dataset)).length && this._destroyDatasetMeta(i) + } + )) + } + buildOrUpdateControllers() { + const t = [] + , e = this.data.datasets; + let i, s; + for (this._removeUnreferencedMetasets(), + i = 0, + s = e.length; i < s; i++) { + const s = e[i]; + let n = this.getDatasetMeta(i); + const o = s.type || this.config.type; + if (n.type && n.type !== o && (this._destroyDatasetMeta(i), + n = this.getDatasetMeta(i)), + n.type = o, + n.indexAxis = s.indexAxis || Xs(o, this.options), + n.order = s.order || 0, + n.index = i, + n.label = "" + s.label, + n.visible = this.isDatasetVisible(i), + n.controller) + n.controller.updateIndex(i), + n.controller.linkScales(); + else { + const e = Hs.getController(o) + , {datasetElementType: s, dataElementType: a} = yt.datasets[o]; + Object.assign(e.prototype, { + dataElementType: Hs.getElement(a), + datasetElementType: s && Hs.getElement(s) + }), + n.controller = new e(this,i), + t.push(n.controller) + } + } + return this._updateMetasets(), + t + } + _resetElements() { + et(this.data.datasets, ((t,e)=>{ + this.getDatasetMeta(e).controller.reset() + } + ), this) + } + reset() { + this._resetElements(), + this.notifyPlugins("reset") + } + update(t) { + const e = this.config; + e.update(); + const i = this._options = e.createResolver(e.chartOptionScopes(), this.getContext()) + , s = this._animationsDisabled = !i.animation; + if (this._updateScales(), + this._checkEventBindings(), + this._updateHiddenIndices(), + this._plugins.invalidate(), + !1 === this.notifyPlugins("beforeUpdate", { + mode: t, + cancelable: !0 + })) + return; + const n = this.buildOrUpdateControllers(); + this.notifyPlugins("beforeElementsUpdate"); + let o = 0; + for (let t = 0, e = this.data.datasets.length; t < e; t++) { + const {controller: e} = this.getDatasetMeta(t) + , i = !s && -1 === n.indexOf(e); + e.buildOrUpdateElements(i), + o = Math.max(+e.getMaxOverflow(), o) + } + o = this._minPadding = i.layout.autoPadding ? o : 0, + this._updateLayout(o), + s || et(n, (t=>{ + t.reset() + } + )), + this._updateDatasets(t), + this.notifyPlugins("afterUpdate", { + mode: t + }), + this._layers.sort(rn("z", "_idx")); + const {_active: a, _lastEvent: r} = this; + r ? this._eventHandler(r, !0) : a.length && this._updateHoverStyles(a, a, !0), + this.render() + } + _updateScales() { + et(this.scales, (t=>{ + qi.removeBox(this, t) + } + )), + this.ensureScalesHaveIDs(), + this.buildOrUpdateScales() + } + _checkEventBindings() { + const t = this.options + , e = new Set(Object.keys(this._listeners)) + , i = new Set(t.events); + gt(e, i) && !!this._responsiveListeners === t.responsive || (this.unbindEvents(), + this.bindEvents()) + } + _updateHiddenIndices() { + const {_hiddenIndices: t} = this + , e = this._getUniformDataChanges() || []; + for (const {method: i, start: s, count: n} of e) { + fn(t, s, "_removeElements" === i ? -n : n) + } + } + _getUniformDataChanges() { + const t = this._dataChanges; + if (!t || !t.length) + return; + this._dataChanges = []; + const e = this.data.datasets.length + , i = e=>new Set(t.filter((t=>t[0] === e)).map(((t,e)=>e + "," + t.splice(1).join(",")))) + , s = i(0); + for (let t = 1; t < e; t++) + if (!gt(s, i(t))) + return; + return Array.from(s).map((t=>t.split(","))).map((t=>({ + method: t[1], + start: +t[2], + count: +t[3] + }))) + } + _updateLayout(t) { + if (!1 === this.notifyPlugins("beforeLayout", { + cancelable: !0 + })) + return; + qi.update(this, this.width, this.height, t); + const e = this.chartArea + , i = e.width <= 0 || e.height <= 0; + this._layers = [], + et(this.boxes, (t=>{ + i && "chartArea" === t.position || (t.configure && t.configure(), + this._layers.push(...t._layers())) + } + ), this), + this._layers.forEach(((t,e)=>{ + t._idx = e + } + )), + this.notifyPlugins("afterLayout") + } + _updateDatasets(t) { + if (!1 !== this.notifyPlugins("beforeDatasetsUpdate", { + mode: t, + cancelable: !0 + })) { + for (let t = 0, e = this.data.datasets.length; t < e; ++t) + this.getDatasetMeta(t).controller.configure(); + for (let e = 0, i = this.data.datasets.length; e < i; ++e) + this._updateDataset(e, ft(t) ? t({ + datasetIndex: e + }) : t); + this.notifyPlugins("afterDatasetsUpdate", { + mode: t + }) + } + } + _updateDataset(t, e) { + const i = this.getDatasetMeta(t) + , s = { + meta: i, + index: t, + mode: e, + cancelable: !0 + }; + !1 !== this.notifyPlugins("beforeDatasetUpdate", s) && (i.controller._update(e), + s.cancelable = !1, + this.notifyPlugins("afterDatasetUpdate", s)) + } + render() { + !1 !== this.notifyPlugins("beforeRender", { + cancelable: !0 + }) && (a.has(this) ? this.attached && !a.running(this) && a.start(this) : (this.draw(), + ln({ + chart: this + }))) + } + draw() { + let t; + if (this._resizeBeforeDraw) { + const {width: t, height: e} = this._resizeBeforeDraw; + this._resize(t, e), + this._resizeBeforeDraw = null + } + if (this.clear(), + this.width <= 0 || this.height <= 0) + return; + if (!1 === this.notifyPlugins("beforeDraw", { + cancelable: !0 + })) + return; + const e = this._layers; + for (t = 0; t < e.length && e[t].z <= 0; ++t) + e[t].draw(this.chartArea); + for (this._drawDatasets(); t < e.length; ++t) + e[t].draw(this.chartArea); + this.notifyPlugins("afterDraw") + } + _getSortedDatasetMetas(t) { + const e = this._sortedMetasets + , i = []; + let s, n; + for (s = 0, + n = e.length; s < n; ++s) { + const n = e[s]; + t && !n.visible || i.push(n) + } + return i + } + getSortedVisibleDatasetMetas() { + return this._getSortedDatasetMetas(!0) + } + _drawDatasets() { + if (!1 === this.notifyPlugins("beforeDatasetsDraw", { + cancelable: !0 + })) + return; + const t = this.getSortedVisibleDatasetMetas(); + for (let e = t.length - 1; e >= 0; --e) + this._drawDataset(t[e]); + this.notifyPlugins("afterDatasetsDraw") + } + _drawDataset(t) { + const e = this.ctx + , i = t._clip + , s = !i.disabled + , n = this.chartArea + , o = { + meta: t, + index: t.index, + cancelable: !0 + }; + !1 !== this.notifyPlugins("beforeDatasetDraw", o) && (s && Me(e, { + left: !1 === i.left ? 0 : n.left - i.left, + right: !1 === i.right ? this.width : n.right + i.right, + top: !1 === i.top ? 0 : n.top - i.top, + bottom: !1 === i.bottom ? this.height : n.bottom + i.bottom + }), + t.controller.draw(), + s && ke(e), + o.cancelable = !1, + this.notifyPlugins("afterDatasetDraw", o)) + } + isPointInArea(t) { + return we(t, this.chartArea, this._minPadding) + } + getElementsAtEventForMode(t, e, i, s) { + const n = Ii.modes[e]; + return "function" == typeof n ? n(this, t, i, s) : [] + } + getDatasetMeta(t) { + const e = this.data.datasets[t] + , i = this._metasets; + let s = i.filter((t=>t && t._dataset === e)).pop(); + return s || (s = { + type: null, + data: [], + dataset: null, + controller: null, + hidden: null, + xAxisID: null, + yAxisID: null, + order: e && e.order || 0, + index: t, + _dataset: e, + _parsed: [], + _sorted: !1 + }, + i.push(s)), + s + } + getContext() { + return this.$context || (this.$context = mi(null, { + chart: this, + type: "chart" + })) + } + getVisibleDatasetCount() { + return this.getSortedVisibleDatasetMetas().length + } + isDatasetVisible(t) { + const e = this.data.datasets[t]; + if (!e) + return !1; + const i = this.getDatasetMeta(t); + return "boolean" == typeof i.hidden ? !i.hidden : !e.hidden + } + setDatasetVisibility(t, e) { + this.getDatasetMeta(t).hidden = !e + } + toggleDataVisibility(t) { + this._hiddenIndices[t] = !this._hiddenIndices[t] + } + getDataVisibility(t) { + return !this._hiddenIndices[t] + } + _updateVisibility(t, e, i) { + const s = i ? "show" : "hide" + , n = this.getDatasetMeta(t) + , o = n.controller._resolveAnimations(void 0, s); + ut(e) ? (n.data[e].hidden = !i, + this.update()) : (this.setDatasetVisibility(t, i), + o.update(n, { + visible: i + }), + this.update((e=>e.datasetIndex === t ? s : void 0))) + } + hide(t, e) { + this._updateVisibility(t, e, !1) + } + show(t, e) { + this._updateVisibility(t, e, !0) + } + _destroyDatasetMeta(t) { + const e = this._metasets[t]; + e && e.controller && e.controller._destroy(), + delete this._metasets[t] + } + _stop() { + let t, e; + for (this.stop(), + a.remove(this), + t = 0, + e = this.data.datasets.length; t < e; ++t) + this._destroyDatasetMeta(t) + } + destroy() { + this.notifyPlugins("beforeDestroy"); + const {canvas: t, ctx: e} = this; + this._stop(), + this.config.clearCache(), + t && (this.unbindEvents(), + _e(t, e), + this.platform.releaseContext(e), + this.canvas = null, + this.ctx = null), + this.notifyPlugins("destroy"), + delete dn[this.id], + this.notifyPlugins("afterDestroy") + } + toBase64Image(...t) { + return this.canvas.toDataURL(...t) + } + bindEvents() { + this.bindUserEvents(), + this.options.responsive ? this.bindResponsiveEvents() : this.attached = !0 + } + bindUserEvents() { + const t = this._listeners + , e = this.platform + , i = (i,s)=>{ + e.addEventListener(this, i, s), + t[i] = s + } + , s = (t,e,i)=>{ + t.offsetX = e, + t.offsetY = i, + this._eventHandler(t) + } + ; + et(this.options.events, (t=>i(t, s))) + } + bindResponsiveEvents() { + this._responsiveListeners || (this._responsiveListeners = {}); + const t = this._responsiveListeners + , e = this.platform + , i = (i,s)=>{ + e.addEventListener(this, i, s), + t[i] = s + } + , s = (i,s)=>{ + t[i] && (e.removeEventListener(this, i, s), + delete t[i]) + } + , n = (t,e)=>{ + this.canvas && this.resize(t, e) + } + ; + let o; + const a = ()=>{ + s("attach", a), + this.attached = !0, + this.resize(), + i("resize", n), + i("detach", o) + } + ; + o = ()=>{ + this.attached = !1, + s("resize", n), + this._stop(), + this._resize(0, 0), + i("attach", a) + } + , + e.isAttached(this.canvas) ? a() : o() + } + unbindEvents() { + et(this._listeners, ((t,e)=>{ + this.platform.removeEventListener(this, e, t) + } + )), + this._listeners = {}, + et(this._responsiveListeners, ((t,e)=>{ + this.platform.removeEventListener(this, e, t) + } + )), + this._responsiveListeners = void 0 + } + updateHoverStyle(t, e, i) { + const s = i ? "set" : "remove"; + let n, o, a, r; + for ("dataset" === e && (n = this.getDatasetMeta(t[0].datasetIndex), + n.controller["_" + s + "DatasetHoverStyle"]()), + a = 0, + r = t.length; a < r; ++a) { + o = t[a]; + const e = o && this.getDatasetMeta(o.datasetIndex).controller; + e && e[s + "HoverStyle"](o.element, o.datasetIndex, o.index) + } + } + getActiveElements() { + return this._active || [] + } + setActiveElements(t) { + const e = this._active || [] + , i = t.map((({datasetIndex: t, index: e})=>{ + const i = this.getDatasetMeta(t); + if (!i) + throw new Error("No dataset found at index " + t); + return { + datasetIndex: t, + element: i.data[e], + index: e + } + } + )); + !it(i, e) && (this._active = i, + this._lastEvent = null, + this._updateHoverStyles(i, e)) + } + notifyPlugins(t, e, i) { + return this._plugins.notify(this, t, e, i) + } + _updateHoverStyles(t, e, i) { + const s = this.options.hover + , n = (t,e)=>t.filter((t=>!e.some((e=>t.datasetIndex === e.datasetIndex && t.index === e.index)))) + , o = n(e, t) + , a = i ? t : n(t, e); + o.length && this.updateHoverStyle(o, s.mode, !1), + a.length && s.mode && this.updateHoverStyle(a, s.mode, !0) + } + _eventHandler(t, e) { + const i = { + event: t, + replay: e, + cancelable: !0, + inChartArea: this.isPointInArea(t) + } + , s = e=>(e.options.events || this.options.events).includes(t.native.type); + if (!1 === this.notifyPlugins("beforeEvent", i, s)) + return; + const n = this._handleEvent(t, e, i.inChartArea); + return i.cancelable = !1, + this.notifyPlugins("afterEvent", i, s), + (n || i.changed) && this.render(), + this + } + _handleEvent(t, e, i) { + const {_active: s=[], options: n} = this + , o = e + , a = this._getActiveElements(t, s, i, o) + , r = pt(t) + , l = function(t, e, i, s) { + return i && "mouseout" !== t.type ? s ? e : t : null + }(t, this._lastEvent, i, r); + i && (this._lastEvent = null, + tt(n.onHover, [t, a, this], this), + r && tt(n.onClick, [t, a, this], this)); + const h = !it(a, s); + return (h || e) && (this._active = a, + this._updateHoverStyles(a, s, e)), + this._lastEvent = l, + h + } + _getActiveElements(t, e, i, s) { + if ("mouseout" === t.type) + return []; + if (!i) + return e; + const n = this.options.hover; + return this.getElementsAtEventForMode(t, n.mode, n, s) + } + } + const pn = ()=>et(gn.instances, (t=>t._plugins.invalidate())) + , mn = !0; + function bn() { + throw new Error("This method is not implemented: Check that a complete date adapter is provided.") + } + Object.defineProperties(gn, { + defaults: { + enumerable: mn, + value: yt + }, + instances: { + enumerable: mn, + value: dn + }, + overrides: { + enumerable: mn, + value: mt + }, + registry: { + enumerable: mn, + value: Hs + }, + version: { + enumerable: mn, + value: "3.8.2" + }, + getChart: { + enumerable: mn, + value: un + }, + register: { + enumerable: mn, + value: (...t)=>{ + Hs.add(...t), + pn() + } + }, + unregister: { + enumerable: mn, + value: (...t)=>{ + Hs.remove(...t), + pn() + } + } + }); + class xn { + constructor(t) { + this.options = t || {} + } + formats() { + return bn() + } + parse(t, e) { + return bn() + } + format(t, e) { + return bn() + } + add(t, e, i) { + return bn() + } + diff(t, e, i) { + return bn() + } + startOf(t, e, i) { + return bn() + } + endOf(t, e) { + return bn() + } + } + xn.override = function(t) { + Object.assign(xn.prototype, t) + } + ; + var _n = { + _date: xn + }; + function yn(t) { + const e = t.iScale + , i = function(t, e) { + if (!t._cache.$bar) { + const i = t.getMatchingVisibleMetas(e); + let s = []; + for (let e = 0, n = i.length; e < n; e++) + s = s.concat(i[e].controller.getAllParsedValues(t)); + t._cache.$bar = Ct(s.sort(((t,e)=>t - e))) + } + return t._cache.$bar + }(e, t.type); + let s, n, o, a, r = e._length; + const l = ()=>{ + 32767 !== o && -32768 !== o && (ut(a) && (r = Math.min(r, Math.abs(o - a) || r)), + a = o) + } + ; + for (s = 0, + n = i.length; s < n; ++s) + o = e.getPixelForValue(i[s]), + l(); + for (a = void 0, + s = 0, + n = e.ticks.length; s < n; ++s) + o = e.getPixelForTick(s), + l(); + return r + } + function vn(t, e, i, s) { + return X(t) ? function(t, e, i, s) { + const n = i.parse(t[0], s) + , o = i.parse(t[1], s) + , a = Math.min(n, o) + , r = Math.max(n, o); + let l = a + , h = r; + Math.abs(a) > Math.abs(r) && (l = r, + h = a), + e[i.axis] = h, + e._custom = { + barStart: l, + barEnd: h, + start: n, + end: o, + min: a, + max: r + } + }(t, e, i, s) : e[i.axis] = i.parse(t, s), + e + } + function wn(t, e, i, s) { + const n = t.iScale + , o = t.vScale + , a = n.getLabels() + , r = n === o + , l = []; + let h, c, d, u; + for (h = i, + c = i + s; h < c; ++h) + u = e[h], + d = {}, + d[n.axis] = r || n.parse(a[h], h), + l.push(vn(u, d, o, h)); + return l + } + function Mn(t) { + return t && void 0 !== t.barStart && void 0 !== t.barEnd + } + function kn(t, e, i, s) { + let n = e.borderSkipped; + const o = {}; + if (!n) + return void (t.borderSkipped = o); + const {start: a, end: r, reverse: l, top: h, bottom: c} = function(t) { + let e, i, s, n, o; + return t.horizontal ? (e = t.base > t.x, + i = "left", + s = "right") : (e = t.base < t.y, + i = "bottom", + s = "top"), + e ? (n = "end", + o = "start") : (n = "start", + o = "end"), + { + start: i, + end: s, + reverse: e, + top: n, + bottom: o + } + }(t); + "middle" === n && i && (t.enableBorderRadius = !0, + (i._top || 0) === s ? n = h : (i._bottom || 0) === s ? n = c : (o[Sn(c, a, r, l)] = !0, + n = h)), + o[Sn(n, a, r, l)] = !0, + t.borderSkipped = o + } + function Sn(t, e, i, s) { + var n, o, a; + return s ? (a = i, + t = Pn(t = (n = t) === (o = e) ? a : n === a ? o : n, i, e)) : t = Pn(t, e, i), + t + } + function Pn(t, e, i) { + return "start" === t ? e : "end" === t ? i : t + } + function Dn(t, {inflateAmount: e}, i) { + t.inflateAmount = "auto" === e ? 1 === i ? .33 : 0 : e + } + class Cn extends Os { + parsePrimitiveData(t, e, i, s) { + return wn(t, e, i, s) + } + parseArrayData(t, e, i, s) { + return wn(t, e, i, s) + } + parseObjectData(t, e, i, s) { + const {iScale: n, vScale: o} = t + , {xAxisKey: a="x", yAxisKey: r="y"} = this._parsing + , l = "x" === n.axis ? a : r + , h = "x" === o.axis ? a : r + , c = []; + let d, u, f, g; + for (d = i, + u = i + s; d < u; ++d) + g = e[d], + f = {}, + f[n.axis] = n.parse(ct(g, l), d), + c.push(vn(ct(g, h), f, o, d)); + return c + } + updateRangeFromParsed(t, e, i, s) { + super.updateRangeFromParsed(t, e, i, s); + const n = i._custom; + n && e === this._cachedMeta.vScale && (t.min = Math.min(t.min, n.min), + t.max = Math.max(t.max, n.max)) + } + getMaxOverflow() { + return 0 + } + getLabelAndValue(t) { + const e = this._cachedMeta + , {iScale: i, vScale: s} = e + , n = this.getParsed(t) + , o = n._custom + , a = Mn(o) ? "[" + o.start + ", " + o.end + "]" : "" + s.getLabelForValue(n[s.axis]); + return { + label: "" + i.getLabelForValue(n[i.axis]), + value: a + } + } + initialize() { + this.enableOptionSharing = !0, + super.initialize(); + this._cachedMeta.stack = this.getDataset().stack + } + update(t) { + const e = this._cachedMeta; + this.updateElements(e.data, 0, e.data.length, t) + } + updateElements(t, e, i, s) { + const n = "reset" === s + , {index: o, _cachedMeta: {vScale: a}} = this + , r = a.getBasePixel() + , l = a.isHorizontal() + , h = this._getRuler() + , {sharedOptions: c, includeOptions: d} = this._getSharedOptions(e, s); + for (let u = e; u < e + i; u++) { + const e = this.getParsed(u) + , i = n || U(e[a.axis]) ? { + base: r, + head: r + } : this._calculateBarValuePixels(u) + , f = this._calculateBarIndexPixels(u, h) + , g = (e._stacks || {})[a.axis] + , p = { + horizontal: l, + base: i.base, + enableBorderRadius: !g || Mn(e._custom) || o === g._top || o === g._bottom, + x: l ? i.head : f.center, + y: l ? f.center : i.head, + height: l ? f.size : Math.abs(i.size), + width: l ? Math.abs(i.size) : f.size + }; + d && (p.options = c || this.resolveDataElementOptions(u, t[u].active ? "active" : s)); + const m = p.options || t[u].options; + kn(p, m, g, o), + Dn(p, m, h.ratio), + this.updateElement(t[u], u, p, s) + } + } + _getStacks(t, e) { + const {iScale: i} = this._cachedMeta + , s = i.getMatchingVisibleMetas(this._type).filter((t=>t.controller.options.grouped)) + , n = i.options.stacked + , o = [] + , a = t=>{ + const i = t.controller.getParsed(e) + , s = i && i[t.vScale.axis]; + if (U(s) || isNaN(s)) + return !0 + } + ; + for (const i of s) + if ((void 0 === e || !a(i)) && ((!1 === n || -1 === o.indexOf(i.stack) || void 0 === n && void 0 === i.stack) && o.push(i.stack), + i.index === t)) + break; + return o.length || o.push(void 0), + o + } + _getStackCount(t) { + return this._getStacks(void 0, t).length + } + _getStackIndex(t, e, i) { + const s = this._getStacks(t, i) + , n = void 0 !== e ? s.indexOf(e) : -1; + return -1 === n ? s.length - 1 : n + } + _getRuler() { + const t = this.options + , e = this._cachedMeta + , i = e.iScale + , s = []; + let n, o; + for (n = 0, + o = e.data.length; n < o; ++n) + s.push(i.getPixelForValue(this.getParsed(n)[i.axis], n)); + const a = t.barThickness; + return { + min: a || yn(e), + pixels: s, + start: i._startPixel, + end: i._endPixel, + stackCount: this._getStackCount(), + scale: i, + grouped: t.grouped, + ratio: a ? 1 : t.categoryPercentage * t.barPercentage + } + } + _calculateBarValuePixels(t) { + const {_cachedMeta: {vScale: e, _stacked: i}, options: {base: s, minBarLength: n}} = this + , o = s || 0 + , a = this.getParsed(t) + , r = a._custom + , l = Mn(r); + let h, c, d = a[e.axis], u = 0, f = i ? this.applyStack(e, a, i) : d; + f !== d && (u = f - d, + f = d), + l && (d = r.barStart, + f = r.barEnd - r.barStart, + 0 !== d && Bt(d) !== Bt(r.barEnd) && (u = 0), + u += d); + const g = U(s) || l ? u : s; + let p = e.getPixelForValue(g); + if (h = this.chart.getDataVisibility(t) ? e.getPixelForValue(u + f) : p, + c = h - p, + Math.abs(c) < n) { + c = function(t, e, i) { + return 0 !== t ? Bt(t) : (e.isHorizontal() ? 1 : -1) * (e.min >= i ? 1 : -1) + }(c, e, o) * n, + d === o && (p -= c / 2); + const t = e.getPixelForDecimal(0) + , i = e.getPixelForDecimal(1) + , s = Math.min(t, i) + , a = Math.max(t, i); + p = Math.max(Math.min(p, a), s), + h = p + c + } + if (p === e.getPixelForValue(o)) { + const t = Bt(c) * e.getLineWidthForValue(o) / 2; + p += t, + c -= t + } + return { + size: c, + base: p, + head: h, + center: h + c / 2 + } + } + _calculateBarIndexPixels(t, e) { + const i = e.scale + , s = this.options + , n = s.skipNull + , o = Z(s.maxBarThickness, 1 / 0); + let a, r; + if (e.grouped) { + const i = n ? this._getStackCount(t) : e.stackCount + , l = "flex" === s.barThickness ? function(t, e, i, s) { + const n = e.pixels + , o = n[t]; + let a = t > 0 ? n[t - 1] : null + , r = t < n.length - 1 ? n[t + 1] : null; + const l = i.categoryPercentage; + null === a && (a = o - (null === r ? e.end - e.start : r - o)), + null === r && (r = o + o - a); + const h = o - (o - Math.min(a, r)) / 2 * l; + return { + chunk: Math.abs(r - a) / 2 * l / s, + ratio: i.barPercentage, + start: h + } + }(t, e, s, i) : function(t, e, i, s) { + const n = i.barThickness; + let o, a; + return U(n) ? (o = e.min * i.categoryPercentage, + a = i.barPercentage) : (o = n * s, + a = 1), + { + chunk: o / s, + ratio: a, + start: e.pixels[t] - o / 2 + } + }(t, e, s, i) + , h = this._getStackIndex(this.index, this._cachedMeta.stack, n ? t : void 0); + a = l.start + l.chunk * h + l.chunk / 2, + r = Math.min(o, l.chunk * l.ratio) + } else + a = i.getPixelForValue(this.getParsed(t)[i.axis], t), + r = Math.min(o, e.min * e.ratio); + return { + base: a - r / 2, + head: a + r / 2, + center: a, + size: r + } + } + draw() { + const t = this._cachedMeta + , e = t.vScale + , i = t.data + , s = i.length; + let n = 0; + for (; n < s; ++n) + null !== this.getParsed(n)[e.axis] && i[n].draw(this._ctx) + } + } + Cn.id = "bar", + Cn.defaults = { + datasetElementType: !1, + dataElementType: "bar", + categoryPercentage: .8, + barPercentage: .9, + grouped: !0, + animations: { + numbers: { + type: "number", + properties: ["x", "y", "base", "width", "height"] + } + } + }, + Cn.overrides = { + scales: { + _index_: { + type: "category", + offset: !0, + grid: { + offset: !0 + } + }, + _value_: { + type: "linear", + beginAtZero: !0 + } + } + }; + class On extends Os { + initialize() { + this.enableOptionSharing = !0, + super.initialize() + } + parsePrimitiveData(t, e, i, s) { + const n = super.parsePrimitiveData(t, e, i, s); + for (let t = 0; t < n.length; t++) + n[t]._custom = this.resolveDataElementOptions(t + i).radius; + return n + } + parseArrayData(t, e, i, s) { + const n = super.parseArrayData(t, e, i, s); + for (let t = 0; t < n.length; t++) { + const s = e[i + t]; + n[t]._custom = Z(s[2], this.resolveDataElementOptions(t + i).radius) + } + return n + } + parseObjectData(t, e, i, s) { + const n = super.parseObjectData(t, e, i, s); + for (let t = 0; t < n.length; t++) { + const s = e[i + t]; + n[t]._custom = Z(s && s.r && +s.r, this.resolveDataElementOptions(t + i).radius) + } + return n + } + getMaxOverflow() { + const t = this._cachedMeta.data; + let e = 0; + for (let i = t.length - 1; i >= 0; --i) + e = Math.max(e, t[i].size(this.resolveDataElementOptions(i)) / 2); + return e > 0 && e + } + getLabelAndValue(t) { + const e = this._cachedMeta + , {xScale: i, yScale: s} = e + , n = this.getParsed(t) + , o = i.getLabelForValue(n.x) + , a = s.getLabelForValue(n.y) + , r = n._custom; + return { + label: e.label, + value: "(" + o + ", " + a + (r ? ", " + r : "") + ")" + } + } + update(t) { + const e = this._cachedMeta.data; + this.updateElements(e, 0, e.length, t) + } + updateElements(t, e, i, s) { + const n = "reset" === s + , {iScale: o, vScale: a} = this._cachedMeta + , {sharedOptions: r, includeOptions: l} = this._getSharedOptions(e, s) + , h = o.axis + , c = a.axis; + for (let d = e; d < e + i; d++) { + const e = t[d] + , i = !n && this.getParsed(d) + , u = {} + , f = u[h] = n ? o.getPixelForDecimal(.5) : o.getPixelForValue(i[h]) + , g = u[c] = n ? a.getBasePixel() : a.getPixelForValue(i[c]); + u.skip = isNaN(f) || isNaN(g), + l && (u.options = r || this.resolveDataElementOptions(d, e.active ? "active" : s), + n && (u.options.radius = 0)), + this.updateElement(e, d, u, s) + } + } + resolveDataElementOptions(t, e) { + const i = this.getParsed(t); + let s = super.resolveDataElementOptions(t, e); + s.$shared && (s = Object.assign({}, s, { + $shared: !1 + })); + const n = s.radius; + return "active" !== e && (s.radius = 0), + s.radius += Z(i && i._custom, n), + s + } + } + On.id = "bubble", + On.defaults = { + datasetElementType: !1, + dataElementType: "point", + animations: { + numbers: { + type: "number", + properties: ["x", "y", "borderWidth", "radius"] + } + } + }, + On.overrides = { + scales: { + x: { + type: "linear" + }, + y: { + type: "linear" + } + }, + plugins: { + tooltip: { + callbacks: { + title: ()=>"" + } + } + } + }; + class An extends Os { + constructor(t, e) { + super(t, e), + this.enableOptionSharing = !0, + this.innerRadius = void 0, + this.outerRadius = void 0, + this.offsetX = void 0, + this.offsetY = void 0 + } + linkScales() {} + parse(t, e) { + const i = this.getDataset().data + , s = this._cachedMeta; + if (!1 === this._parsing) + s._parsed = i; + else { + let n, o, a = t=>+i[t]; + if (q(i[t])) { + const {key: t="value"} = this._parsing; + a = e=>+ct(i[e], t) + } + for (n = t, + o = t + e; n < o; ++n) + s._parsed[n] = a(n) + } + } + _getRotation() { + return Yt(this.options.rotation - 90) + } + _getCircumference() { + return Yt(this.options.circumference) + } + _getRotationExtents() { + let t = At + , e = -At; + for (let i = 0; i < this.chart.data.datasets.length; ++i) + if (this.chart.isDatasetVisible(i)) { + const s = this.chart.getDatasetMeta(i).controller + , n = s._getRotation() + , o = s._getCircumference(); + t = Math.min(t, n), + e = Math.max(e, n + o) + } + return { + rotation: t, + circumference: e - t + } + } + update(t) { + const e = this.chart + , {chartArea: i} = e + , s = this._cachedMeta + , n = s.data + , o = this.getMaxBorderWidth() + this.getMaxOffset(n) + this.options.spacing + , a = Math.max((Math.min(i.width, i.height) - o) / 2, 0) + , r = Math.min(J(this.options.cutout, a), 1) + , l = this._getRingWeight(this.index) + , {circumference: h, rotation: c} = this._getRotationExtents() + , {ratioX: d, ratioY: u, offsetX: f, offsetY: g} = function(t, e, i) { + let s = 1 + , n = 1 + , o = 0 + , a = 0; + if (e < At) { + const r = t + , l = r + e + , h = Math.cos(r) + , c = Math.sin(r) + , d = Math.cos(l) + , u = Math.sin(l) + , f = (t,e,s)=>Jt(t, r, l, !0) ? 1 : Math.max(e, e * i, s, s * i) + , g = (t,e,s)=>Jt(t, r, l, !0) ? -1 : Math.min(e, e * i, s, s * i) + , p = f(0, h, d) + , m = f(Et, c, u) + , b = g(Ot, h, d) + , x = g(Ot + Et, c, u); + s = (p - b) / 2, + n = (m - x) / 2, + o = -(p + b) / 2, + a = -(m + x) / 2 + } + return { + ratioX: s, + ratioY: n, + offsetX: o, + offsetY: a + } + }(c, h, r) + , p = (i.width - o) / d + , m = (i.height - o) / u + , b = Math.max(Math.min(p, m) / 2, 0) + , x = Q(this.options.radius, b) + , _ = (x - Math.max(x * r, 0)) / this._getVisibleDatasetWeightTotal(); + this.offsetX = f * x, + this.offsetY = g * x, + s.total = this.calculateTotal(), + this.outerRadius = x - _ * this._getRingWeightOffset(this.index), + this.innerRadius = Math.max(this.outerRadius - _ * l, 0), + this.updateElements(n, 0, n.length, t) + } + _circumference(t, e) { + const i = this.options + , s = this._cachedMeta + , n = this._getCircumference(); + return e && i.animation.animateRotate || !this.chart.getDataVisibility(t) || null === s._parsed[t] || s.data[t].hidden ? 0 : this.calculateCircumference(s._parsed[t] * n / At) + } + updateElements(t, e, i, s) { + const n = "reset" === s + , o = this.chart + , a = o.chartArea + , r = o.options.animation + , l = (a.left + a.right) / 2 + , h = (a.top + a.bottom) / 2 + , c = n && r.animateScale + , d = c ? 0 : this.innerRadius + , u = c ? 0 : this.outerRadius + , {sharedOptions: f, includeOptions: g} = this._getSharedOptions(e, s); + let p, m = this._getRotation(); + for (p = 0; p < e; ++p) + m += this._circumference(p, n); + for (p = e; p < e + i; ++p) { + const e = this._circumference(p, n) + , i = t[p] + , o = { + x: l + this.offsetX, + y: h + this.offsetY, + startAngle: m, + endAngle: m + e, + circumference: e, + outerRadius: u, + innerRadius: d + }; + g && (o.options = f || this.resolveDataElementOptions(p, i.active ? "active" : s)), + m += e, + this.updateElement(i, p, o, s) + } + } + calculateTotal() { + const t = this._cachedMeta + , e = t.data; + let i, s = 0; + for (i = 0; i < e.length; i++) { + const n = t._parsed[i]; + null === n || isNaN(n) || !this.chart.getDataVisibility(i) || e[i].hidden || (s += Math.abs(n)) + } + return s + } + calculateCircumference(t) { + const e = this._cachedMeta.total; + return e > 0 && !isNaN(t) ? At * (Math.abs(t) / e) : 0 + } + getLabelAndValue(t) { + const e = this._cachedMeta + , i = this.chart + , s = i.data.labels || [] + , n = oi(e._parsed[t], i.options.locale); + return { + label: s[t] || "", + value: n + } + } + getMaxBorderWidth(t) { + let e = 0; + const i = this.chart; + let s, n, o, a, r; + if (!t) + for (s = 0, + n = i.data.datasets.length; s < n; ++s) + if (i.isDatasetVisible(s)) { + o = i.getDatasetMeta(s), + t = o.data, + a = o.controller; + break + } + if (!t) + return 0; + for (s = 0, + n = t.length; s < n; ++s) + r = a.resolveDataElementOptions(s), + "inner" !== r.borderAlign && (e = Math.max(e, r.borderWidth || 0, r.hoverBorderWidth || 0)); + return e + } + getMaxOffset(t) { + let e = 0; + for (let i = 0, s = t.length; i < s; ++i) { + const t = this.resolveDataElementOptions(i); + e = Math.max(e, t.offset || 0, t.hoverOffset || 0) + } + return e + } + _getRingWeightOffset(t) { + let e = 0; + for (let i = 0; i < t; ++i) + this.chart.isDatasetVisible(i) && (e += this._getRingWeight(i)); + return e + } + _getRingWeight(t) { + return Math.max(Z(this.chart.data.datasets[t].weight, 1), 0) + } + _getVisibleDatasetWeightTotal() { + return this._getRingWeightOffset(this.chart.data.datasets.length) || 1 + } + } + An.id = "doughnut", + An.defaults = { + datasetElementType: !1, + dataElementType: "arc", + animation: { + animateRotate: !0, + animateScale: !1 + }, + animations: { + numbers: { + type: "number", + properties: ["circumference", "endAngle", "innerRadius", "outerRadius", "startAngle", "x", "y", "offset", "borderWidth", "spacing"] + } + }, + cutout: "50%", + rotation: 0, + circumference: 360, + radius: "100%", + spacing: 0, + indexAxis: "r" + }, + An.descriptors = { + _scriptable: t=>"spacing" !== t, + _indexable: t=>"spacing" !== t + }, + An.overrides = { + aspectRatio: 1, + plugins: { + legend: { + labels: { + generateLabels(t) { + const e = t.data; + if (e.labels.length && e.datasets.length) { + const {labels: {pointStyle: i}} = t.legend.options; + return e.labels.map(((e,s)=>{ + const n = t.getDatasetMeta(0).controller.getStyle(s); + return { + text: e, + fillStyle: n.backgroundColor, + strokeStyle: n.borderColor, + lineWidth: n.borderWidth, + pointStyle: i, + hidden: !t.getDataVisibility(s), + index: s + } + } + )) + } + return [] + } + }, + onClick(t, e, i) { + i.chart.toggleDataVisibility(e.index), + i.chart.update() + } + }, + tooltip: { + callbacks: { + title: ()=>"", + label(t) { + let e = t.label; + const i = ": " + t.formattedValue; + return X(e) ? (e = e.slice(), + e[0] += i) : e += i, + e + } + } + } + } + }; + class Tn extends Os { + initialize() { + this.enableOptionSharing = !0, + this.supportsDecimation = !0, + super.initialize() + } + update(t) { + const e = this._cachedMeta + , {dataset: i, data: s=[], _dataset: n} = e + , o = this.chart._animationsDisabled; + let {start: a, count: r} = function(t, e, i) { + const s = e.length; + let n = 0 + , o = s; + if (t._sorted) { + const {iScale: a, _parsed: r} = t + , l = a.axis + , {min: h, max: c, minDefined: d, maxDefined: u} = a.getUserBounds(); + d && (n = Qt(Math.min(wt(r, a.axis, h).lo, i ? s : wt(e, l, a.getPixelForValue(h)).lo), 0, s - 1)), + o = u ? Qt(Math.max(wt(r, a.axis, c).hi + 1, i ? 0 : wt(e, l, a.getPixelForValue(c)).hi + 1), n, s) - n : s - n + } + return { + start: n, + count: o + } + }(e, s, o); + this._drawStart = a, + this._drawCount = r, + function(t) { + const {xScale: e, yScale: i, _scaleRanges: s} = t + , n = { + xmin: e.min, + xmax: e.max, + ymin: i.min, + ymax: i.max + }; + if (!s) + return t._scaleRanges = n, + !0; + const o = s.xmin !== e.min || s.xmax !== e.max || s.ymin !== i.min || s.ymax !== i.max; + return Object.assign(s, n), + o + }(e) && (a = 0, + r = s.length), + i._chart = this.chart, + i._datasetIndex = this.index, + i._decimated = !!n._decimated, + i.points = s; + const l = this.resolveDatasetElementOptions(t); + this.options.showLine || (l.borderWidth = 0), + l.segment = this.options.segment, + this.updateElement(i, void 0, { + animated: !o, + options: l + }, t), + this.updateElements(s, a, r, t) + } + updateElements(t, e, i, s) { + const n = "reset" === s + , {iScale: o, vScale: a, _stacked: r, _dataset: l} = this._cachedMeta + , {sharedOptions: h, includeOptions: c} = this._getSharedOptions(e, s) + , d = o.axis + , u = a.axis + , {spanGaps: f, segment: g} = this.options + , p = Nt(f) ? f : Number.POSITIVE_INFINITY + , m = this.chart._animationsDisabled || n || "none" === s; + let b = e > 0 && this.getParsed(e - 1); + for (let f = e; f < e + i; ++f) { + const e = t[f] + , i = this.getParsed(f) + , x = m ? e : {} + , _ = U(i[u]) + , y = x[d] = o.getPixelForValue(i[d], f) + , v = x[u] = n || _ ? a.getBasePixel() : a.getPixelForValue(r ? this.applyStack(a, i, r) : i[u], f); + x.skip = isNaN(y) || isNaN(v) || _, + x.stop = f > 0 && Math.abs(i[d] - b[d]) > p, + g && (x.parsed = i, + x.raw = l.data[f]), + c && (x.options = h || this.resolveDataElementOptions(f, e.active ? "active" : s)), + m || this.updateElement(e, f, x, s), + b = i + } + } + getMaxOverflow() { + const t = this._cachedMeta + , e = t.dataset + , i = e.options && e.options.borderWidth || 0 + , s = t.data || []; + if (!s.length) + return i; + const n = s[0].size(this.resolveDataElementOptions(0)) + , o = s[s.length - 1].size(this.resolveDataElementOptions(s.length - 1)); + return Math.max(i, n, o) / 2 + } + draw() { + const t = this._cachedMeta; + t.dataset.updateControlPoints(this.chart.chartArea, t.iScale.axis), + super.draw() + } + } + Tn.id = "line", + Tn.defaults = { + datasetElementType: "line", + dataElementType: "point", + showLine: !0, + spanGaps: !1 + }, + Tn.overrides = { + scales: { + _index_: { + type: "category" + }, + _value_: { + type: "linear" + } + } + }; + class Ln extends Os { + constructor(t, e) { + super(t, e), + this.innerRadius = void 0, + this.outerRadius = void 0 + } + getLabelAndValue(t) { + const e = this._cachedMeta + , i = this.chart + , s = i.data.labels || [] + , n = oi(e._parsed[t].r, i.options.locale); + return { + label: s[t] || "", + value: n + } + } + parseObjectData(t, e, i, s) { + return He.bind(this)(t, e, i, s) + } + update(t) { + const e = this._cachedMeta.data; + this._updateRadius(), + this.updateElements(e, 0, e.length, t) + } + getMinMax() { + const t = this._cachedMeta + , e = { + min: Number.POSITIVE_INFINITY, + max: Number.NEGATIVE_INFINITY + }; + return t.data.forEach(((t,i)=>{ + const s = this.getParsed(i).r; + !isNaN(s) && this.chart.getDataVisibility(i) && (s < e.min && (e.min = s), + s > e.max && (e.max = s)) + } + )), + e + } + _updateRadius() { + const t = this.chart + , e = t.chartArea + , i = t.options + , s = Math.min(e.right - e.left, e.bottom - e.top) + , n = Math.max(s / 2, 0) + , o = (n - Math.max(i.cutoutPercentage ? n / 100 * i.cutoutPercentage : 1, 0)) / t.getVisibleDatasetCount(); + this.outerRadius = n - o * this.index, + this.innerRadius = this.outerRadius - o + } + updateElements(t, e, i, s) { + const n = "reset" === s + , o = this.chart + , a = o.options.animation + , r = this._cachedMeta.rScale + , l = r.xCenter + , h = r.yCenter + , c = r.getIndexAngle(0) - .5 * Ot; + let d, u = c; + const f = 360 / this.countVisibleElements(); + for (d = 0; d < e; ++d) + u += this._computeAngle(d, s, f); + for (d = e; d < e + i; d++) { + const e = t[d]; + let i = u + , g = u + this._computeAngle(d, s, f) + , p = o.getDataVisibility(d) ? r.getDistanceFromCenterForValue(this.getParsed(d).r) : 0; + u = g, + n && (a.animateScale && (p = 0), + a.animateRotate && (i = g = c)); + const m = { + x: l, + y: h, + innerRadius: 0, + outerRadius: p, + startAngle: i, + endAngle: g, + options: this.resolveDataElementOptions(d, e.active ? "active" : s) + }; + this.updateElement(e, d, m, s) + } + } + countVisibleElements() { + const t = this._cachedMeta; + let e = 0; + return t.data.forEach(((t,i)=>{ + !isNaN(this.getParsed(i).r) && this.chart.getDataVisibility(i) && e++ + } + )), + e + } + _computeAngle(t, e, i) { + return this.chart.getDataVisibility(t) ? Yt(this.resolveDataElementOptions(t, e).angle || i) : 0 + } + } + Ln.id = "polarArea", + Ln.defaults = { + dataElementType: "arc", + animation: { + animateRotate: !0, + animateScale: !0 + }, + animations: { + numbers: { + type: "number", + properties: ["x", "y", "startAngle", "endAngle", "innerRadius", "outerRadius"] + } + }, + indexAxis: "r", + startAngle: 0 + }, + Ln.overrides = { + aspectRatio: 1, + plugins: { + legend: { + labels: { + generateLabels(t) { + const e = t.data; + if (e.labels.length && e.datasets.length) { + const {labels: {pointStyle: i}} = t.legend.options; + return e.labels.map(((e,s)=>{ + const n = t.getDatasetMeta(0).controller.getStyle(s); + return { + text: e, + fillStyle: n.backgroundColor, + strokeStyle: n.borderColor, + lineWidth: n.borderWidth, + pointStyle: i, + hidden: !t.getDataVisibility(s), + index: s + } + } + )) + } + return [] + } + }, + onClick(t, e, i) { + i.chart.toggleDataVisibility(e.index), + i.chart.update() + } + }, + tooltip: { + callbacks: { + title: ()=>"", + label: t=>t.chart.data.labels[t.dataIndex] + ": " + t.formattedValue + } + } + }, + scales: { + r: { + type: "radialLinear", + angleLines: { + display: !1 + }, + beginAtZero: !0, + grid: { + circular: !0 + }, + pointLabels: { + display: !1 + }, + startAngle: 0 + } + } + }; + class Rn extends An { + } + Rn.id = "pie", + Rn.defaults = { + cutout: 0, + rotation: 0, + circumference: 360, + radius: "100%" + }; + class En extends Os { + getLabelAndValue(t) { + const e = this._cachedMeta.vScale + , i = this.getParsed(t); + return { + label: e.getLabels()[t], + value: "" + e.getLabelForValue(i[e.axis]) + } + } + parseObjectData(t, e, i, s) { + return He.bind(this)(t, e, i, s) + } + update(t) { + const e = this._cachedMeta + , i = e.dataset + , s = e.data || [] + , n = e.iScale.getLabels(); + if (i.points = s, + "resize" !== t) { + const e = this.resolveDatasetElementOptions(t); + this.options.showLine || (e.borderWidth = 0); + const o = { + _loop: !0, + _fullLoop: n.length === s.length, + options: e + }; + this.updateElement(i, void 0, o, t) + } + this.updateElements(s, 0, s.length, t) + } + updateElements(t, e, i, s) { + const n = this._cachedMeta.rScale + , o = "reset" === s; + for (let a = e; a < e + i; a++) { + const e = t[a] + , i = this.resolveDataElementOptions(a, e.active ? "active" : s) + , r = n.getPointPositionForValue(a, this.getParsed(a).r) + , l = o ? n.xCenter : r.x + , h = o ? n.yCenter : r.y + , c = { + x: l, + y: h, + angle: r.angle, + skip: isNaN(l) || isNaN(h), + options: i + }; + this.updateElement(e, a, c, s) + } + } + } + En.id = "radar", + En.defaults = { + datasetElementType: "line", + dataElementType: "point", + indexAxis: "r", + showLine: !0, + elements: { + line: { + fill: "start" + } + } + }, + En.overrides = { + aspectRatio: 1, + scales: { + r: { + type: "radialLinear" + } + } + }; + class In extends Tn { + } + In.id = "scatter", + In.defaults = { + showLine: !1, + fill: !1 + }, + In.overrides = { + interaction: { + mode: "point" + }, + plugins: { + tooltip: { + callbacks: { + title: ()=>"", + label: t=>"(" + t.label + ", " + t.formattedValue + ")" + } + } + }, + scales: { + x: { + type: "linear" + }, + y: { + type: "linear" + } + } + }; + var zn = Object.freeze({ + __proto__: null, + BarController: Cn, + BubbleController: On, + DoughnutController: An, + LineController: Tn, + PolarAreaController: Ln, + PieController: Rn, + RadarController: En, + ScatterController: In + }); + function Fn(t, e, i) { + const {startAngle: s, pixelMargin: n, x: o, y: a, outerRadius: r, innerRadius: l} = e; + let h = n / r; + t.beginPath(), + t.arc(o, a, r, s - h, i + h), + l > n ? (h = n / l, + t.arc(o, a, l, i + h, s - h, !0)) : t.arc(o, a, n, i + Et, s - Et), + t.closePath(), + t.clip() + } + function Bn(t, e, i, s) { + const n = hi(t.options.borderRadius, ["outerStart", "outerEnd", "innerStart", "innerEnd"]); + const o = (i - e) / 2 + , a = Math.min(o, s * e / 2) + , r = t=>{ + const e = (i - Math.min(o, t)) * s / 2; + return Qt(t, 0, Math.min(o, e)) + } + ; + return { + outerStart: r(n.outerStart), + outerEnd: r(n.outerEnd), + innerStart: Qt(n.innerStart, 0, a), + innerEnd: Qt(n.innerEnd, 0, a) + } + } + function Vn(t, e, i, s) { + return { + x: i + t * Math.cos(e), + y: s + t * Math.sin(e) + } + } + function Wn(t, e, i, s, n) { + const {x: o, y: a, startAngle: r, pixelMargin: l, innerRadius: h} = e + , c = Math.max(e.outerRadius + s + i - l, 0) + , d = h > 0 ? h + s + i + l : 0; + let u = 0; + const f = n - r; + if (s) { + const t = ((h > 0 ? h - s : 0) + (c > 0 ? c - s : 0)) / 2; + u = (f - (0 !== t ? f * t / (t + s) : f)) / 2 + } + const g = (f - Math.max(.001, f * c - i / Ot) / c) / 2 + , p = r + g + u + , m = n - g - u + , {outerStart: b, outerEnd: x, innerStart: _, innerEnd: y} = Bn(e, d, c, m - p) + , v = c - b + , w = c - x + , M = p + b / v + , k = m - x / w + , S = d + _ + , P = d + y + , D = p + _ / S + , C = m - y / P; + if (t.beginPath(), + t.arc(o, a, c, M, k), + x > 0) { + const e = Vn(w, k, o, a); + t.arc(e.x, e.y, x, k, m + Et) + } + const O = Vn(P, m, o, a); + if (t.lineTo(O.x, O.y), + y > 0) { + const e = Vn(P, C, o, a); + t.arc(e.x, e.y, y, m + Et, C + Math.PI) + } + if (t.arc(o, a, d, m - y / d, p + _ / d, !0), + _ > 0) { + const e = Vn(S, D, o, a); + t.arc(e.x, e.y, _, D + Math.PI, p - Et) + } + const A = Vn(v, p, o, a); + if (t.lineTo(A.x, A.y), + b > 0) { + const e = Vn(v, M, o, a); + t.arc(e.x, e.y, b, p - Et, M) + } + t.closePath() + } + function Nn(t, e, i, s, n) { + const {options: o} = e + , {borderWidth: a, borderJoinStyle: r} = o + , l = "inner" === o.borderAlign; + a && (l ? (t.lineWidth = 2 * a, + t.lineJoin = r || "round") : (t.lineWidth = a, + t.lineJoin = r || "bevel"), + e.fullCircles && function(t, e, i) { + const {x: s, y: n, startAngle: o, pixelMargin: a, fullCircles: r} = e + , l = Math.max(e.outerRadius - a, 0) + , h = e.innerRadius + a; + let c; + for (i && Fn(t, e, o + At), + t.beginPath(), + t.arc(s, n, h, o + At, o, !0), + c = 0; c < r; ++c) + t.stroke(); + for (t.beginPath(), + t.arc(s, n, l, o, o + At), + c = 0; c < r; ++c) + t.stroke() + }(t, e, l), + l && Fn(t, e, n), + Wn(t, e, i, s, n), + t.stroke()) + } + class jn extends As { + constructor(t) { + super(), + this.options = void 0, + this.circumference = void 0, + this.startAngle = void 0, + this.endAngle = void 0, + this.innerRadius = void 0, + this.outerRadius = void 0, + this.pixelMargin = 0, + this.fullCircles = 0, + t && Object.assign(this, t) + } + inRange(t, e, i) { + const s = this.getProps(["x", "y"], i) + , {angle: n, distance: o} = qt(s, { + x: t, + y: e + }) + , {startAngle: a, endAngle: r, innerRadius: l, outerRadius: h, circumference: c} = this.getProps(["startAngle", "endAngle", "innerRadius", "outerRadius", "circumference"], i) + , d = this.options.spacing / 2 + , u = Z(c, r - a) >= At || Jt(n, a, r) + , f = ee(o, l + d, h + d); + return u && f + } + getCenterPoint(t) { + const {x: e, y: i, startAngle: s, endAngle: n, innerRadius: o, outerRadius: a} = this.getProps(["x", "y", "startAngle", "endAngle", "innerRadius", "outerRadius", "circumference"], t) + , {offset: r, spacing: l} = this.options + , h = (s + n) / 2 + , c = (o + a + l + r) / 2; + return { + x: e + Math.cos(h) * c, + y: i + Math.sin(h) * c + } + } + tooltipPosition(t) { + return this.getCenterPoint(t) + } + draw(t) { + const {options: e, circumference: i} = this + , s = (e.offset || 0) / 2 + , n = (e.spacing || 0) / 2; + if (this.pixelMargin = "inner" === e.borderAlign ? .33 : 0, + this.fullCircles = i > At ? Math.floor(i / At) : 0, + 0 === i || this.innerRadius < 0 || this.outerRadius < 0) + return; + t.save(); + let o = 0; + if (s) { + o = s / 2; + const e = (this.startAngle + this.endAngle) / 2; + t.translate(Math.cos(e) * o, Math.sin(e) * o), + this.circumference >= Ot && (o = s) + } + t.fillStyle = e.backgroundColor, + t.strokeStyle = e.borderColor; + const a = function(t, e, i, s) { + const {fullCircles: n, startAngle: o, circumference: a} = e; + let r = e.endAngle; + if (n) { + Wn(t, e, i, s, o + At); + for (let e = 0; e < n; ++e) + t.fill(); + isNaN(a) || (r = o + a % At, + a % At == 0 && (r += At)) + } + return Wn(t, e, i, s, r), + t.fill(), + r + }(t, this, o, n); + Nn(t, this, o, n, a), + t.restore() + } + } + function Hn(t, e, i=e) { + t.lineCap = Z(i.borderCapStyle, e.borderCapStyle), + t.setLineDash(Z(i.borderDash, e.borderDash)), + t.lineDashOffset = Z(i.borderDashOffset, e.borderDashOffset), + t.lineJoin = Z(i.borderJoinStyle, e.borderJoinStyle), + t.lineWidth = Z(i.borderWidth, e.borderWidth), + t.strokeStyle = Z(i.borderColor, e.borderColor) + } + function $n(t, e, i) { + t.lineTo(i.x, i.y) + } + function Yn(t, e, i={}) { + const s = t.length + , {start: n=0, end: o=s - 1} = i + , {start: a, end: r} = e + , l = Math.max(n, a) + , h = Math.min(o, r) + , c = n < a && o < a || n > r && o > r; + return { + count: s, + start: l, + loop: e.loop, + ilen: h < l && !c ? s + h - l : h - l + } + } + function Un(t, e, i, s) { + const {points: n, options: o} = e + , {count: a, start: r, loop: l, ilen: h} = Yn(n, i, s) + , c = function(t) { + return t.stepped ? Se : t.tension || "monotone" === t.cubicInterpolationMode ? Pe : $n + }(o); + let d, u, f, {move: g=!0, reverse: p} = s || {}; + for (d = 0; d <= h; ++d) + u = n[(r + (p ? h - d : d)) % a], + u.skip || (g ? (t.moveTo(u.x, u.y), + g = !1) : c(t, f, u, p, o.stepped), + f = u); + return l && (u = n[(r + (p ? h : 0)) % a], + c(t, f, u, p, o.stepped)), + !!l + } + function Xn(t, e, i, s) { + const n = e.points + , {count: o, start: a, ilen: r} = Yn(n, i, s) + , {move: l=!0, reverse: h} = s || {}; + let c, d, u, f, g, p, m = 0, b = 0; + const x = t=>(a + (h ? r - t : t)) % o + , _ = ()=>{ + f !== g && (t.lineTo(m, g), + t.lineTo(m, f), + t.lineTo(m, p)) + } + ; + for (l && (d = n[x(0)], + t.moveTo(d.x, d.y)), + c = 0; c <= r; ++c) { + if (d = n[x(c)], + d.skip) + continue; + const e = d.x + , i = d.y + , s = 0 | e; + s === u ? (i < f ? f = i : i > g && (g = i), + m = (b * m + e) / ++b) : (_(), + t.lineTo(e, i), + u = s, + b = 0, + f = g = i), + p = i + } + _() + } + function qn(t) { + const e = t.options + , i = e.borderDash && e.borderDash.length; + return !(t._decimated || t._loop || e.tension || "monotone" === e.cubicInterpolationMode || e.stepped || i) ? Xn : Un + } + jn.id = "arc", + jn.defaults = { + borderAlign: "center", + borderColor: "#fff", + borderJoinStyle: void 0, + borderRadius: 0, + borderWidth: 2, + offset: 0, + spacing: 0, + angle: void 0 + }, + jn.defaultRoutes = { + backgroundColor: "backgroundColor" + }; + const Kn = "function" == typeof Path2D; + function Gn(t, e, i, s) { + Kn && !e.options.segment ? function(t, e, i, s) { + let n = e._path; + n || (n = e._path = new Path2D, + e.path(n, i, s) && n.closePath()), + Hn(t, e.options), + t.stroke(n) + }(t, e, i, s) : function(t, e, i, s) { + const {segments: n, options: o} = e + , a = qn(e); + for (const r of n) + Hn(t, o, r.style), + t.beginPath(), + a(t, e, r, { + start: i, + end: i + s - 1 + }) && t.closePath(), + t.stroke() + }(t, e, i, s) + } + class Zn extends As { + constructor(t) { + super(), + this.animated = !0, + this.options = void 0, + this._chart = void 0, + this._loop = void 0, + this._fullLoop = void 0, + this._path = void 0, + this._points = void 0, + this._segments = void 0, + this._decimated = !1, + this._pointsUpdated = !1, + this._datasetIndex = void 0, + t && Object.assign(this, t) + } + updateControlPoints(t, e) { + const i = this.options; + if ((i.tension || "monotone" === i.cubicInterpolationMode) && !i.stepped && !this._pointsUpdated) { + const s = i.spanGaps ? this._loop : this._fullLoop; + Ge(this._points, i, t, s, e), + this._pointsUpdated = !0 + } + } + set points(t) { + this._points = t, + delete this._segments, + delete this._path, + this._pointsUpdated = !1 + } + get points() { + return this._points + } + get segments() { + return this._segments || (this._segments = ki(this, this.options.segment)) + } + first() { + const t = this.segments + , e = this.points; + return t.length && e[t[0].start] + } + last() { + const t = this.segments + , e = this.points + , i = t.length; + return i && e[t[i - 1].end] + } + interpolate(t, e) { + const i = this.options + , s = t[e] + , n = this.points + , o = Mi(this, { + property: e, + start: s, + end: s + }); + if (!o.length) + return; + const a = [] + , r = function(t) { + return t.stepped ? ii : t.tension || "monotone" === t.cubicInterpolationMode ? si : ei + }(i); + let l, h; + for (l = 0, + h = o.length; l < h; ++l) { + const {start: h, end: c} = o[l] + , d = n[h] + , u = n[c]; + if (d === u) { + a.push(d); + continue + } + const f = r(d, u, Math.abs((s - d[e]) / (u[e] - d[e])), i.stepped); + f[e] = t[e], + a.push(f) + } + return 1 === a.length ? a[0] : a + } + pathSegment(t, e, i) { + return qn(this)(t, this, e, i) + } + path(t, e, i) { + const s = this.segments + , n = qn(this); + let o = this._loop; + e = e || 0, + i = i || this.points.length - e; + for (const a of s) + o &= n(t, this, a, { + start: e, + end: e + i - 1 + }); + return !!o + } + draw(t, e, i, s) { + const n = this.options || {}; + (this.points || []).length && n.borderWidth && (t.save(), + Gn(t, this, i, s), + t.restore()), + this.animated && (this._pointsUpdated = !1, + this._path = void 0) + } + } + function Jn(t, e, i, s) { + const n = t.options + , {[i]: o} = t.getProps([i], s); + return Math.abs(e - o) < n.radius + n.hitRadius + } + Zn.id = "line", + Zn.defaults = { + borderCapStyle: "butt", + borderDash: [], + borderDashOffset: 0, + borderJoinStyle: "miter", + borderWidth: 3, + capBezierPoints: !0, + cubicInterpolationMode: "default", + fill: !1, + spanGaps: !1, + stepped: !1, + tension: 0 + }, + Zn.defaultRoutes = { + backgroundColor: "backgroundColor", + borderColor: "borderColor" + }, + Zn.descriptors = { + _scriptable: !0, + _indexable: t=>"borderDash" !== t && "fill" !== t + }; + class Qn extends As { + constructor(t) { + super(), + this.options = void 0, + this.parsed = void 0, + this.skip = void 0, + this.stop = void 0, + t && Object.assign(this, t) + } + inRange(t, e, i) { + const s = this.options + , {x: n, y: o} = this.getProps(["x", "y"], i); + return Math.pow(t - n, 2) + Math.pow(e - o, 2) < Math.pow(s.hitRadius + s.radius, 2) + } + inXRange(t, e) { + return Jn(this, t, "x", e) + } + inYRange(t, e) { + return Jn(this, t, "y", e) + } + getCenterPoint(t) { + const {x: e, y: i} = this.getProps(["x", "y"], t); + return { + x: e, + y: i + } + } + size(t) { + let e = (t = t || this.options || {}).radius || 0; + e = Math.max(e, e && t.hoverRadius || 0); + return 2 * (e + (e && t.borderWidth || 0)) + } + draw(t, e) { + const i = this.options; + this.skip || i.radius < .1 || !we(this, e, this.size(i) / 2) || (t.strokeStyle = i.borderColor, + t.lineWidth = i.borderWidth, + t.fillStyle = i.backgroundColor, + ye(t, i, this.x, this.y)) + } + getRange() { + const t = this.options || {}; + return t.radius + t.hitRadius + } + } + function to(t, e) { + const {x: i, y: s, base: n, width: o, height: a} = t.getProps(["x", "y", "base", "width", "height"], e); + let r, l, h, c, d; + return t.horizontal ? (d = a / 2, + r = Math.min(i, n), + l = Math.max(i, n), + h = s - d, + c = s + d) : (d = o / 2, + r = i - d, + l = i + d, + h = Math.min(s, n), + c = Math.max(s, n)), + { + left: r, + top: h, + right: l, + bottom: c + } + } + function eo(t, e, i, s) { + return t ? 0 : Qt(e, i, s) + } + function io(t) { + const e = to(t) + , i = e.right - e.left + , s = e.bottom - e.top + , n = function(t, e, i) { + const s = t.options.borderWidth + , n = t.borderSkipped + , o = ci(s); + return { + t: eo(n.top, o.top, 0, i), + r: eo(n.right, o.right, 0, e), + b: eo(n.bottom, o.bottom, 0, i), + l: eo(n.left, o.left, 0, e) + } + }(t, i / 2, s / 2) + , o = function(t, e, i) { + const {enableBorderRadius: s} = t.getProps(["enableBorderRadius"]) + , n = t.options.borderRadius + , o = di(n) + , a = Math.min(e, i) + , r = t.borderSkipped + , l = s || q(n); + return { + topLeft: eo(!l || r.top || r.left, o.topLeft, 0, a), + topRight: eo(!l || r.top || r.right, o.topRight, 0, a), + bottomLeft: eo(!l || r.bottom || r.left, o.bottomLeft, 0, a), + bottomRight: eo(!l || r.bottom || r.right, o.bottomRight, 0, a) + } + }(t, i / 2, s / 2); + return { + outer: { + x: e.left, + y: e.top, + w: i, + h: s, + radius: o + }, + inner: { + x: e.left + n.l, + y: e.top + n.t, + w: i - n.l - n.r, + h: s - n.t - n.b, + radius: { + topLeft: Math.max(0, o.topLeft - Math.max(n.t, n.l)), + topRight: Math.max(0, o.topRight - Math.max(n.t, n.r)), + bottomLeft: Math.max(0, o.bottomLeft - Math.max(n.b, n.l)), + bottomRight: Math.max(0, o.bottomRight - Math.max(n.b, n.r)) + } + } + } + } + function so(t, e, i, s) { + const n = null === e + , o = null === i + , a = t && !(n && o) && to(t, s); + return a && (n || ee(e, a.left, a.right)) && (o || ee(i, a.top, a.bottom)) + } + function no(t, e) { + t.rect(e.x, e.y, e.w, e.h) + } + function oo(t, e, i={}) { + const s = t.x !== i.x ? -e : 0 + , n = t.y !== i.y ? -e : 0 + , o = (t.x + t.w !== i.x + i.w ? e : 0) - s + , a = (t.y + t.h !== i.y + i.h ? e : 0) - n; + return { + x: t.x + s, + y: t.y + n, + w: t.w + o, + h: t.h + a, + radius: t.radius + } + } + Qn.id = "point", + Qn.defaults = { + borderWidth: 1, + hitRadius: 1, + hoverBorderWidth: 1, + hoverRadius: 4, + pointStyle: "circle", + radius: 3, + rotation: 0 + }, + Qn.defaultRoutes = { + backgroundColor: "backgroundColor", + borderColor: "borderColor" + }; + class ao extends As { + constructor(t) { + super(), + this.options = void 0, + this.horizontal = void 0, + this.base = void 0, + this.width = void 0, + this.height = void 0, + this.inflateAmount = void 0, + t && Object.assign(this, t) + } + draw(t) { + const {inflateAmount: e, options: {borderColor: i, backgroundColor: s}} = this + , {inner: n, outer: o} = io(this) + , a = (r = o.radius).topLeft || r.topRight || r.bottomLeft || r.bottomRight ? Oe : no; + var r; + t.save(), + o.w === n.w && o.h === n.h || (t.beginPath(), + a(t, oo(o, e, n)), + t.clip(), + a(t, oo(n, -e, o)), + t.fillStyle = i, + t.fill("evenodd")), + t.beginPath(), + a(t, oo(n, e)), + t.fillStyle = s, + t.fill(), + t.restore() + } + inRange(t, e, i) { + return so(this, t, e, i) + } + inXRange(t, e) { + return so(this, t, null, e) + } + inYRange(t, e) { + return so(this, null, t, e) + } + getCenterPoint(t) { + const {x: e, y: i, base: s, horizontal: n} = this.getProps(["x", "y", "base", "horizontal"], t); + return { + x: n ? (e + s) / 2 : e, + y: n ? i : (i + s) / 2 + } + } + getRange(t) { + return "x" === t ? this.width / 2 : this.height / 2 + } + } + ao.id = "bar", + ao.defaults = { + borderSkipped: "start", + borderWidth: 0, + borderRadius: 0, + inflateAmount: "auto", + pointStyle: void 0 + }, + ao.defaultRoutes = { + backgroundColor: "backgroundColor", + borderColor: "borderColor" + }; + var ro = Object.freeze({ + __proto__: null, + ArcElement: jn, + LineElement: Zn, + PointElement: Qn, + BarElement: ao + }); + function lo(t) { + if (t._decimated) { + const e = t._data; + delete t._decimated, + delete t._data, + Object.defineProperty(t, "data", { + value: e + }) + } + } + function ho(t) { + t.data.datasets.forEach((t=>{ + lo(t) + } + )) + } + var co = { + id: "decimation", + defaults: { + algorithm: "min-max", + enabled: !1 + }, + beforeElementsUpdate: (t,e,i)=>{ + if (!i.enabled) + return void ho(t); + const s = t.width; + t.data.datasets.forEach(((e,n)=>{ + const {_data: o, indexAxis: a} = e + , r = t.getDatasetMeta(n) + , l = o || e.data; + if ("y" === gi([a, t.options.indexAxis])) + return; + if (!r.controller.supportsDecimation) + return; + const h = t.scales[r.xAxisID]; + if ("linear" !== h.type && "time" !== h.type) + return; + if (t.options.parsing) + return; + let {start: c, count: d} = function(t, e) { + const i = e.length; + let s, n = 0; + const {iScale: o} = t + , {min: a, max: r, minDefined: l, maxDefined: h} = o.getUserBounds(); + return l && (n = Qt(wt(e, o.axis, a).lo, 0, i - 1)), + s = h ? Qt(wt(e, o.axis, r).hi + 1, n, i) - n : i - n, + { + start: n, + count: s + } + }(r, l); + if (d <= (i.threshold || 4 * s)) + return void lo(e); + let u; + switch (U(o) && (e._data = l, + delete e.data, + Object.defineProperty(e, "data", { + configurable: !0, + enumerable: !0, + get: function() { + return this._decimated + }, + set: function(t) { + this._data = t + } + })), + i.algorithm) { + case "lttb": + u = function(t, e, i, s, n) { + const o = n.samples || s; + if (o >= i) + return t.slice(e, e + i); + const a = [] + , r = (i - 2) / (o - 2); + let l = 0; + const h = e + i - 1; + let c, d, u, f, g, p = e; + for (a[l++] = t[p], + c = 0; c < o - 2; c++) { + let s, n = 0, o = 0; + const h = Math.floor((c + 1) * r) + 1 + e + , m = Math.min(Math.floor((c + 2) * r) + 1, i) + e + , b = m - h; + for (s = h; s < m; s++) + n += t[s].x, + o += t[s].y; + n /= b, + o /= b; + const x = Math.floor(c * r) + 1 + e + , _ = Math.min(Math.floor((c + 1) * r) + 1, i) + e + , {x: y, y: v} = t[p]; + for (u = f = -1, + s = x; s < _; s++) + f = .5 * Math.abs((y - n) * (t[s].y - v) - (y - t[s].x) * (o - v)), + f > u && (u = f, + d = t[s], + g = s); + a[l++] = d, + p = g + } + return a[l++] = t[h], + a + }(l, c, d, s, i); + break; + case "min-max": + u = function(t, e, i, s) { + let n, o, a, r, l, h, c, d, u, f, g = 0, p = 0; + const m = [] + , b = e + i - 1 + , x = t[e].x + , _ = t[b].x - x; + for (n = e; n < e + i; ++n) { + o = t[n], + a = (o.x - x) / _ * s, + r = o.y; + const e = 0 | a; + if (e === l) + r < u ? (u = r, + h = n) : r > f && (f = r, + c = n), + g = (p * g + o.x) / ++p; + else { + const i = n - 1; + if (!U(h) && !U(c)) { + const e = Math.min(h, c) + , s = Math.max(h, c); + e !== d && e !== i && m.push({ + ...t[e], + x: g + }), + s !== d && s !== i && m.push({ + ...t[s], + x: g + }) + } + n > 0 && i !== d && m.push(t[i]), + m.push(o), + l = e, + p = 0, + u = f = r, + h = c = d = n + } + } + return m + }(l, c, d, s); + break; + default: + throw new Error(`Unsupported decimation algorithm '${i.algorithm}'`) + } + e._decimated = u + } + )) + } + , + destroy(t) { + ho(t) + } + }; + function uo(t, e, i, s) { + if (s) + return; + let n = e[t] + , o = i[t]; + return "angle" === t && (n = Zt(n), + o = Zt(o)), + { + property: t, + start: n, + end: o + } + } + function fo(t, e, i) { + for (; e > t; e--) { + const t = i[e]; + if (!isNaN(t.x) && !isNaN(t.y)) + break + } + return e + } + function go(t, e, i, s) { + return t && e ? s(t[i], e[i]) : t ? t[i] : e ? e[i] : 0 + } + function po(t, e) { + let i = [] + , s = !1; + return X(t) ? (s = !0, + i = t) : i = function(t, e) { + const {x: i=null, y: s=null} = t || {} + , n = e.points + , o = []; + return e.segments.forEach((({start: t, end: e})=>{ + e = fo(t, e, n); + const a = n[t] + , r = n[e]; + null !== s ? (o.push({ + x: a.x, + y: s + }), + o.push({ + x: r.x, + y: s + })) : null !== i && (o.push({ + x: i, + y: a.y + }), + o.push({ + x: i, + y: r.y + })) + } + )), + o + }(t, e), + i.length ? new Zn({ + points: i, + options: { + tension: 0 + }, + _loop: s, + _fullLoop: s + }) : null + } + function mo(t) { + return t && !1 !== t.fill + } + function bo(t, e, i) { + let s = t[e].fill; + const n = [e]; + let o; + if (!i) + return s; + for (; !1 !== s && -1 === n.indexOf(s); ) { + if (!K(s)) + return s; + if (o = t[s], + !o) + return !1; + if (o.visible) + return s; + n.push(s), + s = o.fill + } + return !1 + } + function xo(t, e, i) { + const s = function(t) { + const e = t.options + , i = e.fill; + let s = Z(i && i.target, i); + void 0 === s && (s = !!e.backgroundColor); + if (!1 === s || null === s) + return !1; + if (!0 === s) + return "origin"; + return s + }(t); + if (q(s)) + return !isNaN(s.value) && s; + let n = parseFloat(s); + return K(n) && Math.floor(n) === n ? function(t, e, i, s) { + "-" !== t && "+" !== t || (i = e + i); + if (i === e || i < 0 || i >= s) + return !1; + return i + }(s[0], e, n, i) : ["origin", "start", "end", "stack", "shape"].indexOf(s) >= 0 && s + } + function _o(t, e, i) { + const s = []; + for (let n = 0; n < i.length; n++) { + const o = i[n] + , {first: a, last: r, point: l} = yo(o, e, "x"); + if (!(!l || a && r)) + if (a) + s.unshift(l); + else if (t.push(l), + !r) + break + } + t.push(...s) + } + function yo(t, e, i) { + const s = t.interpolate(e, i); + if (!s) + return {}; + const n = s[i] + , o = t.segments + , a = t.points; + let r = !1 + , l = !1; + for (let t = 0; t < o.length; t++) { + const e = o[t] + , s = a[e.start][i] + , h = a[e.end][i]; + if (ee(n, s, h)) { + r = n === s, + l = n === h; + break + } + } + return { + first: r, + last: l, + point: s + } + } + class vo { + constructor(t) { + this.x = t.x, + this.y = t.y, + this.radius = t.radius + } + pathSegment(t, e, i) { + const {x: s, y: n, radius: o} = this; + return e = e || { + start: 0, + end: At + }, + t.arc(s, n, o, e.end, e.start, !0), + !i.bounds + } + interpolate(t) { + const {x: e, y: i, radius: s} = this + , n = t.angle; + return { + x: e + Math.cos(n) * s, + y: i + Math.sin(n) * s, + angle: n + } + } + } + function wo(t) { + const {chart: e, fill: i, line: s} = t; + if (K(i)) + return function(t, e) { + const i = t.getDatasetMeta(e); + return i && t.isDatasetVisible(e) ? i.dataset : null + }(e, i); + if ("stack" === i) + return function(t) { + const {scale: e, index: i, line: s} = t + , n = [] + , o = s.segments + , a = s.points + , r = function(t, e) { + const i = [] + , s = t.getMatchingVisibleMetas("line"); + for (let t = 0; t < s.length; t++) { + const n = s[t]; + if (n.index === e) + break; + n.hidden || i.unshift(n.dataset) + } + return i + }(e, i); + r.push(po({ + x: null, + y: e.bottom + }, s)); + for (let t = 0; t < o.length; t++) { + const e = o[t]; + for (let t = e.start; t <= e.end; t++) + _o(n, a[t], r) + } + return new Zn({ + points: n, + options: {} + }) + }(t); + if ("shape" === i) + return !0; + const n = function(t) { + if ((t.scale || {}).getPointPositionForValue) + return function(t) { + const {scale: e, fill: i} = t + , s = e.options + , n = e.getLabels().length + , o = s.reverse ? e.max : e.min + , a = function(t, e, i) { + let s; + return s = "start" === t ? i : "end" === t ? e.options.reverse ? e.min : e.max : q(t) ? t.value : e.getBaseValue(), + s + }(i, e, o) + , r = []; + if (s.grid.circular) { + const t = e.getPointPositionForValue(0, o); + return new vo({ + x: t.x, + y: t.y, + radius: e.getDistanceFromCenterForValue(a) + }) + } + for (let t = 0; t < n; ++t) + r.push(e.getPointPositionForValue(t, a)); + return r + }(t); + return function(t) { + const {scale: e={}, fill: i} = t + , s = function(t, e) { + let i = null; + return "start" === t ? i = e.bottom : "end" === t ? i = e.top : q(t) ? i = e.getPixelForValue(t.value) : e.getBasePixel && (i = e.getBasePixel()), + i + }(i, e); + if (K(s)) { + const t = e.isHorizontal(); + return { + x: t ? s : null, + y: t ? null : s + } + } + return null + }(t) + }(t); + return n instanceof vo ? n : po(n, s) + } + function Mo(t, e, i) { + const s = wo(e) + , {line: n, scale: o, axis: a} = e + , r = n.options + , l = r.fill + , h = r.backgroundColor + , {above: c=h, below: d=h} = l || {}; + s && n.points.length && (Me(t, i), + function(t, e) { + const {line: i, target: s, above: n, below: o, area: a, scale: r} = e + , l = i._loop ? "angle" : e.axis; + t.save(), + "x" === l && o !== n && (ko(t, s, a.top), + So(t, { + line: i, + target: s, + color: n, + scale: r, + property: l + }), + t.restore(), + t.save(), + ko(t, s, a.bottom)); + So(t, { + line: i, + target: s, + color: o, + scale: r, + property: l + }), + t.restore() + }(t, { + line: n, + target: s, + above: c, + below: d, + area: i, + scale: o, + axis: a + }), + ke(t)) + } + function ko(t, e, i) { + const {segments: s, points: n} = e; + let o = !0 + , a = !1; + t.beginPath(); + for (const r of s) { + const {start: s, end: l} = r + , h = n[s] + , c = n[fo(s, l, n)]; + o ? (t.moveTo(h.x, h.y), + o = !1) : (t.lineTo(h.x, i), + t.lineTo(h.x, h.y)), + a = !!e.pathSegment(t, r, { + move: a + }), + a ? t.closePath() : t.lineTo(c.x, i) + } + t.lineTo(e.first().x, i), + t.closePath(), + t.clip() + } + function So(t, e) { + const {line: i, target: s, property: n, color: o, scale: a} = e + , r = function(t, e, i) { + const s = t.segments + , n = t.points + , o = e.points + , a = []; + for (const t of s) { + let {start: s, end: r} = t; + r = fo(s, r, n); + const l = uo(i, n[s], n[r], t.loop); + if (!e.segments) { + a.push({ + source: t, + target: l, + start: n[s], + end: n[r] + }); + continue + } + const h = Mi(e, l); + for (const e of h) { + const s = uo(i, o[e.start], o[e.end], e.loop) + , r = wi(t, n, s); + for (const t of r) + a.push({ + source: t, + target: e, + start: { + [i]: go(l, s, "start", Math.max) + }, + end: { + [i]: go(l, s, "end", Math.min) + } + }) + } + } + return a + }(i, s, n); + for (const {source: e, target: l, start: h, end: c} of r) { + const {style: {backgroundColor: r=o}={}} = e + , d = !0 !== s; + t.save(), + t.fillStyle = r, + Po(t, a, d && uo(n, h, c)), + t.beginPath(); + const u = !!i.pathSegment(t, e); + let f; + if (d) { + u ? t.closePath() : Do(t, s, c, n); + const e = !!s.pathSegment(t, l, { + move: u, + reverse: !0 + }); + f = u && e, + f || Do(t, s, h, n) + } + t.closePath(), + t.fill(f ? "evenodd" : "nonzero"), + t.restore() + } + } + function Po(t, e, i) { + const {top: s, bottom: n} = e.chart.chartArea + , {property: o, start: a, end: r} = i || {}; + "x" === o && (t.beginPath(), + t.rect(a, s, r - a, n - s), + t.clip()) + } + function Do(t, e, i, s) { + const n = e.interpolate(i, s); + n && t.lineTo(n.x, n.y) + } + var Co = { + id: "filler", + afterDatasetsUpdate(t, e, i) { + const s = (t.data.datasets || []).length + , n = []; + let o, a, r, l; + for (a = 0; a < s; ++a) + o = t.getDatasetMeta(a), + r = o.dataset, + l = null, + r && r.options && r instanceof Zn && (l = { + visible: t.isDatasetVisible(a), + index: a, + fill: xo(r, a, s), + chart: t, + axis: o.controller.options.indexAxis, + scale: o.vScale, + line: r + }), + o.$filler = l, + n.push(l); + for (a = 0; a < s; ++a) + l = n[a], + l && !1 !== l.fill && (l.fill = bo(n, a, i.propagate)) + }, + beforeDraw(t, e, i) { + const s = "beforeDraw" === i.drawTime + , n = t.getSortedVisibleDatasetMetas() + , o = t.chartArea; + for (let e = n.length - 1; e >= 0; --e) { + const i = n[e].$filler; + i && (i.line.updateControlPoints(o, i.axis), + s && i.fill && Mo(t.ctx, i, o)) + } + }, + beforeDatasetsDraw(t, e, i) { + if ("beforeDatasetsDraw" !== i.drawTime) + return; + const s = t.getSortedVisibleDatasetMetas(); + for (let e = s.length - 1; e >= 0; --e) { + const i = s[e].$filler; + mo(i) && Mo(t.ctx, i, t.chartArea) + } + }, + beforeDatasetDraw(t, e, i) { + const s = e.meta.$filler; + mo(s) && "beforeDatasetDraw" === i.drawTime && Mo(t.ctx, s, t.chartArea) + }, + defaults: { + propagate: !0, + drawTime: "beforeDatasetDraw" + } + }; + const Oo = (t,e)=>{ + let {boxHeight: i=e, boxWidth: s=e} = t; + return t.usePointStyle && (i = Math.min(i, e), + s = t.pointStyleWidth || Math.min(s, e)), + { + boxWidth: s, + boxHeight: i, + itemHeight: Math.max(e, i) + } + } + ; + class Ao extends As { + constructor(t) { + super(), + this._added = !1, + this.legendHitBoxes = [], + this._hoveredItem = null, + this.doughnutMode = !1, + this.chart = t.chart, + this.options = t.options, + this.ctx = t.ctx, + this.legendItems = void 0, + this.columnSizes = void 0, + this.lineWidths = void 0, + this.maxHeight = void 0, + this.maxWidth = void 0, + this.top = void 0, + this.bottom = void 0, + this.left = void 0, + this.right = void 0, + this.height = void 0, + this.width = void 0, + this._margins = void 0, + this.position = void 0, + this.weight = void 0, + this.fullSize = void 0 + } + update(t, e, i) { + this.maxWidth = t, + this.maxHeight = e, + this._margins = i, + this.setDimensions(), + this.buildLabels(), + this.fit() + } + setDimensions() { + this.isHorizontal() ? (this.width = this.maxWidth, + this.left = this._margins.left, + this.right = this.width) : (this.height = this.maxHeight, + this.top = this._margins.top, + this.bottom = this.height) + } + buildLabels() { + const t = this.options.labels || {}; + let e = tt(t.generateLabels, [this.chart], this) || []; + t.filter && (e = e.filter((e=>t.filter(e, this.chart.data)))), + t.sort && (e = e.sort(((e,i)=>t.sort(e, i, this.chart.data)))), + this.options.reverse && e.reverse(), + this.legendItems = e + } + fit() { + const {options: t, ctx: e} = this; + if (!t.display) + return void (this.width = this.height = 0); + const i = t.labels + , s = fi(i.font) + , n = s.size + , o = this._computeTitleHeight() + , {boxWidth: a, itemHeight: r} = Oo(i, n); + let l, h; + e.font = s.string, + this.isHorizontal() ? (l = this.maxWidth, + h = this._fitRows(o, n, a, r) + 10) : (h = this.maxHeight, + l = this._fitCols(o, n, a, r) + 10), + this.width = Math.min(l, t.maxWidth || this.maxWidth), + this.height = Math.min(h, t.maxHeight || this.maxHeight) + } + _fitRows(t, e, i, s) { + const {ctx: n, maxWidth: o, options: {labels: {padding: a}}} = this + , r = this.legendHitBoxes = [] + , l = this.lineWidths = [0] + , h = s + a; + let c = t; + n.textAlign = "left", + n.textBaseline = "middle"; + let d = -1 + , u = -h; + return this.legendItems.forEach(((t,f)=>{ + const g = i + e / 2 + n.measureText(t.text).width; + (0 === f || l[l.length - 1] + g + 2 * a > o) && (c += h, + l[l.length - (f > 0 ? 0 : 1)] = 0, + u += h, + d++), + r[f] = { + left: 0, + top: u, + row: d, + width: g, + height: s + }, + l[l.length - 1] += g + a + } + )), + c + } + _fitCols(t, e, i, s) { + const {ctx: n, maxHeight: o, options: {labels: {padding: a}}} = this + , r = this.legendHitBoxes = [] + , l = this.columnSizes = [] + , h = o - t; + let c = a + , d = 0 + , u = 0 + , f = 0 + , g = 0; + return this.legendItems.forEach(((t,o)=>{ + const p = i + e / 2 + n.measureText(t.text).width; + o > 0 && u + s + 2 * a > h && (c += d + a, + l.push({ + width: d, + height: u + }), + f += d + a, + g++, + d = u = 0), + r[o] = { + left: f, + top: u, + col: g, + width: p, + height: s + }, + d = Math.max(d, p), + u += s + a + } + )), + c += d, + l.push({ + width: d, + height: u + }), + c + } + adjustHitBoxes() { + if (!this.options.display) + return; + const t = this._computeTitleHeight() + , {legendHitBoxes: e, options: {align: i, labels: {padding: s}, rtl: o}} = this + , a = bi(o, this.left, this.width); + if (this.isHorizontal()) { + let o = 0 + , r = n(i, this.left + s, this.right - this.lineWidths[o]); + for (const l of e) + o !== l.row && (o = l.row, + r = n(i, this.left + s, this.right - this.lineWidths[o])), + l.top += this.top + t + s, + l.left = a.leftForLtr(a.x(r), l.width), + r += l.width + s + } else { + let o = 0 + , r = n(i, this.top + t + s, this.bottom - this.columnSizes[o].height); + for (const l of e) + l.col !== o && (o = l.col, + r = n(i, this.top + t + s, this.bottom - this.columnSizes[o].height)), + l.top = r, + l.left += this.left + s, + l.left = a.leftForLtr(a.x(l.left), l.width), + r += l.height + s + } + } + isHorizontal() { + return "top" === this.options.position || "bottom" === this.options.position + } + draw() { + if (this.options.display) { + const t = this.ctx; + Me(t, this), + this._draw(), + ke(t) + } + } + _draw() { + const {options: t, columnSizes: e, lineWidths: i, ctx: s} = this + , {align: a, labels: r} = t + , l = yt.color + , h = bi(t.rtl, this.left, this.width) + , c = fi(r.font) + , {color: d, padding: u} = r + , f = c.size + , g = f / 2; + let p; + this.drawTitle(), + s.textAlign = h.textAlign("left"), + s.textBaseline = "middle", + s.lineWidth = .5, + s.font = c.string; + const {boxWidth: m, boxHeight: b, itemHeight: x} = Oo(r, f) + , _ = this.isHorizontal() + , y = this._computeTitleHeight(); + p = _ ? { + x: n(a, this.left + u, this.right - i[0]), + y: this.top + u + y, + line: 0 + } : { + x: this.left + u, + y: n(a, this.top + y + u, this.bottom - e[0].height), + line: 0 + }, + xi(this.ctx, t.textDirection); + const v = x + u; + this.legendItems.forEach(((w,M)=>{ + s.strokeStyle = w.fontColor || d, + s.fillStyle = w.fontColor || d; + const k = s.measureText(w.text).width + , S = h.textAlign(w.textAlign || (w.textAlign = r.textAlign)) + , P = m + g + k; + let D = p.x + , C = p.y; + h.setWidth(this.width), + _ ? M > 0 && D + P + u > this.right && (C = p.y += v, + p.line++, + D = p.x = n(a, this.left + u, this.right - i[p.line])) : M > 0 && C + v > this.bottom && (D = p.x = D + e[p.line].width + u, + p.line++, + C = p.y = n(a, this.top + y + u, this.bottom - e[p.line].height)); + !function(t, e, i) { + if (isNaN(m) || m <= 0 || isNaN(b) || b < 0) + return; + s.save(); + const n = Z(i.lineWidth, 1); + if (s.fillStyle = Z(i.fillStyle, l), + s.lineCap = Z(i.lineCap, "butt"), + s.lineDashOffset = Z(i.lineDashOffset, 0), + s.lineJoin = Z(i.lineJoin, "miter"), + s.lineWidth = n, + s.strokeStyle = Z(i.strokeStyle, l), + s.setLineDash(Z(i.lineDash, [])), + r.usePointStyle) { + const o = { + radius: b * Math.SQRT2 / 2, + pointStyle: i.pointStyle, + rotation: i.rotation, + borderWidth: n + } + , a = h.xPlus(t, m / 2); + ve(s, o, a, e + g, m) + } else { + const o = e + Math.max((f - b) / 2, 0) + , a = h.leftForLtr(t, m) + , r = di(i.borderRadius); + s.beginPath(), + Object.values(r).some((t=>0 !== t)) ? Oe(s, { + x: a, + y: o, + w: m, + h: b, + radius: r + }) : s.rect(a, o, m, b), + s.fill(), + 0 !== n && s.stroke() + } + s.restore() + }(h.x(D), C, w), + D = o(S, D + m + g, _ ? D + P : this.right, t.rtl), + function(t, e, i) { + De(s, i.text, t, e + x / 2, c, { + strikethrough: i.hidden, + textAlign: h.textAlign(i.textAlign) + }) + }(h.x(D), C, w), + _ ? p.x += P + u : p.y += v + } + )), + _i(this.ctx, t.textDirection) + } + drawTitle() { + const t = this.options + , e = t.title + , i = fi(e.font) + , o = ui(e.padding); + if (!e.display) + return; + const a = bi(t.rtl, this.left, this.width) + , r = this.ctx + , l = e.position + , h = i.size / 2 + , c = o.top + h; + let d, u = this.left, f = this.width; + if (this.isHorizontal()) + f = Math.max(...this.lineWidths), + d = this.top + c, + u = n(t.align, u, this.right - f); + else { + const e = this.columnSizes.reduce(((t,e)=>Math.max(t, e.height)), 0); + d = c + n(t.align, this.top, this.bottom - e - t.labels.padding - this._computeTitleHeight()) + } + const g = n(l, u, u + f); + r.textAlign = a.textAlign(s(l)), + r.textBaseline = "middle", + r.strokeStyle = e.color, + r.fillStyle = e.color, + r.font = i.string, + De(r, e.text, g, d, i) + } + _computeTitleHeight() { + const t = this.options.title + , e = fi(t.font) + , i = ui(t.padding); + return t.display ? e.lineHeight + i.height : 0 + } + _getLegendItemAt(t, e) { + let i, s, n; + if (ee(t, this.left, this.right) && ee(e, this.top, this.bottom)) + for (n = this.legendHitBoxes, + i = 0; i < n.length; ++i) + if (s = n[i], + ee(t, s.left, s.left + s.width) && ee(e, s.top, s.top + s.height)) + return this.legendItems[i]; + return null + } + handleEvent(t) { + const e = this.options; + if (!function(t, e) { + if (("mousemove" === t || "mouseout" === t) && (e.onHover || e.onLeave)) + return !0; + if (e.onClick && ("click" === t || "mouseup" === t)) + return !0; + return !1 + }(t.type, e)) + return; + const i = this._getLegendItemAt(t.x, t.y); + if ("mousemove" === t.type || "mouseout" === t.type) { + const o = this._hoveredItem + , a = (n = i, + null !== (s = o) && null !== n && s.datasetIndex === n.datasetIndex && s.index === n.index); + o && !a && tt(e.onLeave, [t, o, this], this), + this._hoveredItem = i, + i && !a && tt(e.onHover, [t, i, this], this) + } else + i && tt(e.onClick, [t, i, this], this); + var s, n + } + } + var To = { + id: "legend", + _element: Ao, + start(t, e, i) { + const s = t.legend = new Ao({ + ctx: t.ctx, + options: i, + chart: t + }); + qi.configure(t, s, i), + qi.addBox(t, s) + }, + stop(t) { + qi.removeBox(t, t.legend), + delete t.legend + }, + beforeUpdate(t, e, i) { + const s = t.legend; + qi.configure(t, s, i), + s.options = i + }, + afterUpdate(t) { + const e = t.legend; + e.buildLabels(), + e.adjustHitBoxes() + }, + afterEvent(t, e) { + e.replay || t.legend.handleEvent(e.event) + }, + defaults: { + display: !0, + position: "top", + align: "center", + fullSize: !0, + reverse: !1, + weight: 1e3, + onClick(t, e, i) { + const s = e.datasetIndex + , n = i.chart; + n.isDatasetVisible(s) ? (n.hide(s), + e.hidden = !0) : (n.show(s), + e.hidden = !1) + }, + onHover: null, + onLeave: null, + labels: { + color: t=>t.chart.options.color, + boxWidth: 40, + padding: 10, + generateLabels(t) { + const e = t.data.datasets + , {labels: {usePointStyle: i, pointStyle: s, textAlign: n, color: o}} = t.legend.options; + return t._getSortedDatasetMetas().map((t=>{ + const a = t.controller.getStyle(i ? 0 : void 0) + , r = ui(a.borderWidth); + return { + text: e[t.index].label, + fillStyle: a.backgroundColor, + fontColor: o, + hidden: !t.visible, + lineCap: a.borderCapStyle, + lineDash: a.borderDash, + lineDashOffset: a.borderDashOffset, + lineJoin: a.borderJoinStyle, + lineWidth: (r.width + r.height) / 4, + strokeStyle: a.borderColor, + pointStyle: s || a.pointStyle, + rotation: a.rotation, + textAlign: n || a.textAlign, + borderRadius: 0, + datasetIndex: t.index + } + } + ), this) + } + }, + title: { + color: t=>t.chart.options.color, + display: !1, + position: "center", + text: "" + } + }, + descriptors: { + _scriptable: t=>!t.startsWith("on"), + labels: { + _scriptable: t=>!["generateLabels", "filter", "sort"].includes(t) + } + } + }; + class Lo extends As { + constructor(t) { + super(), + this.chart = t.chart, + this.options = t.options, + this.ctx = t.ctx, + this._padding = void 0, + this.top = void 0, + this.bottom = void 0, + this.left = void 0, + this.right = void 0, + this.width = void 0, + this.height = void 0, + this.position = void 0, + this.weight = void 0, + this.fullSize = void 0 + } + update(t, e) { + const i = this.options; + if (this.left = 0, + this.top = 0, + !i.display) + return void (this.width = this.height = this.right = this.bottom = 0); + this.width = this.right = t, + this.height = this.bottom = e; + const s = X(i.text) ? i.text.length : 1; + this._padding = ui(i.padding); + const n = s * fi(i.font).lineHeight + this._padding.height; + this.isHorizontal() ? this.height = n : this.width = n + } + isHorizontal() { + const t = this.options.position; + return "top" === t || "bottom" === t + } + _drawArgs(t) { + const {top: e, left: i, bottom: s, right: o, options: a} = this + , r = a.align; + let l, h, c, d = 0; + return this.isHorizontal() ? (h = n(r, i, o), + c = e + t, + l = o - i) : ("left" === a.position ? (h = i + t, + c = n(r, s, e), + d = -.5 * Ot) : (h = o - t, + c = n(r, e, s), + d = .5 * Ot), + l = s - e), + { + titleX: h, + titleY: c, + maxWidth: l, + rotation: d + } + } + draw() { + const t = this.ctx + , e = this.options; + if (!e.display) + return; + const i = fi(e.font) + , n = i.lineHeight / 2 + this._padding.top + , {titleX: o, titleY: a, maxWidth: r, rotation: l} = this._drawArgs(n); + De(t, e.text, 0, 0, i, { + color: e.color, + maxWidth: r, + rotation: l, + textAlign: s(e.align), + textBaseline: "middle", + translation: [o, a] + }) + } + } + var Ro = { + id: "title", + _element: Lo, + start(t, e, i) { + !function(t, e) { + const i = new Lo({ + ctx: t.ctx, + options: e, + chart: t + }); + qi.configure(t, i, e), + qi.addBox(t, i), + t.titleBlock = i + }(t, i) + }, + stop(t) { + const e = t.titleBlock; + qi.removeBox(t, e), + delete t.titleBlock + }, + beforeUpdate(t, e, i) { + const s = t.titleBlock; + qi.configure(t, s, i), + s.options = i + }, + defaults: { + align: "center", + display: !1, + font: { + weight: "bold" + }, + fullSize: !0, + padding: 10, + position: "top", + text: "", + weight: 2e3 + }, + defaultRoutes: { + color: "color" + }, + descriptors: { + _scriptable: !0, + _indexable: !1 + } + }; + const Eo = new WeakMap; + var Io = { + id: "subtitle", + start(t, e, i) { + const s = new Lo({ + ctx: t.ctx, + options: i, + chart: t + }); + qi.configure(t, s, i), + qi.addBox(t, s), + Eo.set(t, s) + }, + stop(t) { + qi.removeBox(t, Eo.get(t)), + Eo.delete(t) + }, + beforeUpdate(t, e, i) { + const s = Eo.get(t); + qi.configure(t, s, i), + s.options = i + }, + defaults: { + align: "center", + display: !1, + font: { + weight: "normal" + }, + fullSize: !0, + padding: 0, + position: "top", + text: "", + weight: 1500 + }, + defaultRoutes: { + color: "color" + }, + descriptors: { + _scriptable: !0, + _indexable: !1 + } + }; + const zo = { + average(t) { + if (!t.length) + return !1; + let e, i, s = 0, n = 0, o = 0; + for (e = 0, + i = t.length; e < i; ++e) { + const i = t[e].element; + if (i && i.hasValue()) { + const t = i.tooltipPosition(); + s += t.x, + n += t.y, + ++o + } + } + return { + x: s / o, + y: n / o + } + }, + nearest(t, e) { + if (!t.length) + return !1; + let i, s, n, o = e.x, a = e.y, r = Number.POSITIVE_INFINITY; + for (i = 0, + s = t.length; i < s; ++i) { + const s = t[i].element; + if (s && s.hasValue()) { + const t = Kt(e, s.getCenterPoint()); + t < r && (r = t, + n = s) + } + } + if (n) { + const t = n.tooltipPosition(); + o = t.x, + a = t.y + } + return { + x: o, + y: a + } + } + }; + function Fo(t, e) { + return e && (X(e) ? Array.prototype.push.apply(t, e) : t.push(e)), + t + } + function Bo(t) { + return ("string" == typeof t || t instanceof String) && t.indexOf("\n") > -1 ? t.split("\n") : t + } + function Vo(t, e) { + const {element: i, datasetIndex: s, index: n} = e + , o = t.getDatasetMeta(s).controller + , {label: a, value: r} = o.getLabelAndValue(n); + return { + chart: t, + label: a, + parsed: o.getParsed(n), + raw: t.data.datasets[s].data[n], + formattedValue: r, + dataset: o.getDataset(), + dataIndex: n, + datasetIndex: s, + element: i + } + } + function Wo(t, e) { + const i = t.chart.ctx + , {body: s, footer: n, title: o} = t + , {boxWidth: a, boxHeight: r} = e + , l = fi(e.bodyFont) + , h = fi(e.titleFont) + , c = fi(e.footerFont) + , d = o.length + , u = n.length + , f = s.length + , g = ui(e.padding); + let p = g.height + , m = 0 + , b = s.reduce(((t,e)=>t + e.before.length + e.lines.length + e.after.length), 0); + if (b += t.beforeBody.length + t.afterBody.length, + d && (p += d * h.lineHeight + (d - 1) * e.titleSpacing + e.titleMarginBottom), + b) { + p += f * (e.displayColors ? Math.max(r, l.lineHeight) : l.lineHeight) + (b - f) * l.lineHeight + (b - 1) * e.bodySpacing + } + u && (p += e.footerMarginTop + u * c.lineHeight + (u - 1) * e.footerSpacing); + let x = 0; + const _ = function(t) { + m = Math.max(m, i.measureText(t).width + x) + }; + return i.save(), + i.font = h.string, + et(t.title, _), + i.font = l.string, + et(t.beforeBody.concat(t.afterBody), _), + x = e.displayColors ? a + 2 + e.boxPadding : 0, + et(s, (t=>{ + et(t.before, _), + et(t.lines, _), + et(t.after, _) + } + )), + x = 0, + i.font = c.string, + et(t.footer, _), + i.restore(), + m += g.width, + { + width: m, + height: p + } + } + function No(t, e, i, s) { + const {x: n, width: o} = i + , {width: a, chartArea: {left: r, right: l}} = t; + let h = "center"; + return "center" === s ? h = n <= (r + l) / 2 ? "left" : "right" : n <= o / 2 ? h = "left" : n >= a - o / 2 && (h = "right"), + function(t, e, i, s) { + const {x: n, width: o} = s + , a = i.caretSize + i.caretPadding; + return "left" === t && n + o + a > e.width || "right" === t && n - o - a < 0 || void 0 + }(h, t, e, i) && (h = "center"), + h + } + function jo(t, e, i) { + const s = i.yAlign || e.yAlign || function(t, e) { + const {y: i, height: s} = e; + return i < s / 2 ? "top" : i > t.height - s / 2 ? "bottom" : "center" + }(t, i); + return { + xAlign: i.xAlign || e.xAlign || No(t, e, i, s), + yAlign: s + } + } + function Ho(t, e, i, s) { + const {caretSize: n, caretPadding: o, cornerRadius: a} = t + , {xAlign: r, yAlign: l} = i + , h = n + o + , {topLeft: c, topRight: d, bottomLeft: u, bottomRight: f} = di(a); + let g = function(t, e) { + let {x: i, width: s} = t; + return "right" === e ? i -= s : "center" === e && (i -= s / 2), + i + }(e, r); + const p = function(t, e, i) { + let {y: s, height: n} = t; + return "top" === e ? s += i : s -= "bottom" === e ? n + i : n / 2, + s + }(e, l, h); + return "center" === l ? "left" === r ? g += h : "right" === r && (g -= h) : "left" === r ? g -= Math.max(c, u) + n : "right" === r && (g += Math.max(d, f) + n), + { + x: Qt(g, 0, s.width - e.width), + y: Qt(p, 0, s.height - e.height) + } + } + function $o(t, e, i) { + const s = ui(i.padding); + return "center" === e ? t.x + t.width / 2 : "right" === e ? t.x + t.width - s.right : t.x + s.left + } + function Yo(t) { + return Fo([], Bo(t)) + } + function Uo(t, e) { + const i = e && e.dataset && e.dataset.tooltip && e.dataset.tooltip.callbacks; + return i ? t.override(i) : t + } + class Xo extends As { + constructor(t) { + super(), + this.opacity = 0, + this._active = [], + this._eventPosition = void 0, + this._size = void 0, + this._cachedAnimations = void 0, + this._tooltipItems = [], + this.$animations = void 0, + this.$context = void 0, + this.chart = t.chart || t._chart, + this._chart = this.chart, + this.options = t.options, + this.dataPoints = void 0, + this.title = void 0, + this.beforeBody = void 0, + this.body = void 0, + this.afterBody = void 0, + this.footer = void 0, + this.xAlign = void 0, + this.yAlign = void 0, + this.x = void 0, + this.y = void 0, + this.height = void 0, + this.width = void 0, + this.caretX = void 0, + this.caretY = void 0, + this.labelColors = void 0, + this.labelPointStyles = void 0, + this.labelTextColors = void 0 + } + initialize(t) { + this.options = t, + this._cachedAnimations = void 0, + this.$context = void 0 + } + _resolveAnimations() { + const t = this._cachedAnimations; + if (t) + return t; + const e = this.chart + , i = this.options.setContext(this.getContext()) + , s = i.enabled && e.options.animation && i.animations + , n = new bs(this.chart,s); + return s._cacheable && (this._cachedAnimations = Object.freeze(n)), + n + } + getContext() { + return this.$context || (this.$context = (t = this.chart.getContext(), + e = this, + i = this._tooltipItems, + mi(t, { + tooltip: e, + tooltipItems: i, + type: "tooltip" + }))); + var t, e, i + } + getTitle(t, e) { + const {callbacks: i} = e + , s = i.beforeTitle.apply(this, [t]) + , n = i.title.apply(this, [t]) + , o = i.afterTitle.apply(this, [t]); + let a = []; + return a = Fo(a, Bo(s)), + a = Fo(a, Bo(n)), + a = Fo(a, Bo(o)), + a + } + getBeforeBody(t, e) { + return Yo(e.callbacks.beforeBody.apply(this, [t])) + } + getBody(t, e) { + const {callbacks: i} = e + , s = []; + return et(t, (t=>{ + const e = { + before: [], + lines: [], + after: [] + } + , n = Uo(i, t); + Fo(e.before, Bo(n.beforeLabel.call(this, t))), + Fo(e.lines, n.label.call(this, t)), + Fo(e.after, Bo(n.afterLabel.call(this, t))), + s.push(e) + } + )), + s + } + getAfterBody(t, e) { + return Yo(e.callbacks.afterBody.apply(this, [t])) + } + getFooter(t, e) { + const {callbacks: i} = e + , s = i.beforeFooter.apply(this, [t]) + , n = i.footer.apply(this, [t]) + , o = i.afterFooter.apply(this, [t]); + let a = []; + return a = Fo(a, Bo(s)), + a = Fo(a, Bo(n)), + a = Fo(a, Bo(o)), + a + } + _createItems(t) { + const e = this._active + , i = this.chart.data + , s = [] + , n = [] + , o = []; + let a, r, l = []; + for (a = 0, + r = e.length; a < r; ++a) + l.push(Vo(this.chart, e[a])); + return t.filter && (l = l.filter(((e,s,n)=>t.filter(e, s, n, i)))), + t.itemSort && (l = l.sort(((e,s)=>t.itemSort(e, s, i)))), + et(l, (e=>{ + const i = Uo(t.callbacks, e); + s.push(i.labelColor.call(this, e)), + n.push(i.labelPointStyle.call(this, e)), + o.push(i.labelTextColor.call(this, e)) + } + )), + this.labelColors = s, + this.labelPointStyles = n, + this.labelTextColors = o, + this.dataPoints = l, + l + } + update(t, e) { + const i = this.options.setContext(this.getContext()) + , s = this._active; + let n, o = []; + if (s.length) { + const t = zo[i.position].call(this, s, this._eventPosition); + o = this._createItems(i), + this.title = this.getTitle(o, i), + this.beforeBody = this.getBeforeBody(o, i), + this.body = this.getBody(o, i), + this.afterBody = this.getAfterBody(o, i), + this.footer = this.getFooter(o, i); + const e = this._size = Wo(this, i) + , a = Object.assign({}, t, e) + , r = jo(this.chart, i, a) + , l = Ho(i, a, r, this.chart); + this.xAlign = r.xAlign, + this.yAlign = r.yAlign, + n = { + opacity: 1, + x: l.x, + y: l.y, + width: e.width, + height: e.height, + caretX: t.x, + caretY: t.y + } + } else + 0 !== this.opacity && (n = { + opacity: 0 + }); + this._tooltipItems = o, + this.$context = void 0, + n && this._resolveAnimations().update(this, n), + t && i.external && i.external.call(this, { + chart: this.chart, + tooltip: this, + replay: e + }) + } + drawCaret(t, e, i, s) { + const n = this.getCaretPosition(t, i, s); + e.lineTo(n.x1, n.y1), + e.lineTo(n.x2, n.y2), + e.lineTo(n.x3, n.y3) + } + getCaretPosition(t, e, i) { + const {xAlign: s, yAlign: n} = this + , {caretSize: o, cornerRadius: a} = i + , {topLeft: r, topRight: l, bottomLeft: h, bottomRight: c} = di(a) + , {x: d, y: u} = t + , {width: f, height: g} = e; + let p, m, b, x, _, y; + return "center" === n ? (_ = u + g / 2, + "left" === s ? (p = d, + m = p - o, + x = _ + o, + y = _ - o) : (p = d + f, + m = p + o, + x = _ - o, + y = _ + o), + b = p) : (m = "left" === s ? d + Math.max(r, h) + o : "right" === s ? d + f - Math.max(l, c) - o : this.caretX, + "top" === n ? (x = u, + _ = x - o, + p = m - o, + b = m + o) : (x = u + g, + _ = x + o, + p = m + o, + b = m - o), + y = x), + { + x1: p, + x2: m, + x3: b, + y1: x, + y2: _, + y3: y + } + } + drawTitle(t, e, i) { + const s = this.title + , n = s.length; + let o, a, r; + if (n) { + const l = bi(i.rtl, this.x, this.width); + for (t.x = $o(this, i.titleAlign, i), + e.textAlign = l.textAlign(i.titleAlign), + e.textBaseline = "middle", + o = fi(i.titleFont), + a = i.titleSpacing, + e.fillStyle = i.titleColor, + e.font = o.string, + r = 0; r < n; ++r) + e.fillText(s[r], l.x(t.x), t.y + o.lineHeight / 2), + t.y += o.lineHeight + a, + r + 1 === n && (t.y += i.titleMarginBottom - a) + } + } + _drawColorBox(t, e, i, s, n) { + const o = this.labelColors[i] + , a = this.labelPointStyles[i] + , {boxHeight: r, boxWidth: l, boxPadding: h} = n + , c = fi(n.bodyFont) + , d = $o(this, "left", n) + , u = s.x(d) + , f = r < c.lineHeight ? (c.lineHeight - r) / 2 : 0 + , g = e.y + f; + if (n.usePointStyle) { + const e = { + radius: Math.min(l, r) / 2, + pointStyle: a.pointStyle, + rotation: a.rotation, + borderWidth: 1 + } + , i = s.leftForLtr(u, l) + l / 2 + , h = g + r / 2; + t.strokeStyle = n.multiKeyBackground, + t.fillStyle = n.multiKeyBackground, + ye(t, e, i, h), + t.strokeStyle = o.borderColor, + t.fillStyle = o.backgroundColor, + ye(t, e, i, h) + } else { + t.lineWidth = q(o.borderWidth) ? Math.max(...Object.values(o.borderWidth)) : o.borderWidth || 1, + t.strokeStyle = o.borderColor, + t.setLineDash(o.borderDash || []), + t.lineDashOffset = o.borderDashOffset || 0; + const e = s.leftForLtr(u, l - h) + , i = s.leftForLtr(s.xPlus(u, 1), l - h - 2) + , a = di(o.borderRadius); + Object.values(a).some((t=>0 !== t)) ? (t.beginPath(), + t.fillStyle = n.multiKeyBackground, + Oe(t, { + x: e, + y: g, + w: l, + h: r, + radius: a + }), + t.fill(), + t.stroke(), + t.fillStyle = o.backgroundColor, + t.beginPath(), + Oe(t, { + x: i, + y: g + 1, + w: l - 2, + h: r - 2, + radius: a + }), + t.fill()) : (t.fillStyle = n.multiKeyBackground, + t.fillRect(e, g, l, r), + t.strokeRect(e, g, l, r), + t.fillStyle = o.backgroundColor, + t.fillRect(i, g + 1, l - 2, r - 2)) + } + t.fillStyle = this.labelTextColors[i] + } + drawBody(t, e, i) { + const {body: s} = this + , {bodySpacing: n, bodyAlign: o, displayColors: a, boxHeight: r, boxWidth: l, boxPadding: h} = i + , c = fi(i.bodyFont); + let d = c.lineHeight + , u = 0; + const f = bi(i.rtl, this.x, this.width) + , g = function(i) { + e.fillText(i, f.x(t.x + u), t.y + d / 2), + t.y += d + n + } + , p = f.textAlign(o); + let m, b, x, _, y, v, w; + for (e.textAlign = o, + e.textBaseline = "middle", + e.font = c.string, + t.x = $o(this, p, i), + e.fillStyle = i.bodyColor, + et(this.beforeBody, g), + u = a && "right" !== p ? "center" === o ? l / 2 + h : l + 2 + h : 0, + _ = 0, + v = s.length; _ < v; ++_) { + for (m = s[_], + b = this.labelTextColors[_], + e.fillStyle = b, + et(m.before, g), + x = m.lines, + a && x.length && (this._drawColorBox(e, t, _, f, i), + d = Math.max(c.lineHeight, r)), + y = 0, + w = x.length; y < w; ++y) + g(x[y]), + d = c.lineHeight; + et(m.after, g) + } + u = 0, + d = c.lineHeight, + et(this.afterBody, g), + t.y -= n + } + drawFooter(t, e, i) { + const s = this.footer + , n = s.length; + let o, a; + if (n) { + const r = bi(i.rtl, this.x, this.width); + for (t.x = $o(this, i.footerAlign, i), + t.y += i.footerMarginTop, + e.textAlign = r.textAlign(i.footerAlign), + e.textBaseline = "middle", + o = fi(i.footerFont), + e.fillStyle = i.footerColor, + e.font = o.string, + a = 0; a < n; ++a) + e.fillText(s[a], r.x(t.x), t.y + o.lineHeight / 2), + t.y += o.lineHeight + i.footerSpacing + } + } + drawBackground(t, e, i, s) { + const {xAlign: n, yAlign: o} = this + , {x: a, y: r} = t + , {width: l, height: h} = i + , {topLeft: c, topRight: d, bottomLeft: u, bottomRight: f} = di(s.cornerRadius); + e.fillStyle = s.backgroundColor, + e.strokeStyle = s.borderColor, + e.lineWidth = s.borderWidth, + e.beginPath(), + e.moveTo(a + c, r), + "top" === o && this.drawCaret(t, e, i, s), + e.lineTo(a + l - d, r), + e.quadraticCurveTo(a + l, r, a + l, r + d), + "center" === o && "right" === n && this.drawCaret(t, e, i, s), + e.lineTo(a + l, r + h - f), + e.quadraticCurveTo(a + l, r + h, a + l - f, r + h), + "bottom" === o && this.drawCaret(t, e, i, s), + e.lineTo(a + u, r + h), + e.quadraticCurveTo(a, r + h, a, r + h - u), + "center" === o && "left" === n && this.drawCaret(t, e, i, s), + e.lineTo(a, r + c), + e.quadraticCurveTo(a, r, a + c, r), + e.closePath(), + e.fill(), + s.borderWidth > 0 && e.stroke() + } + _updateAnimationTarget(t) { + const e = this.chart + , i = this.$animations + , s = i && i.x + , n = i && i.y; + if (s || n) { + const i = zo[t.position].call(this, this._active, this._eventPosition); + if (!i) + return; + const o = this._size = Wo(this, t) + , a = Object.assign({}, i, this._size) + , r = jo(e, t, a) + , l = Ho(t, a, r, e); + s._to === l.x && n._to === l.y || (this.xAlign = r.xAlign, + this.yAlign = r.yAlign, + this.width = o.width, + this.height = o.height, + this.caretX = i.x, + this.caretY = i.y, + this._resolveAnimations().update(this, l)) + } + } + _willRender() { + return !!this.opacity + } + draw(t) { + const e = this.options.setContext(this.getContext()); + let i = this.opacity; + if (!i) + return; + this._updateAnimationTarget(e); + const s = { + width: this.width, + height: this.height + } + , n = { + x: this.x, + y: this.y + }; + i = Math.abs(i) < .001 ? 0 : i; + const o = ui(e.padding) + , a = this.title.length || this.beforeBody.length || this.body.length || this.afterBody.length || this.footer.length; + e.enabled && a && (t.save(), + t.globalAlpha = i, + this.drawBackground(n, t, s, e), + xi(t, e.textDirection), + n.y += o.top, + this.drawTitle(n, t, e), + this.drawBody(n, t, e), + this.drawFooter(n, t, e), + _i(t, e.textDirection), + t.restore()) + } + getActiveElements() { + return this._active || [] + } + setActiveElements(t, e) { + const i = this._active + , s = t.map((({datasetIndex: t, index: e})=>{ + const i = this.chart.getDatasetMeta(t); + if (!i) + throw new Error("Cannot find a dataset at index " + t); + return { + datasetIndex: t, + element: i.data[e], + index: e + } + } + )) + , n = !it(i, s) + , o = this._positionChanged(s, e); + (n || o) && (this._active = s, + this._eventPosition = e, + this._ignoreReplayEvents = !0, + this.update(!0)) + } + handleEvent(t, e, i=!0) { + if (e && this._ignoreReplayEvents) + return !1; + this._ignoreReplayEvents = !1; + const s = this.options + , n = this._active || [] + , o = this._getActiveElements(t, n, e, i) + , a = this._positionChanged(o, t) + , r = e || !it(o, n) || a; + return r && (this._active = o, + (s.enabled || s.external) && (this._eventPosition = { + x: t.x, + y: t.y + }, + this.update(!0, e))), + r + } + _getActiveElements(t, e, i, s) { + const n = this.options; + if ("mouseout" === t.type) + return []; + if (!s) + return e; + const o = this.chart.getElementsAtEventForMode(t, n.mode, n, i); + return n.reverse && o.reverse(), + o + } + _positionChanged(t, e) { + const {caretX: i, caretY: s, options: n} = this + , o = zo[n.position].call(this, t, e); + return !1 !== o && (i !== o.x || s !== o.y) + } + } + Xo.positioners = zo; + var qo = { + id: "tooltip", + _element: Xo, + positioners: zo, + afterInit(t, e, i) { + i && (t.tooltip = new Xo({ + chart: t, + options: i + })) + }, + beforeUpdate(t, e, i) { + t.tooltip && t.tooltip.initialize(i) + }, + reset(t, e, i) { + t.tooltip && t.tooltip.initialize(i) + }, + afterDraw(t) { + const e = t.tooltip; + if (e && e._willRender()) { + const i = { + tooltip: e + }; + if (!1 === t.notifyPlugins("beforeTooltipDraw", i)) + return; + e.draw(t.ctx), + t.notifyPlugins("afterTooltipDraw", i) + } + }, + afterEvent(t, e) { + if (t.tooltip) { + const i = e.replay; + t.tooltip.handleEvent(e.event, i, e.inChartArea) && (e.changed = !0) + } + }, + defaults: { + enabled: !0, + external: null, + position: "average", + backgroundColor: "rgba(0,0,0,0.8)", + titleColor: "#fff", + titleFont: { + weight: "bold" + }, + titleSpacing: 2, + titleMarginBottom: 6, + titleAlign: "left", + bodyColor: "#fff", + bodySpacing: 2, + bodyFont: {}, + bodyAlign: "left", + footerColor: "#fff", + footerSpacing: 2, + footerMarginTop: 6, + footerFont: { + weight: "bold" + }, + footerAlign: "left", + padding: 6, + caretPadding: 2, + caretSize: 5, + cornerRadius: 6, + boxHeight: (t,e)=>e.bodyFont.size, + boxWidth: (t,e)=>e.bodyFont.size, + multiKeyBackground: "#fff", + displayColors: !0, + boxPadding: 0, + borderColor: "rgba(0,0,0,0)", + borderWidth: 0, + animation: { + duration: 400, + easing: "easeOutQuart" + }, + animations: { + numbers: { + type: "number", + properties: ["x", "y", "width", "height", "caretX", "caretY"] + }, + opacity: { + easing: "linear", + duration: 200 + } + }, + callbacks: { + beforeTitle: $, + title(t) { + if (t.length > 0) { + const e = t[0] + , i = e.chart.data.labels + , s = i ? i.length : 0; + if (this && this.options && "dataset" === this.options.mode) + return e.dataset.label || ""; + if (e.label) + return e.label; + if (s > 0 && e.dataIndex < s) + return i[e.dataIndex] + } + return "" + }, + afterTitle: $, + beforeBody: $, + beforeLabel: $, + label(t) { + if (this && this.options && "dataset" === this.options.mode) + return t.label + ": " + t.formattedValue || t.formattedValue; + let e = t.dataset.label || ""; + e && (e += ": "); + const i = t.formattedValue; + return U(i) || (e += i), + e + }, + labelColor(t) { + const e = t.chart.getDatasetMeta(t.datasetIndex).controller.getStyle(t.dataIndex); + return { + borderColor: e.borderColor, + backgroundColor: e.backgroundColor, + borderWidth: e.borderWidth, + borderDash: e.borderDash, + borderDashOffset: e.borderDashOffset, + borderRadius: 0 + } + }, + labelTextColor() { + return this.options.bodyColor + }, + labelPointStyle(t) { + const e = t.chart.getDatasetMeta(t.datasetIndex).controller.getStyle(t.dataIndex); + return { + pointStyle: e.pointStyle, + rotation: e.rotation + } + }, + afterLabel: $, + afterBody: $, + beforeFooter: $, + footer: $, + afterFooter: $ + } + }, + defaultRoutes: { + bodyFont: "font", + footerFont: "font", + titleFont: "font" + }, + descriptors: { + _scriptable: t=>"filter" !== t && "itemSort" !== t && "external" !== t, + _indexable: !1, + callbacks: { + _scriptable: !1, + _indexable: !1 + }, + animation: { + _fallback: !1 + }, + animations: { + _fallback: "animation" + } + }, + additionalOptionScopes: ["interaction"] + } + , Ko = Object.freeze({ + __proto__: null, + Decimation: co, + Filler: Co, + Legend: To, + SubTitle: Io, + Title: Ro, + Tooltip: qo + }); + function Go(t, e, i, s) { + const n = t.indexOf(e); + if (-1 === n) + return ((t,e,i,s)=>("string" == typeof e ? (i = t.push(e) - 1, + s.unshift({ + index: i, + label: e + })) : isNaN(e) && (i = null), + i))(t, e, i, s); + return n !== t.lastIndexOf(e) ? i : n + } + class Zo extends Ns { + constructor(t) { + super(t), + this._startValue = void 0, + this._valueRange = 0, + this._addedLabels = [] + } + init(t) { + const e = this._addedLabels; + if (e.length) { + const t = this.getLabels(); + for (const {index: i, label: s} of e) + t[i] === s && t.splice(i, 1); + this._addedLabels = [] + } + super.init(t) + } + parse(t, e) { + if (U(t)) + return null; + const i = this.getLabels(); + return ((t,e)=>null === t ? null : Qt(Math.round(t), 0, e))(e = isFinite(e) && i[e] === t ? e : Go(i, t, Z(e, t), this._addedLabels), i.length - 1) + } + determineDataLimits() { + const {minDefined: t, maxDefined: e} = this.getUserBounds(); + let {min: i, max: s} = this.getMinMax(!0); + "ticks" === this.options.bounds && (t || (i = 0), + e || (s = this.getLabels().length - 1)), + this.min = i, + this.max = s + } + buildTicks() { + const t = this.min + , e = this.max + , i = this.options.offset + , s = []; + let n = this.getLabels(); + n = 0 === t && e === n.length - 1 ? n : n.slice(t, e + 1), + this._valueRange = Math.max(n.length - (i ? 0 : 1), 1), + this._startValue = this.min - (i ? .5 : 0); + for (let i = t; i <= e; i++) + s.push({ + value: i + }); + return s + } + getLabelForValue(t) { + const e = this.getLabels(); + return t >= 0 && t < e.length ? e[t] : t + } + configure() { + super.configure(), + this.isHorizontal() || (this._reversePixels = !this._reversePixels) + } + getPixelForValue(t) { + return "number" != typeof t && (t = this.parse(t)), + null === t ? NaN : this.getPixelForDecimal((t - this._startValue) / this._valueRange) + } + getPixelForTick(t) { + const e = this.ticks; + return t < 0 || t > e.length - 1 ? null : this.getPixelForValue(e[t].value) + } + getValueForPixel(t) { + return Math.round(this._startValue + this.getDecimalForPixel(t) * this._valueRange) + } + getBasePixel() { + return this.bottom + } + } + function Jo(t, e, {horizontal: i, minRotation: s}) { + const n = Yt(s) + , o = (i ? Math.sin(n) : Math.cos(n)) || .001 + , a = .75 * e * ("" + t).length; + return Math.min(e / o, a) + } + Zo.id = "category", + Zo.defaults = { + ticks: { + callback: Zo.prototype.getLabelForValue + } + }; + class Qo extends Ns { + constructor(t) { + super(t), + this.start = void 0, + this.end = void 0, + this._startValue = void 0, + this._endValue = void 0, + this._valueRange = 0 + } + parse(t, e) { + return U(t) || ("number" == typeof t || t instanceof Number) && !isFinite(+t) ? null : +t + } + handleTickRangeOptions() { + const {beginAtZero: t} = this.options + , {minDefined: e, maxDefined: i} = this.getUserBounds(); + let {min: s, max: n} = this; + const o = t=>s = e ? s : t + , a = t=>n = i ? n : t; + if (t) { + const t = Bt(s) + , e = Bt(n); + t < 0 && e < 0 ? a(0) : t > 0 && e > 0 && o(0) + } + if (s === n) { + let e = 1; + (n >= Number.MAX_SAFE_INTEGER || s <= Number.MIN_SAFE_INTEGER) && (e = Math.abs(.05 * n)), + a(n + e), + t || o(s - e) + } + this.min = s, + this.max = n + } + getTickLimit() { + const t = this.options.ticks; + let e, {maxTicksLimit: i, stepSize: s} = t; + return s ? (e = Math.ceil(this.max / s) - Math.floor(this.min / s) + 1, + e > 1e3 && (console.warn(`scales.${this.id}.ticks.stepSize: ${s} would result generating up to ${e} ticks. Limiting to 1000.`), + e = 1e3)) : (e = this.computeTickLimit(), + i = i || 11), + i && (e = Math.min(i, e)), + e + } + computeTickLimit() { + return Number.POSITIVE_INFINITY + } + buildTicks() { + const t = this.options + , e = t.ticks; + let i = this.getTickLimit(); + i = Math.max(2, i); + const s = function(t, e) { + const i = [] + , {bounds: s, step: n, min: o, max: a, precision: r, count: l, maxTicks: h, maxDigits: c, includeBounds: d} = t + , u = n || 1 + , f = h - 1 + , {min: g, max: p} = e + , m = !U(o) + , b = !U(a) + , x = !U(l) + , _ = (p - g) / (c + 1); + let y, v, w, M, k = Vt((p - g) / f / u) * u; + if (k < 1e-14 && !m && !b) + return [{ + value: g + }, { + value: p + }]; + M = Math.ceil(p / k) - Math.floor(g / k), + M > f && (k = Vt(M * k / f / u) * u), + U(r) || (y = Math.pow(10, r), + k = Math.ceil(k * y) / y), + "ticks" === s ? (v = Math.floor(g / k) * k, + w = Math.ceil(p / k) * k) : (v = g, + w = p), + m && b && n && Ht((a - o) / n, k / 1e3) ? (M = Math.round(Math.min((a - o) / k, h)), + k = (a - o) / M, + v = o, + w = a) : x ? (v = m ? o : v, + w = b ? a : w, + M = l - 1, + k = (w - v) / M) : (M = (w - v) / k, + M = jt(M, Math.round(M), k / 1e3) ? Math.round(M) : Math.ceil(M)); + const S = Math.max(Xt(k), Xt(v)); + y = Math.pow(10, U(r) ? S : r), + v = Math.round(v * y) / y, + w = Math.round(w * y) / y; + let P = 0; + for (m && (d && v !== o ? (i.push({ + value: o + }), + v < o && P++, + jt(Math.round((v + P * k) * y) / y, o, Jo(o, _, t)) && P++) : v < o && P++); P < M; ++P) + i.push({ + value: Math.round((v + P * k) * y) / y + }); + return b && d && w !== a ? i.length && jt(i[i.length - 1].value, a, Jo(a, _, t)) ? i[i.length - 1].value = a : i.push({ + value: a + }) : b && w !== a || i.push({ + value: w + }), + i + }({ + maxTicks: i, + bounds: t.bounds, + min: t.min, + max: t.max, + precision: e.precision, + step: e.stepSize, + count: e.count, + maxDigits: this._maxDigits(), + horizontal: this.isHorizontal(), + minRotation: e.minRotation || 0, + includeBounds: !1 !== e.includeBounds + }, this._range || this); + return "ticks" === t.bounds && $t(s, this, "value"), + t.reverse ? (s.reverse(), + this.start = this.max, + this.end = this.min) : (this.start = this.min, + this.end = this.max), + s + } + configure() { + const t = this.ticks; + let e = this.min + , i = this.max; + if (super.configure(), + this.options.offset && t.length) { + const s = (i - e) / Math.max(t.length - 1, 1) / 2; + e -= s, + i += s + } + this._startValue = e, + this._endValue = i, + this._valueRange = i - e + } + getLabelForValue(t) { + return oi(t, this.chart.options.locale, this.options.ticks.format) + } + } + class ta extends Qo { + determineDataLimits() { + const {min: t, max: e} = this.getMinMax(!0); + this.min = K(t) ? t : 0, + this.max = K(e) ? e : 1, + this.handleTickRangeOptions() + } + computeTickLimit() { + const t = this.isHorizontal() + , e = t ? this.width : this.height + , i = Yt(this.options.ticks.minRotation) + , s = (t ? Math.sin(i) : Math.cos(i)) || .001 + , n = this._resolveTickFontOptions(0); + return Math.ceil(e / Math.min(40, n.lineHeight / s)) + } + getPixelForValue(t) { + return null === t ? NaN : this.getPixelForDecimal((t - this._startValue) / this._valueRange) + } + getValueForPixel(t) { + return this._startValue + this.getDecimalForPixel(t) * this._valueRange + } + } + function ea(t) { + return 1 === t / Math.pow(10, Math.floor(Ft(t))) + } + ta.id = "linear", + ta.defaults = { + ticks: { + callback: Ls.formatters.numeric + } + }; + class ia extends Ns { + constructor(t) { + super(t), + this.start = void 0, + this.end = void 0, + this._startValue = void 0, + this._valueRange = 0 + } + parse(t, e) { + const i = Qo.prototype.parse.apply(this, [t, e]); + if (0 !== i) + return K(i) && i > 0 ? i : null; + this._zero = !0 + } + determineDataLimits() { + const {min: t, max: e} = this.getMinMax(!0); + this.min = K(t) ? Math.max(0, t) : null, + this.max = K(e) ? Math.max(0, e) : null, + this.options.beginAtZero && (this._zero = !0), + this.handleTickRangeOptions() + } + handleTickRangeOptions() { + const {minDefined: t, maxDefined: e} = this.getUserBounds(); + let i = this.min + , s = this.max; + const n = e=>i = t ? i : e + , o = t=>s = e ? s : t + , a = (t,e)=>Math.pow(10, Math.floor(Ft(t)) + e); + i === s && (i <= 0 ? (n(1), + o(10)) : (n(a(i, -1)), + o(a(s, 1)))), + i <= 0 && n(a(s, -1)), + s <= 0 && o(a(i, 1)), + this._zero && this.min !== this._suggestedMin && i === a(this.min, 0) && n(a(i, -1)), + this.min = i, + this.max = s + } + buildTicks() { + const t = this.options + , e = function(t, e) { + const i = Math.floor(Ft(e.max)) + , s = Math.ceil(e.max / Math.pow(10, i)) + , n = []; + let o = G(t.min, Math.pow(10, Math.floor(Ft(e.min)))) + , a = Math.floor(Ft(o)) + , r = Math.floor(o / Math.pow(10, a)) + , l = a < 0 ? Math.pow(10, Math.abs(a)) : 1; + do { + n.push({ + value: o, + major: ea(o) + }), + ++r, + 10 === r && (r = 1, + ++a, + l = a >= 0 ? 1 : l), + o = Math.round(r * Math.pow(10, a) * l) / l + } while (a < i || a === i && r < s); + const h = G(t.max, o); + return n.push({ + value: h, + major: ea(o) + }), + n + }({ + min: this._userMin, + max: this._userMax + }, this); + return "ticks" === t.bounds && $t(e, this, "value"), + t.reverse ? (e.reverse(), + this.start = this.max, + this.end = this.min) : (this.start = this.min, + this.end = this.max), + e + } + getLabelForValue(t) { + return void 0 === t ? "0" : oi(t, this.chart.options.locale, this.options.ticks.format) + } + configure() { + const t = this.min; + super.configure(), + this._startValue = Ft(t), + this._valueRange = Ft(this.max) - Ft(t) + } + getPixelForValue(t) { + return void 0 !== t && 0 !== t || (t = this.min), + null === t || isNaN(t) ? NaN : this.getPixelForDecimal(t === this.min ? 0 : (Ft(t) - this._startValue) / this._valueRange) + } + getValueForPixel(t) { + const e = this.getDecimalForPixel(t); + return Math.pow(10, this._startValue + e * this._valueRange) + } + } + function sa(t) { + const e = t.ticks; + if (e.display && t.display) { + const t = ui(e.backdropPadding); + return Z(e.font && e.font.size, yt.font.size) + t.height + } + return 0 + } + function na(t, e, i, s, n) { + return t === s || t === n ? { + start: e - i / 2, + end: e + i / 2 + } : t < s || t > n ? { + start: e - i, + end: e + } : { + start: e, + end: e + i + } + } + function oa(t) { + const e = { + l: t.left + t._padding.left, + r: t.right - t._padding.right, + t: t.top + t._padding.top, + b: t.bottom - t._padding.bottom + } + , i = Object.assign({}, e) + , s = [] + , n = [] + , o = t._pointLabels.length + , a = t.options.pointLabels + , r = a.centerPointLabels ? Ot / o : 0; + for (let d = 0; d < o; d++) { + const o = a.setContext(t.getPointLabelContext(d)); + n[d] = o.padding; + const u = t.getPointPosition(d, t.drawingArea + n[d], r) + , f = fi(o.font) + , g = (l = t.ctx, + h = f, + c = X(c = t._pointLabels[d]) ? c : [c], + { + w: be(l, h.string, c), + h: c.length * h.lineHeight + }); + s[d] = g; + const p = Zt(t.getIndexAngle(d) + r) + , m = Math.round(Ut(p)); + aa(i, e, p, na(m, u.x, g.w, 0, 180), na(m, u.y, g.h, 90, 270)) + } + var l, h, c; + t.setCenterPoint(e.l - i.l, i.r - e.r, e.t - i.t, i.b - e.b), + t._pointLabelItems = function(t, e, i) { + const s = [] + , n = t._pointLabels.length + , o = t.options + , a = sa(o) / 2 + , r = t.drawingArea + , l = o.pointLabels.centerPointLabels ? Ot / n : 0; + for (let o = 0; o < n; o++) { + const n = t.getPointPosition(o, r + a + i[o], l) + , h = Math.round(Ut(Zt(n.angle + Et))) + , c = e[o] + , d = ha(n.y, c.h, h) + , u = ra(h) + , f = la(n.x, c.w, u); + s.push({ + x: n.x, + y: d, + textAlign: u, + left: f, + top: d, + right: f + c.w, + bottom: d + c.h + }) + } + return s + }(t, s, n) + } + function aa(t, e, i, s, n) { + const o = Math.abs(Math.sin(i)) + , a = Math.abs(Math.cos(i)); + let r = 0 + , l = 0; + s.start < e.l ? (r = (e.l - s.start) / o, + t.l = Math.min(t.l, e.l - r)) : s.end > e.r && (r = (s.end - e.r) / o, + t.r = Math.max(t.r, e.r + r)), + n.start < e.t ? (l = (e.t - n.start) / a, + t.t = Math.min(t.t, e.t - l)) : n.end > e.b && (l = (n.end - e.b) / a, + t.b = Math.max(t.b, e.b + l)) + } + function ra(t) { + return 0 === t || 180 === t ? "center" : t < 180 ? "left" : "right" + } + function la(t, e, i) { + return "right" === i ? t -= e : "center" === i && (t -= e / 2), + t + } + function ha(t, e, i) { + return 90 === i || 270 === i ? t -= e / 2 : (i > 270 || i < 90) && (t -= e), + t + } + function ca(t, e, i, s) { + const {ctx: n} = t; + if (i) + n.arc(t.xCenter, t.yCenter, e, 0, At); + else { + let i = t.getPointPosition(0, e); + n.moveTo(i.x, i.y); + for (let o = 1; o < s; o++) + i = t.getPointPosition(o, e), + n.lineTo(i.x, i.y) + } + } + ia.id = "logarithmic", + ia.defaults = { + ticks: { + callback: Ls.formatters.logarithmic, + major: { + enabled: !0 + } + } + }; + class da extends Qo { + constructor(t) { + super(t), + this.xCenter = void 0, + this.yCenter = void 0, + this.drawingArea = void 0, + this._pointLabels = [], + this._pointLabelItems = [] + } + setDimensions() { + const t = this._padding = ui(sa(this.options) / 2) + , e = this.width = this.maxWidth - t.width + , i = this.height = this.maxHeight - t.height; + this.xCenter = Math.floor(this.left + e / 2 + t.left), + this.yCenter = Math.floor(this.top + i / 2 + t.top), + this.drawingArea = Math.floor(Math.min(e, i) / 2) + } + determineDataLimits() { + const {min: t, max: e} = this.getMinMax(!1); + this.min = K(t) && !isNaN(t) ? t : 0, + this.max = K(e) && !isNaN(e) ? e : 0, + this.handleTickRangeOptions() + } + computeTickLimit() { + return Math.ceil(this.drawingArea / sa(this.options)) + } + generateTickLabels(t) { + Qo.prototype.generateTickLabels.call(this, t), + this._pointLabels = this.getLabels().map(((t,e)=>{ + const i = tt(this.options.pointLabels.callback, [t, e], this); + return i || 0 === i ? i : "" + } + )).filter(((t,e)=>this.chart.getDataVisibility(e))) + } + fit() { + const t = this.options; + t.display && t.pointLabels.display ? oa(this) : this.setCenterPoint(0, 0, 0, 0) + } + setCenterPoint(t, e, i, s) { + this.xCenter += Math.floor((t - e) / 2), + this.yCenter += Math.floor((i - s) / 2), + this.drawingArea -= Math.min(this.drawingArea / 2, Math.max(t, e, i, s)) + } + getIndexAngle(t) { + return Zt(t * (At / (this._pointLabels.length || 1)) + Yt(this.options.startAngle || 0)) + } + getDistanceFromCenterForValue(t) { + if (U(t)) + return NaN; + const e = this.drawingArea / (this.max - this.min); + return this.options.reverse ? (this.max - t) * e : (t - this.min) * e + } + getValueForDistanceFromCenter(t) { + if (U(t)) + return NaN; + const e = t / (this.drawingArea / (this.max - this.min)); + return this.options.reverse ? this.max - e : this.min + e + } + getPointLabelContext(t) { + const e = this._pointLabels || []; + if (t >= 0 && t < e.length) { + const i = e[t]; + return function(t, e, i) { + return mi(t, { + label: i, + index: e, + type: "pointLabel" + }) + }(this.getContext(), t, i) + } + } + getPointPosition(t, e, i=0) { + const s = this.getIndexAngle(t) - Et + i; + return { + x: Math.cos(s) * e + this.xCenter, + y: Math.sin(s) * e + this.yCenter, + angle: s + } + } + getPointPositionForValue(t, e) { + return this.getPointPosition(t, this.getDistanceFromCenterForValue(e)) + } + getBasePosition(t) { + return this.getPointPositionForValue(t || 0, this.getBaseValue()) + } + getPointLabelPosition(t) { + const {left: e, top: i, right: s, bottom: n} = this._pointLabelItems[t]; + return { + left: e, + top: i, + right: s, + bottom: n + } + } + drawBackground() { + const {backgroundColor: t, grid: {circular: e}} = this.options; + if (t) { + const i = this.ctx; + i.save(), + i.beginPath(), + ca(this, this.getDistanceFromCenterForValue(this._endValue), e, this._pointLabels.length), + i.closePath(), + i.fillStyle = t, + i.fill(), + i.restore() + } + } + drawGrid() { + const t = this.ctx + , e = this.options + , {angleLines: i, grid: s} = e + , n = this._pointLabels.length; + let o, a, r; + if (e.pointLabels.display && function(t, e) { + const {ctx: i, options: {pointLabels: s}} = t; + for (let n = e - 1; n >= 0; n--) { + const e = s.setContext(t.getPointLabelContext(n)) + , o = fi(e.font) + , {x: a, y: r, textAlign: l, left: h, top: c, right: d, bottom: u} = t._pointLabelItems[n] + , {backdropColor: f} = e; + if (!U(f)) { + const t = di(e.borderRadius) + , s = ui(e.backdropPadding); + i.fillStyle = f; + const n = h - s.left + , o = c - s.top + , a = d - h + s.width + , r = u - c + s.height; + Object.values(t).some((t=>0 !== t)) ? (i.beginPath(), + Oe(i, { + x: n, + y: o, + w: a, + h: r, + radius: t + }), + i.fill()) : i.fillRect(n, o, a, r) + } + De(i, t._pointLabels[n], a, r + o.lineHeight / 2, o, { + color: e.color, + textAlign: l, + textBaseline: "middle" + }) + } + }(this, n), + s.display && this.ticks.forEach(((t,e)=>{ + if (0 !== e) { + a = this.getDistanceFromCenterForValue(t.value); + !function(t, e, i, s) { + const n = t.ctx + , o = e.circular + , {color: a, lineWidth: r} = e; + !o && !s || !a || !r || i < 0 || (n.save(), + n.strokeStyle = a, + n.lineWidth = r, + n.setLineDash(e.borderDash), + n.lineDashOffset = e.borderDashOffset, + n.beginPath(), + ca(t, i, o, s), + n.closePath(), + n.stroke(), + n.restore()) + }(this, s.setContext(this.getContext(e - 1)), a, n) + } + } + )), + i.display) { + for (t.save(), + o = n - 1; o >= 0; o--) { + const s = i.setContext(this.getPointLabelContext(o)) + , {color: n, lineWidth: l} = s; + l && n && (t.lineWidth = l, + t.strokeStyle = n, + t.setLineDash(s.borderDash), + t.lineDashOffset = s.borderDashOffset, + a = this.getDistanceFromCenterForValue(e.ticks.reverse ? this.min : this.max), + r = this.getPointPosition(o, a), + t.beginPath(), + t.moveTo(this.xCenter, this.yCenter), + t.lineTo(r.x, r.y), + t.stroke()) + } + t.restore() + } + } + drawBorder() {} + drawLabels() { + const t = this.ctx + , e = this.options + , i = e.ticks; + if (!i.display) + return; + const s = this.getIndexAngle(0); + let n, o; + t.save(), + t.translate(this.xCenter, this.yCenter), + t.rotate(s), + t.textAlign = "center", + t.textBaseline = "middle", + this.ticks.forEach(((s,a)=>{ + if (0 === a && !e.reverse) + return; + const r = i.setContext(this.getContext(a)) + , l = fi(r.font); + if (n = this.getDistanceFromCenterForValue(this.ticks[a].value), + r.showLabelBackdrop) { + t.font = l.string, + o = t.measureText(s.label).width, + t.fillStyle = r.backdropColor; + const e = ui(r.backdropPadding); + t.fillRect(-o / 2 - e.left, -n - l.size / 2 - e.top, o + e.width, l.size + e.height) + } + De(t, s.label, 0, -n, l, { + color: r.color + }) + } + )), + t.restore() + } + drawTitle() {} + } + da.id = "radialLinear", + da.defaults = { + display: !0, + animate: !0, + position: "chartArea", + angleLines: { + display: !0, + lineWidth: 1, + borderDash: [], + borderDashOffset: 0 + }, + grid: { + circular: !1 + }, + startAngle: 0, + ticks: { + showLabelBackdrop: !0, + callback: Ls.formatters.numeric + }, + pointLabels: { + backdropColor: void 0, + backdropPadding: 2, + display: !0, + font: { + size: 10 + }, + callback: t=>t, + padding: 5, + centerPointLabels: !1 + } + }, + da.defaultRoutes = { + "angleLines.color": "borderColor", + "pointLabels.color": "color", + "ticks.color": "color" + }, + da.descriptors = { + angleLines: { + _fallback: "grid" + } + }; + const ua = { + millisecond: { + common: !0, + size: 1, + steps: 1e3 + }, + second: { + common: !0, + size: 1e3, + steps: 60 + }, + minute: { + common: !0, + size: 6e4, + steps: 60 + }, + hour: { + common: !0, + size: 36e5, + steps: 24 + }, + day: { + common: !0, + size: 864e5, + steps: 30 + }, + week: { + common: !1, + size: 6048e5, + steps: 4 + }, + month: { + common: !0, + size: 2628e6, + steps: 12 + }, + quarter: { + common: !1, + size: 7884e6, + steps: 4 + }, + year: { + common: !0, + size: 3154e7 + } + } + , fa = Object.keys(ua); + function ga(t, e) { + return t - e + } + function pa(t, e) { + if (U(e)) + return null; + const i = t._adapter + , {parser: s, round: n, isoWeekday: o} = t._parseOpts; + let a = e; + return "function" == typeof s && (a = s(a)), + K(a) || (a = "string" == typeof s ? i.parse(a, s) : i.parse(a)), + null === a ? null : (n && (a = "week" !== n || !Nt(o) && !0 !== o ? i.startOf(a, n) : i.startOf(a, "isoWeek", o)), + +a) + } + function ma(t, e, i, s) { + const n = fa.length; + for (let o = fa.indexOf(t); o < n - 1; ++o) { + const t = ua[fa[o]] + , n = t.steps ? t.steps : Number.MAX_SAFE_INTEGER; + if (t.common && Math.ceil((i - e) / (n * t.size)) <= s) + return fa[o] + } + return fa[n - 1] + } + function ba(t, e, i) { + if (i) { + if (i.length) { + const {lo: s, hi: n} = vt(i, e); + t[i[s] >= e ? i[s] : i[n]] = !0 + } + } else + t[e] = !0 + } + function xa(t, e, i) { + const s = [] + , n = {} + , o = e.length; + let a, r; + for (a = 0; a < o; ++a) + r = e[a], + n[r] = a, + s.push({ + value: r, + major: !1 + }); + return 0 !== o && i ? function(t, e, i, s) { + const n = t._adapter + , o = +n.startOf(e[0].value, s) + , a = e[e.length - 1].value; + let r, l; + for (r = o; r <= a; r = +n.add(r, 1, s)) + l = i[r], + l >= 0 && (e[l].major = !0); + return e + }(t, s, n, i) : s + } + class _a extends Ns { + constructor(t) { + super(t), + this._cache = { + data: [], + labels: [], + all: [] + }, + this._unit = "day", + this._majorUnit = void 0, + this._offsets = {}, + this._normalized = !1, + this._parseOpts = void 0 + } + init(t, e) { + const i = t.time || (t.time = {}) + , s = this._adapter = new _n._date(t.adapters.date); + rt(i.displayFormats, s.formats()), + this._parseOpts = { + parser: i.parser, + round: i.round, + isoWeekday: i.isoWeekday + }, + super.init(t), + this._normalized = e.normalized + } + parse(t, e) { + return void 0 === t ? null : pa(this, t) + } + beforeLayout() { + super.beforeLayout(), + this._cache = { + data: [], + labels: [], + all: [] + } + } + determineDataLimits() { + const t = this.options + , e = this._adapter + , i = t.time.unit || "day"; + let {min: s, max: n, minDefined: o, maxDefined: a} = this.getUserBounds(); + function r(t) { + o || isNaN(t.min) || (s = Math.min(s, t.min)), + a || isNaN(t.max) || (n = Math.max(n, t.max)) + } + o && a || (r(this._getLabelBounds()), + "ticks" === t.bounds && "labels" === t.ticks.source || r(this.getMinMax(!1))), + s = K(s) && !isNaN(s) ? s : +e.startOf(Date.now(), i), + n = K(n) && !isNaN(n) ? n : +e.endOf(Date.now(), i) + 1, + this.min = Math.min(s, n - 1), + this.max = Math.max(s + 1, n) + } + _getLabelBounds() { + const t = this.getLabelTimestamps(); + let e = Number.POSITIVE_INFINITY + , i = Number.NEGATIVE_INFINITY; + return t.length && (e = t[0], + i = t[t.length - 1]), + { + min: e, + max: i + } + } + buildTicks() { + const t = this.options + , e = t.time + , i = t.ticks + , s = "labels" === i.source ? this.getLabelTimestamps() : this._generate(); + "ticks" === t.bounds && s.length && (this.min = this._userMin || s[0], + this.max = this._userMax || s[s.length - 1]); + const n = this.min + , o = kt(s, n, this.max); + return this._unit = e.unit || (i.autoSkip ? ma(e.minUnit, this.min, this.max, this._getLabelCapacity(n)) : function(t, e, i, s, n) { + for (let o = fa.length - 1; o >= fa.indexOf(i); o--) { + const i = fa[o]; + if (ua[i].common && t._adapter.diff(n, s, i) >= e - 1) + return i + } + return fa[i ? fa.indexOf(i) : 0] + }(this, o.length, e.minUnit, this.min, this.max)), + this._majorUnit = i.major.enabled && "year" !== this._unit ? function(t) { + for (let e = fa.indexOf(t) + 1, i = fa.length; e < i; ++e) + if (ua[fa[e]].common) + return fa[e] + }(this._unit) : void 0, + this.initOffsets(s), + t.reverse && o.reverse(), + xa(this, o, this._majorUnit) + } + afterAutoSkip() { + this.options.offsetAfterAutoskip && this.initOffsets(this.ticks.map((t=>+t.value))) + } + initOffsets(t) { + let e, i, s = 0, n = 0; + this.options.offset && t.length && (e = this.getDecimalForValue(t[0]), + s = 1 === t.length ? 1 - e : (this.getDecimalForValue(t[1]) - e) / 2, + i = this.getDecimalForValue(t[t.length - 1]), + n = 1 === t.length ? i : (i - this.getDecimalForValue(t[t.length - 2])) / 2); + const o = t.length < 3 ? .5 : .25; + s = Qt(s, 0, o), + n = Qt(n, 0, o), + this._offsets = { + start: s, + end: n, + factor: 1 / (s + 1 + n) + } + } + _generate() { + const t = this._adapter + , e = this.min + , i = this.max + , s = this.options + , n = s.time + , o = n.unit || ma(n.minUnit, e, i, this._getLabelCapacity(e)) + , a = Z(n.stepSize, 1) + , r = "week" === o && n.isoWeekday + , l = Nt(r) || !0 === r + , h = {}; + let c, d, u = e; + if (l && (u = +t.startOf(u, "isoWeek", r)), + u = +t.startOf(u, l ? "day" : o), + t.diff(i, e, o) > 1e5 * a) + throw new Error(e + " and " + i + " are too far apart with stepSize of " + a + " " + o); + const f = "data" === s.ticks.source && this.getDataTimestamps(); + for (c = u, + d = 0; c < i; c = +t.add(c, a, o), + d++) + ba(h, c, f); + return c !== i && "ticks" !== s.bounds && 1 !== d || ba(h, c, f), + Object.keys(h).sort(((t,e)=>t - e)).map((t=>+t)) + } + getLabelForValue(t) { + const e = this._adapter + , i = this.options.time; + return i.tooltipFormat ? e.format(t, i.tooltipFormat) : e.format(t, i.displayFormats.datetime) + } + _tickFormatFunction(t, e, i, s) { + const n = this.options + , o = n.time.displayFormats + , a = this._unit + , r = this._majorUnit + , l = a && o[a] + , h = r && o[r] + , c = i[e] + , d = r && h && c && c.major + , u = this._adapter.format(t, s || (d ? h : l)) + , f = n.ticks.callback; + return f ? tt(f, [u, e, i], this) : u + } + generateTickLabels(t) { + let e, i, s; + for (e = 0, + i = t.length; e < i; ++e) + s = t[e], + s.label = this._tickFormatFunction(s.value, e, t) + } + getDecimalForValue(t) { + return null === t ? NaN : (t - this.min) / (this.max - this.min) + } + getPixelForValue(t) { + const e = this._offsets + , i = this.getDecimalForValue(t); + return this.getPixelForDecimal((e.start + i) * e.factor) + } + getValueForPixel(t) { + const e = this._offsets + , i = this.getDecimalForPixel(t) / e.factor - e.end; + return this.min + i * (this.max - this.min) + } + _getLabelSize(t) { + const e = this.options.ticks + , i = this.ctx.measureText(t).width + , s = Yt(this.isHorizontal() ? e.maxRotation : e.minRotation) + , n = Math.cos(s) + , o = Math.sin(s) + , a = this._resolveTickFontOptions(0).size; + return { + w: i * n + a * o, + h: i * o + a * n + } + } + _getLabelCapacity(t) { + const e = this.options.time + , i = e.displayFormats + , s = i[e.unit] || i.millisecond + , n = this._tickFormatFunction(t, 0, xa(this, [t], this._majorUnit), s) + , o = this._getLabelSize(n) + , a = Math.floor(this.isHorizontal() ? this.width / o.w : this.height / o.h) - 1; + return a > 0 ? a : 1 + } + getDataTimestamps() { + let t, e, i = this._cache.data || []; + if (i.length) + return i; + const s = this.getMatchingVisibleMetas(); + if (this._normalized && s.length) + return this._cache.data = s[0].controller.getAllParsedValues(this); + for (t = 0, + e = s.length; t < e; ++t) + i = i.concat(s[t].controller.getAllParsedValues(this)); + return this._cache.data = this.normalize(i) + } + getLabelTimestamps() { + const t = this._cache.labels || []; + let e, i; + if (t.length) + return t; + const s = this.getLabels(); + for (e = 0, + i = s.length; e < i; ++e) + t.push(pa(this, s[e])); + return this._cache.labels = this._normalized ? t : this.normalize(t) + } + normalize(t) { + return Ct(t.sort(ga)) + } + } + function ya(t, e, i) { + let s, n, o, a, r = 0, l = t.length - 1; + i ? (e >= t[r].pos && e <= t[l].pos && ({lo: r, hi: l} = wt(t, "pos", e)), + ({pos: s, time: o} = t[r]), + ({pos: n, time: a} = t[l])) : (e >= t[r].time && e <= t[l].time && ({lo: r, hi: l} = wt(t, "time", e)), + ({time: s, pos: o} = t[r]), + ({time: n, pos: a} = t[l])); + const h = n - s; + return h ? o + (a - o) * (e - s) / h : o + } + _a.id = "time", + _a.defaults = { + bounds: "data", + adapters: {}, + time: { + parser: !1, + unit: !1, + round: !1, + isoWeekday: !1, + minUnit: "millisecond", + displayFormats: {} + }, + ticks: { + source: "auto", + major: { + enabled: !1 + } + } + }; + class va extends _a { + constructor(t) { + super(t), + this._table = [], + this._minPos = void 0, + this._tableRange = void 0 + } + initOffsets() { + const t = this._getTimestampsForTable() + , e = this._table = this.buildLookupTable(t); + this._minPos = ya(e, this.min), + this._tableRange = ya(e, this.max) - this._minPos, + super.initOffsets(t) + } + buildLookupTable(t) { + const {min: e, max: i} = this + , s = [] + , n = []; + let o, a, r, l, h; + for (o = 0, + a = t.length; o < a; ++o) + l = t[o], + l >= e && l <= i && s.push(l); + if (s.length < 2) + return [{ + time: e, + pos: 0 + }, { + time: i, + pos: 1 + }]; + for (o = 0, + a = s.length; o < a; ++o) + h = s[o + 1], + r = s[o - 1], + l = s[o], + Math.round((h + r) / 2) !== l && n.push({ + time: l, + pos: o / (a - 1) + }); + return n + } + _getTimestampsForTable() { + let t = this._cache.all || []; + if (t.length) + return t; + const e = this.getDataTimestamps() + , i = this.getLabelTimestamps(); + return t = e.length && i.length ? this.normalize(e.concat(i)) : e.length ? e : i, + t = this._cache.all = t, + t + } + getDecimalForValue(t) { + return (ya(this._table, t) - this._minPos) / this._tableRange + } + getValueForPixel(t) { + const e = this._offsets + , i = this.getDecimalForPixel(t) / e.factor - e.end; + return ya(this._table, i * this._tableRange + this._minPos, !0) + } + } + va.id = "timeseries", + va.defaults = _a.defaults; + var wa = Object.freeze({ + __proto__: null, + CategoryScale: Zo, + LinearScale: ta, + LogarithmicScale: ia, + RadialLinearScale: da, + TimeScale: _a, + TimeSeriesScale: va + }); + return gn.register(zn, wa, ro, Ko), + gn.helpers = { + ...Ci + }, + gn._adapters = _n, + gn.Animation = ps, + gn.Animations = bs, + gn.animator = a, + gn.controllers = Hs.controllers.items, + gn.DatasetController = Os, + gn.Element = As, + gn.elements = ro, + gn.Interaction = Ii, + gn.layouts = qi, + gn.platforms = us, + gn.Scale = Ns, + gn.Ticks = Ls, + Object.assign(gn, zn, wa, ro, Ko, us), + gn.Chart = gn, + "undefined" != typeof window && (window.Chart = gn), + gn +} +)); diff --git a/src/main/resources/res/d3.js b/src/main/resources/res/d3.js new file mode 100644 index 00000000..16648730 --- /dev/null +++ b/src/main/resources/res/d3.js @@ -0,0 +1,5 @@ +!function(){function n(n){return n&&(n.ownerDocument||n.document||n).documentElement}function t(n){return n&&(n.ownerDocument&&n.ownerDocument.defaultView||n.document&&n||n.defaultView)}function e(n,t){return t>n?-1:n>t?1:n>=t?0:NaN}function r(n){return null===n?NaN:+n}function i(n){return!isNaN(n)}function u(n){return{left:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)<0?r=u+1:i=u}return r},right:function(t,e,r,i){for(arguments.length<3&&(r=0),arguments.length<4&&(i=t.length);i>r;){var u=r+i>>>1;n(t[u],e)>0?i=u:r=u+1}return r}}}function o(n){return n.length}function a(n){for(var t=1;n*t%1;)t*=10;return t}function l(n,t){for(var e in t)Object.defineProperty(n.prototype,e,{value:t[e],enumerable:!1})}function c(){this._=Object.create(null)}function f(n){return(n+="")===bo||n[0]===_o?_o+n:n}function s(n){return(n+="")[0]===_o?n.slice(1):n}function h(n){return f(n)in this._}function p(n){return(n=f(n))in this._&&delete this._[n]}function g(){var n=[];for(var t in this._)n.push(s(t));return n}function v(){var n=0;for(var t in this._)++n;return n}function d(){for(var n in this._)return!1;return!0}function y(){this._=Object.create(null)}function m(n){return n}function M(n,t,e){return function(){var r=e.apply(t,arguments);return r===t?n:r}}function x(n,t){if(t in n)return t;t=t.charAt(0).toUpperCase()+t.slice(1);for(var e=0,r=wo.length;r>e;++e){var i=wo[e]+t;if(i in n)return i}}function b(){}function _(){}function w(n){function t(){for(var t,r=e,i=-1,u=r.length;++ie;e++)for(var i,u=n[e],o=0,a=u.length;a>o;o++)(i=u[o])&&t(i,o,e);return n}function Z(n){return ko(n,qo),n}function V(n){var t,e;return function(r,i,u){var o,a=n[u].update,l=a.length;for(u!=e&&(e=u,t=0),i>=t&&(t=i+1);!(o=a[t])&&++t0&&(n=n.slice(0,a));var c=To.get(n);return c&&(n=c,l=B),a?t?i:r:t?b:u}function $(n,t){return function(e){var r=ao.event;ao.event=e,t[0]=this.__data__;try{n.apply(this,t)}finally{ao.event=r}}}function B(n,t){var e=$(n,t);return function(n){var t=this,r=n.relatedTarget;r&&(r===t||8&r.compareDocumentPosition(t))||e.call(t,n)}}function W(e){var r=".dragsuppress-"+ ++Do,i="click"+r,u=ao.select(t(e)).on("touchmove"+r,S).on("dragstart"+r,S).on("selectstart"+r,S);if(null==Ro&&(Ro="onselectstart"in e?!1:x(e.style,"userSelect")),Ro){var o=n(e).style,a=o[Ro];o[Ro]="none"}return function(n){if(u.on(r,null),Ro&&(o[Ro]=a),n){var t=function(){u.on(i,null)};u.on(i,function(){S(),t()},!0),setTimeout(t,0)}}}function J(n,e){e.changedTouches&&(e=e.changedTouches[0]);var r=n.ownerSVGElement||n;if(r.createSVGPoint){var i=r.createSVGPoint();if(0>Po){var u=t(n);if(u.scrollX||u.scrollY){r=ao.select("body").append("svg").style({position:"absolute",top:0,left:0,margin:0,padding:0,border:"none"},"important");var o=r[0][0].getScreenCTM();Po=!(o.f||o.e),r.remove()}}return Po?(i.x=e.pageX,i.y=e.pageY):(i.x=e.clientX,i.y=e.clientY),i=i.matrixTransform(n.getScreenCTM().inverse()),[i.x,i.y]}var a=n.getBoundingClientRect();return[e.clientX-a.left-n.clientLeft,e.clientY-a.top-n.clientTop]}function G(){return ao.event.changedTouches[0].identifier}function K(n){return n>0?1:0>n?-1:0}function Q(n,t,e){return(t[0]-n[0])*(e[1]-n[1])-(t[1]-n[1])*(e[0]-n[0])}function nn(n){return n>1?0:-1>n?Fo:Math.acos(n)}function tn(n){return n>1?Io:-1>n?-Io:Math.asin(n)}function en(n){return((n=Math.exp(n))-1/n)/2}function rn(n){return((n=Math.exp(n))+1/n)/2}function un(n){return((n=Math.exp(2*n))-1)/(n+1)}function on(n){return(n=Math.sin(n/2))*n}function an(){}function ln(n,t,e){return this instanceof ln?(this.h=+n,this.s=+t,void(this.l=+e)):arguments.length<2?n instanceof ln?new ln(n.h,n.s,n.l):_n(""+n,wn,ln):new ln(n,t,e)}function cn(n,t,e){function r(n){return n>360?n-=360:0>n&&(n+=360),60>n?u+(o-u)*n/60:180>n?o:240>n?u+(o-u)*(240-n)/60:u}function i(n){return Math.round(255*r(n))}var u,o;return n=isNaN(n)?0:(n%=360)<0?n+360:n,t=isNaN(t)?0:0>t?0:t>1?1:t,e=0>e?0:e>1?1:e,o=.5>=e?e*(1+t):e+t-e*t,u=2*e-o,new mn(i(n+120),i(n),i(n-120))}function fn(n,t,e){return this instanceof fn?(this.h=+n,this.c=+t,void(this.l=+e)):arguments.length<2?n instanceof fn?new fn(n.h,n.c,n.l):n instanceof hn?gn(n.l,n.a,n.b):gn((n=Sn((n=ao.rgb(n)).r,n.g,n.b)).l,n.a,n.b):new fn(n,t,e)}function sn(n,t,e){return isNaN(n)&&(n=0),isNaN(t)&&(t=0),new hn(e,Math.cos(n*=Yo)*t,Math.sin(n)*t)}function hn(n,t,e){return this instanceof hn?(this.l=+n,this.a=+t,void(this.b=+e)):arguments.length<2?n instanceof hn?new hn(n.l,n.a,n.b):n instanceof fn?sn(n.h,n.c,n.l):Sn((n=mn(n)).r,n.g,n.b):new hn(n,t,e)}function pn(n,t,e){var r=(n+16)/116,i=r+t/500,u=r-e/200;return i=vn(i)*na,r=vn(r)*ta,u=vn(u)*ea,new mn(yn(3.2404542*i-1.5371385*r-.4985314*u),yn(-.969266*i+1.8760108*r+.041556*u),yn(.0556434*i-.2040259*r+1.0572252*u))}function gn(n,t,e){return n>0?new fn(Math.atan2(e,t)*Zo,Math.sqrt(t*t+e*e),n):new fn(NaN,NaN,n)}function vn(n){return n>.206893034?n*n*n:(n-4/29)/7.787037}function dn(n){return n>.008856?Math.pow(n,1/3):7.787037*n+4/29}function yn(n){return Math.round(255*(.00304>=n?12.92*n:1.055*Math.pow(n,1/2.4)-.055))}function mn(n,t,e){return this instanceof mn?(this.r=~~n,this.g=~~t,void(this.b=~~e)):arguments.length<2?n instanceof mn?new mn(n.r,n.g,n.b):_n(""+n,mn,cn):new mn(n,t,e)}function Mn(n){return new mn(n>>16,n>>8&255,255&n)}function xn(n){return Mn(n)+""}function bn(n){return 16>n?"0"+Math.max(0,n).toString(16):Math.min(255,n).toString(16)}function _n(n,t,e){var r,i,u,o=0,a=0,l=0;if(r=/([a-z]+)\((.*)\)/.exec(n=n.toLowerCase()))switch(i=r[2].split(","),r[1]){case"hsl":return e(parseFloat(i[0]),parseFloat(i[1])/100,parseFloat(i[2])/100);case"rgb":return t(Nn(i[0]),Nn(i[1]),Nn(i[2]))}return(u=ua.get(n))?t(u.r,u.g,u.b):(null==n||"#"!==n.charAt(0)||isNaN(u=parseInt(n.slice(1),16))||(4===n.length?(o=(3840&u)>>4,o=o>>4|o,a=240&u,a=a>>4|a,l=15&u,l=l<<4|l):7===n.length&&(o=(16711680&u)>>16,a=(65280&u)>>8,l=255&u)),t(o,a,l))}function wn(n,t,e){var r,i,u=Math.min(n/=255,t/=255,e/=255),o=Math.max(n,t,e),a=o-u,l=(o+u)/2;return a?(i=.5>l?a/(o+u):a/(2-o-u),r=n==o?(t-e)/a+(e>t?6:0):t==o?(e-n)/a+2:(n-t)/a+4,r*=60):(r=NaN,i=l>0&&1>l?0:r),new ln(r,i,l)}function Sn(n,t,e){n=kn(n),t=kn(t),e=kn(e);var r=dn((.4124564*n+.3575761*t+.1804375*e)/na),i=dn((.2126729*n+.7151522*t+.072175*e)/ta),u=dn((.0193339*n+.119192*t+.9503041*e)/ea);return hn(116*i-16,500*(r-i),200*(i-u))}function kn(n){return(n/=255)<=.04045?n/12.92:Math.pow((n+.055)/1.055,2.4)}function Nn(n){var t=parseFloat(n);return"%"===n.charAt(n.length-1)?Math.round(2.55*t):t}function En(n){return"function"==typeof n?n:function(){return n}}function An(n){return function(t,e,r){return 2===arguments.length&&"function"==typeof e&&(r=e,e=null),Cn(t,e,n,r)}}function Cn(n,t,e,r){function i(){var n,t=l.status;if(!t&&Ln(l)||t>=200&&300>t||304===t){try{n=e.call(u,l)}catch(r){return void o.error.call(u,r)}o.load.call(u,n)}else o.error.call(u,l)}var u={},o=ao.dispatch("beforesend","progress","load","error"),a={},l=new XMLHttpRequest,c=null;return!this.XDomainRequest||"withCredentials"in l||!/^(http(s)?:)?\/\//.test(n)||(l=new XDomainRequest),"onload"in l?l.onload=l.onerror=i:l.onreadystatechange=function(){l.readyState>3&&i()},l.onprogress=function(n){var t=ao.event;ao.event=n;try{o.progress.call(u,l)}finally{ao.event=t}},u.header=function(n,t){return n=(n+"").toLowerCase(),arguments.length<2?a[n]:(null==t?delete a[n]:a[n]=t+"",u)},u.mimeType=function(n){return arguments.length?(t=null==n?null:n+"",u):t},u.responseType=function(n){return arguments.length?(c=n,u):c},u.response=function(n){return e=n,u},["get","post"].forEach(function(n){u[n]=function(){return u.send.apply(u,[n].concat(co(arguments)))}}),u.send=function(e,r,i){if(2===arguments.length&&"function"==typeof r&&(i=r,r=null),l.open(e,n,!0),null==t||"accept"in a||(a.accept=t+",*/*"),l.setRequestHeader)for(var f in a)l.setRequestHeader(f,a[f]);return null!=t&&l.overrideMimeType&&l.overrideMimeType(t),null!=c&&(l.responseType=c),null!=i&&u.on("error",i).on("load",function(n){i(null,n)}),o.beforesend.call(u,l),l.send(null==r?null:r),u},u.abort=function(){return l.abort(),u},ao.rebind(u,o,"on"),null==r?u:u.get(zn(r))}function zn(n){return 1===n.length?function(t,e){n(null==t?e:null)}:n}function Ln(n){var t=n.responseType;return t&&"text"!==t?n.response:n.responseText}function qn(n,t,e){var r=arguments.length;2>r&&(t=0),3>r&&(e=Date.now());var i=e+t,u={c:n,t:i,n:null};return aa?aa.n=u:oa=u,aa=u,la||(ca=clearTimeout(ca),la=1,fa(Tn)),u}function Tn(){var n=Rn(),t=Dn()-n;t>24?(isFinite(t)&&(clearTimeout(ca),ca=setTimeout(Tn,t)),la=0):(la=1,fa(Tn))}function Rn(){for(var n=Date.now(),t=oa;t;)n>=t.t&&t.c(n-t.t)&&(t.c=null),t=t.n;return n}function Dn(){for(var n,t=oa,e=1/0;t;)t.c?(t.t8?function(n){return n/e}:function(n){return n*e},symbol:n}}function jn(n){var t=n.decimal,e=n.thousands,r=n.grouping,i=n.currency,u=r&&e?function(n,t){for(var i=n.length,u=[],o=0,a=r[0],l=0;i>0&&a>0&&(l+a+1>t&&(a=Math.max(1,t-l)),u.push(n.substring(i-=a,i+a)),!((l+=a+1)>t));)a=r[o=(o+1)%r.length];return u.reverse().join(e)}:m;return function(n){var e=ha.exec(n),r=e[1]||" ",o=e[2]||">",a=e[3]||"-",l=e[4]||"",c=e[5],f=+e[6],s=e[7],h=e[8],p=e[9],g=1,v="",d="",y=!1,m=!0;switch(h&&(h=+h.substring(1)),(c||"0"===r&&"="===o)&&(c=r="0",o="="),p){case"n":s=!0,p="g";break;case"%":g=100,d="%",p="f";break;case"p":g=100,d="%",p="r";break;case"b":case"o":case"x":case"X":"#"===l&&(v="0"+p.toLowerCase());case"c":m=!1;case"d":y=!0,h=0;break;case"s":g=-1,p="r"}"$"===l&&(v=i[0],d=i[1]),"r"!=p||h||(p="g"),null!=h&&("g"==p?h=Math.max(1,Math.min(21,h)):"e"!=p&&"f"!=p||(h=Math.max(0,Math.min(20,h)))),p=pa.get(p)||Fn;var M=c&&s;return function(n){var e=d;if(y&&n%1)return"";var i=0>n||0===n&&0>1/n?(n=-n,"-"):"-"===a?"":a;if(0>g){var l=ao.formatPrefix(n,h);n=l.scale(n),e=l.symbol+d}else n*=g;n=p(n,h);var x,b,_=n.lastIndexOf(".");if(0>_){var w=m?n.lastIndexOf("e"):-1;0>w?(x=n,b=""):(x=n.substring(0,w),b=n.substring(w))}else x=n.substring(0,_),b=t+n.substring(_+1);!c&&s&&(x=u(x,1/0));var S=v.length+x.length+b.length+(M?0:i.length),k=f>S?new Array(S=f-S+1).join(r):"";return M&&(x=u(k+x,k.length?f-b.length:1/0)),i+=v,n=x+b,("<"===o?i+n+k:">"===o?k+i+n:"^"===o?k.substring(0,S>>=1)+i+n+k.substring(S):i+(M?n:k+n))+e}}}function Fn(n){return n+""}function Hn(){this._=new Date(arguments.length>1?Date.UTC.apply(this,arguments):arguments[0])}function On(n,t,e){function r(t){var e=n(t),r=u(e,1);return r-t>t-e?e:r}function i(e){return t(e=n(new va(e-1)),1),e}function u(n,e){return t(n=new va(+n),e),n}function o(n,r,u){var o=i(n),a=[];if(u>1)for(;r>o;)e(o)%u||a.push(new Date(+o)),t(o,1);else for(;r>o;)a.push(new Date(+o)),t(o,1);return a}function a(n,t,e){try{va=Hn;var r=new Hn;return r._=n,o(r,t,e)}finally{va=Date}}n.floor=n,n.round=r,n.ceil=i,n.offset=u,n.range=o;var l=n.utc=In(n);return l.floor=l,l.round=In(r),l.ceil=In(i),l.offset=In(u),l.range=a,n}function In(n){return function(t,e){try{va=Hn;var r=new Hn;return r._=t,n(r,e)._}finally{va=Date}}}function Yn(n){function t(n){function t(t){for(var e,i,u,o=[],a=-1,l=0;++aa;){if(r>=c)return-1;if(i=t.charCodeAt(a++),37===i){if(o=t.charAt(a++),u=C[o in ya?t.charAt(a++):o],!u||(r=u(n,e,r))<0)return-1}else if(i!=e.charCodeAt(r++))return-1}return r}function r(n,t,e){_.lastIndex=0;var r=_.exec(t.slice(e));return r?(n.w=w.get(r[0].toLowerCase()),e+r[0].length):-1}function i(n,t,e){x.lastIndex=0;var r=x.exec(t.slice(e));return r?(n.w=b.get(r[0].toLowerCase()),e+r[0].length):-1}function u(n,t,e){N.lastIndex=0;var r=N.exec(t.slice(e));return r?(n.m=E.get(r[0].toLowerCase()),e+r[0].length):-1}function o(n,t,e){S.lastIndex=0;var r=S.exec(t.slice(e));return r?(n.m=k.get(r[0].toLowerCase()),e+r[0].length):-1}function a(n,t,r){return e(n,A.c.toString(),t,r)}function l(n,t,r){return e(n,A.x.toString(),t,r)}function c(n,t,r){return e(n,A.X.toString(),t,r)}function f(n,t,e){var r=M.get(t.slice(e,e+=2).toLowerCase());return null==r?-1:(n.p=r,e)}var s=n.dateTime,h=n.date,p=n.time,g=n.periods,v=n.days,d=n.shortDays,y=n.months,m=n.shortMonths;t.utc=function(n){function e(n){try{va=Hn;var t=new va;return t._=n,r(t)}finally{va=Date}}var r=t(n);return e.parse=function(n){try{va=Hn;var t=r.parse(n);return t&&t._}finally{va=Date}},e.toString=r.toString,e},t.multi=t.utc.multi=ct;var M=ao.map(),x=Vn(v),b=Xn(v),_=Vn(d),w=Xn(d),S=Vn(y),k=Xn(y),N=Vn(m),E=Xn(m);g.forEach(function(n,t){M.set(n.toLowerCase(),t)});var A={a:function(n){return d[n.getDay()]},A:function(n){return v[n.getDay()]},b:function(n){return m[n.getMonth()]},B:function(n){return y[n.getMonth()]},c:t(s),d:function(n,t){return Zn(n.getDate(),t,2)},e:function(n,t){return Zn(n.getDate(),t,2)},H:function(n,t){return Zn(n.getHours(),t,2)},I:function(n,t){return Zn(n.getHours()%12||12,t,2)},j:function(n,t){return Zn(1+ga.dayOfYear(n),t,3)},L:function(n,t){return Zn(n.getMilliseconds(),t,3)},m:function(n,t){return Zn(n.getMonth()+1,t,2)},M:function(n,t){return Zn(n.getMinutes(),t,2)},p:function(n){return g[+(n.getHours()>=12)]},S:function(n,t){return Zn(n.getSeconds(),t,2)},U:function(n,t){return Zn(ga.sundayOfYear(n),t,2)},w:function(n){return n.getDay()},W:function(n,t){return Zn(ga.mondayOfYear(n),t,2)},x:t(h),X:t(p),y:function(n,t){return Zn(n.getFullYear()%100,t,2)},Y:function(n,t){return Zn(n.getFullYear()%1e4,t,4)},Z:at,"%":function(){return"%"}},C={a:r,A:i,b:u,B:o,c:a,d:tt,e:tt,H:rt,I:rt,j:et,L:ot,m:nt,M:it,p:f,S:ut,U:Bn,w:$n,W:Wn,x:l,X:c,y:Gn,Y:Jn,Z:Kn,"%":lt};return t}function Zn(n,t,e){var r=0>n?"-":"",i=(r?-n:n)+"",u=i.length;return r+(e>u?new Array(e-u+1).join(t)+i:i)}function Vn(n){return new RegExp("^(?:"+n.map(ao.requote).join("|")+")","i")}function Xn(n){for(var t=new c,e=-1,r=n.length;++e68?1900:2e3)}function nt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.m=r[0]-1,e+r[0].length):-1}function tt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.d=+r[0],e+r[0].length):-1}function et(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.j=+r[0],e+r[0].length):-1}function rt(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.H=+r[0],e+r[0].length):-1}function it(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.M=+r[0],e+r[0].length):-1}function ut(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+2));return r?(n.S=+r[0],e+r[0].length):-1}function ot(n,t,e){ma.lastIndex=0;var r=ma.exec(t.slice(e,e+3));return r?(n.L=+r[0],e+r[0].length):-1}function at(n){var t=n.getTimezoneOffset(),e=t>0?"-":"+",r=xo(t)/60|0,i=xo(t)%60;return e+Zn(r,"0",2)+Zn(i,"0",2)}function lt(n,t,e){Ma.lastIndex=0;var r=Ma.exec(t.slice(e,e+1));return r?e+r[0].length:-1}function ct(n){for(var t=n.length,e=-1;++e=0?1:-1,a=o*e,l=Math.cos(t),c=Math.sin(t),f=u*c,s=i*l+f*Math.cos(a),h=f*o*Math.sin(a);ka.add(Math.atan2(h,s)),r=n,i=l,u=c}var t,e,r,i,u;Na.point=function(o,a){Na.point=n,r=(t=o)*Yo,i=Math.cos(a=(e=a)*Yo/2+Fo/4),u=Math.sin(a)},Na.lineEnd=function(){n(t,e)}}function dt(n){var t=n[0],e=n[1],r=Math.cos(e);return[r*Math.cos(t),r*Math.sin(t),Math.sin(e)]}function yt(n,t){return n[0]*t[0]+n[1]*t[1]+n[2]*t[2]}function mt(n,t){return[n[1]*t[2]-n[2]*t[1],n[2]*t[0]-n[0]*t[2],n[0]*t[1]-n[1]*t[0]]}function Mt(n,t){n[0]+=t[0],n[1]+=t[1],n[2]+=t[2]}function xt(n,t){return[n[0]*t,n[1]*t,n[2]*t]}function bt(n){var t=Math.sqrt(n[0]*n[0]+n[1]*n[1]+n[2]*n[2]);n[0]/=t,n[1]/=t,n[2]/=t}function _t(n){return[Math.atan2(n[1],n[0]),tn(n[2])]}function wt(n,t){return xo(n[0]-t[0])a;++a)i.point((e=n[a])[0],e[1]);return void i.lineEnd()}var l=new Tt(e,n,null,!0),c=new Tt(e,null,l,!1);l.o=c,u.push(l),o.push(c),l=new Tt(r,n,null,!1),c=new Tt(r,null,l,!0),l.o=c,u.push(l),o.push(c)}}),o.sort(t),qt(u),qt(o),u.length){for(var a=0,l=e,c=o.length;c>a;++a)o[a].e=l=!l;for(var f,s,h=u[0];;){for(var p=h,g=!0;p.v;)if((p=p.n)===h)return;f=p.z,i.lineStart();do{if(p.v=p.o.v=!0,p.e){if(g)for(var a=0,c=f.length;c>a;++a)i.point((s=f[a])[0],s[1]);else r(p.x,p.n.x,1,i);p=p.n}else{if(g){f=p.p.z;for(var a=f.length-1;a>=0;--a)i.point((s=f[a])[0],s[1])}else r(p.x,p.p.x,-1,i);p=p.p}p=p.o,f=p.z,g=!g}while(!p.v);i.lineEnd()}}}function qt(n){if(t=n.length){for(var t,e,r=0,i=n[0];++r0){for(b||(u.polygonStart(),b=!0),u.lineStart();++o1&&2&t&&e.push(e.pop().concat(e.shift())),p.push(e.filter(Dt))}var p,g,v,d=t(u),y=i.invert(r[0],r[1]),m={point:o,lineStart:l,lineEnd:c,polygonStart:function(){m.point=f,m.lineStart=s,m.lineEnd=h,p=[],g=[]},polygonEnd:function(){m.point=o,m.lineStart=l,m.lineEnd=c,p=ao.merge(p);var n=Ot(y,g);p.length?(b||(u.polygonStart(),b=!0),Lt(p,Ut,n,e,u)):n&&(b||(u.polygonStart(),b=!0),u.lineStart(),e(null,null,1,u),u.lineEnd()),b&&(u.polygonEnd(),b=!1),p=g=null},sphere:function(){u.polygonStart(),u.lineStart(),e(null,null,1,u),u.lineEnd(),u.polygonEnd()}},M=Pt(),x=t(M),b=!1;return m}}function Dt(n){return n.length>1}function Pt(){var n,t=[];return{lineStart:function(){t.push(n=[])},point:function(t,e){n.push([t,e])},lineEnd:b,buffer:function(){var e=t;return t=[],n=null,e},rejoin:function(){t.length>1&&t.push(t.pop().concat(t.shift()))}}}function Ut(n,t){return((n=n.x)[0]<0?n[1]-Io-Uo:Io-n[1])-((t=t.x)[0]<0?t[1]-Io-Uo:Io-t[1])}function jt(n){var t,e=NaN,r=NaN,i=NaN;return{lineStart:function(){n.lineStart(),t=1},point:function(u,o){var a=u>0?Fo:-Fo,l=xo(u-e);xo(l-Fo)0?Io:-Io),n.point(i,r),n.lineEnd(),n.lineStart(),n.point(a,r),n.point(u,r),t=0):i!==a&&l>=Fo&&(xo(e-i)Uo?Math.atan((Math.sin(t)*(u=Math.cos(r))*Math.sin(e)-Math.sin(r)*(i=Math.cos(t))*Math.sin(n))/(i*u*o)):(t+r)/2}function Ht(n,t,e,r){var i;if(null==n)i=e*Io,r.point(-Fo,i),r.point(0,i),r.point(Fo,i),r.point(Fo,0),r.point(Fo,-i),r.point(0,-i),r.point(-Fo,-i),r.point(-Fo,0),r.point(-Fo,i);else if(xo(n[0]-t[0])>Uo){var u=n[0]a;++a){var c=t[a],f=c.length;if(f)for(var s=c[0],h=s[0],p=s[1]/2+Fo/4,g=Math.sin(p),v=Math.cos(p),d=1;;){d===f&&(d=0),n=c[d];var y=n[0],m=n[1]/2+Fo/4,M=Math.sin(m),x=Math.cos(m),b=y-h,_=b>=0?1:-1,w=_*b,S=w>Fo,k=g*M;if(ka.add(Math.atan2(k*_*Math.sin(w),v*x+k*Math.cos(w))),u+=S?b+_*Ho:b,S^h>=e^y>=e){var N=mt(dt(s),dt(n));bt(N);var E=mt(i,N);bt(E);var A=(S^b>=0?-1:1)*tn(E[2]);(r>A||r===A&&(N[0]||N[1]))&&(o+=S^b>=0?1:-1)}if(!d++)break;h=y,g=M,v=x,s=n}}return(-Uo>u||Uo>u&&-Uo>ka)^1&o}function It(n){function t(n,t){return Math.cos(n)*Math.cos(t)>u}function e(n){var e,u,l,c,f;return{lineStart:function(){c=l=!1,f=1},point:function(s,h){var p,g=[s,h],v=t(s,h),d=o?v?0:i(s,h):v?i(s+(0>s?Fo:-Fo),h):0;if(!e&&(c=l=v)&&n.lineStart(),v!==l&&(p=r(e,g),(wt(e,p)||wt(g,p))&&(g[0]+=Uo,g[1]+=Uo,v=t(g[0],g[1]))),v!==l)f=0,v?(n.lineStart(),p=r(g,e),n.point(p[0],p[1])):(p=r(e,g),n.point(p[0],p[1]),n.lineEnd()),e=p;else if(a&&e&&o^v){var y;d&u||!(y=r(g,e,!0))||(f=0,o?(n.lineStart(),n.point(y[0][0],y[0][1]),n.point(y[1][0],y[1][1]),n.lineEnd()):(n.point(y[1][0],y[1][1]),n.lineEnd(),n.lineStart(),n.point(y[0][0],y[0][1])))}!v||e&&wt(e,g)||n.point(g[0],g[1]),e=g,l=v,u=d},lineEnd:function(){l&&n.lineEnd(),e=null},clean:function(){return f|(c&&l)<<1}}}function r(n,t,e){var r=dt(n),i=dt(t),o=[1,0,0],a=mt(r,i),l=yt(a,a),c=a[0],f=l-c*c;if(!f)return!e&&n;var s=u*l/f,h=-u*c/f,p=mt(o,a),g=xt(o,s),v=xt(a,h);Mt(g,v);var d=p,y=yt(g,d),m=yt(d,d),M=y*y-m*(yt(g,g)-1);if(!(0>M)){var x=Math.sqrt(M),b=xt(d,(-y-x)/m);if(Mt(b,g),b=_t(b),!e)return b;var _,w=n[0],S=t[0],k=n[1],N=t[1];w>S&&(_=w,w=S,S=_);var E=S-w,A=xo(E-Fo)E;if(!A&&k>N&&(_=k,k=N,N=_),C?A?k+N>0^b[1]<(xo(b[0]-w)Fo^(w<=b[0]&&b[0]<=S)){var z=xt(d,(-y+x)/m);return Mt(z,g),[b,_t(z)]}}}function i(t,e){var r=o?n:Fo-n,i=0;return-r>t?i|=1:t>r&&(i|=2),-r>e?i|=4:e>r&&(i|=8),i}var u=Math.cos(n),o=u>0,a=xo(u)>Uo,l=ve(n,6*Yo);return Rt(t,e,l,o?[0,-n]:[-Fo,n-Fo])}function Yt(n,t,e,r){return function(i){var u,o=i.a,a=i.b,l=o.x,c=o.y,f=a.x,s=a.y,h=0,p=1,g=f-l,v=s-c;if(u=n-l,g||!(u>0)){if(u/=g,0>g){if(h>u)return;p>u&&(p=u)}else if(g>0){if(u>p)return;u>h&&(h=u)}if(u=e-l,g||!(0>u)){if(u/=g,0>g){if(u>p)return;u>h&&(h=u)}else if(g>0){if(h>u)return;p>u&&(p=u)}if(u=t-c,v||!(u>0)){if(u/=v,0>v){if(h>u)return;p>u&&(p=u)}else if(v>0){if(u>p)return;u>h&&(h=u)}if(u=r-c,v||!(0>u)){if(u/=v,0>v){if(u>p)return;u>h&&(h=u)}else if(v>0){if(h>u)return;p>u&&(p=u)}return h>0&&(i.a={x:l+h*g,y:c+h*v}),1>p&&(i.b={x:l+p*g,y:c+p*v}),i}}}}}}function Zt(n,t,e,r){function i(r,i){return xo(r[0]-n)0?0:3:xo(r[0]-e)0?2:1:xo(r[1]-t)0?1:0:i>0?3:2}function u(n,t){return o(n.x,t.x)}function o(n,t){var e=i(n,1),r=i(t,1);return e!==r?e-r:0===e?t[1]-n[1]:1===e?n[0]-t[0]:2===e?n[1]-t[1]:t[0]-n[0]}return function(a){function l(n){for(var t=0,e=d.length,r=n[1],i=0;e>i;++i)for(var u,o=1,a=d[i],l=a.length,c=a[0];l>o;++o)u=a[o],c[1]<=r?u[1]>r&&Q(c,u,n)>0&&++t:u[1]<=r&&Q(c,u,n)<0&&--t,c=u;return 0!==t}function c(u,a,l,c){var f=0,s=0;if(null==u||(f=i(u,l))!==(s=i(a,l))||o(u,a)<0^l>0){do c.point(0===f||3===f?n:e,f>1?r:t);while((f=(f+l+4)%4)!==s)}else c.point(a[0],a[1])}function f(i,u){return i>=n&&e>=i&&u>=t&&r>=u}function s(n,t){f(n,t)&&a.point(n,t)}function h(){C.point=g,d&&d.push(y=[]),S=!0,w=!1,b=_=NaN}function p(){v&&(g(m,M),x&&w&&E.rejoin(),v.push(E.buffer())),C.point=s,w&&a.lineEnd()}function g(n,t){n=Math.max(-Ha,Math.min(Ha,n)),t=Math.max(-Ha,Math.min(Ha,t));var e=f(n,t);if(d&&y.push([n,t]),S)m=n,M=t,x=e,S=!1,e&&(a.lineStart(),a.point(n,t));else if(e&&w)a.point(n,t);else{var r={a:{x:b,y:_},b:{x:n,y:t}};A(r)?(w||(a.lineStart(),a.point(r.a.x,r.a.y)),a.point(r.b.x,r.b.y),e||a.lineEnd(),k=!1):e&&(a.lineStart(),a.point(n,t),k=!1)}b=n,_=t,w=e}var v,d,y,m,M,x,b,_,w,S,k,N=a,E=Pt(),A=Yt(n,t,e,r),C={point:s,lineStart:h,lineEnd:p,polygonStart:function(){a=E,v=[],d=[],k=!0},polygonEnd:function(){a=N,v=ao.merge(v);var t=l([n,r]),e=k&&t,i=v.length;(e||i)&&(a.polygonStart(),e&&(a.lineStart(),c(null,null,1,a),a.lineEnd()),i&&Lt(v,u,t,c,a),a.polygonEnd()),v=d=y=null}};return C}}function Vt(n){var t=0,e=Fo/3,r=ae(n),i=r(t,e);return i.parallels=function(n){return arguments.length?r(t=n[0]*Fo/180,e=n[1]*Fo/180):[t/Fo*180,e/Fo*180]},i}function Xt(n,t){function e(n,t){var e=Math.sqrt(u-2*i*Math.sin(t))/i;return[e*Math.sin(n*=i),o-e*Math.cos(n)]}var r=Math.sin(n),i=(r+Math.sin(t))/2,u=1+r*(2*i-r),o=Math.sqrt(u)/i;return e.invert=function(n,t){var e=o-t;return[Math.atan2(n,e)/i,tn((u-(n*n+e*e)*i*i)/(2*i))]},e}function $t(){function n(n,t){Ia+=i*n-r*t,r=n,i=t}var t,e,r,i;$a.point=function(u,o){$a.point=n,t=r=u,e=i=o},$a.lineEnd=function(){n(t,e)}}function Bt(n,t){Ya>n&&(Ya=n),n>Va&&(Va=n),Za>t&&(Za=t),t>Xa&&(Xa=t)}function Wt(){function n(n,t){o.push("M",n,",",t,u)}function t(n,t){o.push("M",n,",",t),a.point=e}function e(n,t){o.push("L",n,",",t)}function r(){a.point=n}function i(){o.push("Z")}var u=Jt(4.5),o=[],a={point:n,lineStart:function(){a.point=t},lineEnd:r,polygonStart:function(){a.lineEnd=i},polygonEnd:function(){a.lineEnd=r,a.point=n},pointRadius:function(n){return u=Jt(n),a},result:function(){if(o.length){var n=o.join("");return o=[],n}}};return a}function Jt(n){return"m0,"+n+"a"+n+","+n+" 0 1,1 0,"+-2*n+"a"+n+","+n+" 0 1,1 0,"+2*n+"z"}function Gt(n,t){Ca+=n,za+=t,++La}function Kt(){function n(n,r){var i=n-t,u=r-e,o=Math.sqrt(i*i+u*u);qa+=o*(t+n)/2,Ta+=o*(e+r)/2,Ra+=o,Gt(t=n,e=r)}var t,e;Wa.point=function(r,i){Wa.point=n,Gt(t=r,e=i)}}function Qt(){Wa.point=Gt}function ne(){function n(n,t){var e=n-r,u=t-i,o=Math.sqrt(e*e+u*u);qa+=o*(r+n)/2,Ta+=o*(i+t)/2,Ra+=o,o=i*n-r*t,Da+=o*(r+n),Pa+=o*(i+t),Ua+=3*o,Gt(r=n,i=t)}var t,e,r,i;Wa.point=function(u,o){Wa.point=n,Gt(t=r=u,e=i=o)},Wa.lineEnd=function(){n(t,e)}}function te(n){function t(t,e){n.moveTo(t+o,e),n.arc(t,e,o,0,Ho)}function e(t,e){n.moveTo(t,e),a.point=r}function r(t,e){n.lineTo(t,e)}function i(){a.point=t}function u(){n.closePath()}var o=4.5,a={point:t,lineStart:function(){a.point=e},lineEnd:i,polygonStart:function(){a.lineEnd=u},polygonEnd:function(){a.lineEnd=i,a.point=t},pointRadius:function(n){return o=n,a},result:b};return a}function ee(n){function t(n){return(a?r:e)(n)}function e(t){return ue(t,function(e,r){e=n(e,r),t.point(e[0],e[1])})}function r(t){function e(e,r){e=n(e,r),t.point(e[0],e[1])}function r(){M=NaN,S.point=u,t.lineStart()}function u(e,r){var u=dt([e,r]),o=n(e,r);i(M,x,m,b,_,w,M=o[0],x=o[1],m=e,b=u[0],_=u[1],w=u[2],a,t),t.point(M,x)}function o(){S.point=e,t.lineEnd()}function l(){ +r(),S.point=c,S.lineEnd=f}function c(n,t){u(s=n,h=t),p=M,g=x,v=b,d=_,y=w,S.point=u}function f(){i(M,x,m,b,_,w,p,g,s,v,d,y,a,t),S.lineEnd=o,o()}var s,h,p,g,v,d,y,m,M,x,b,_,w,S={point:e,lineStart:r,lineEnd:o,polygonStart:function(){t.polygonStart(),S.lineStart=l},polygonEnd:function(){t.polygonEnd(),S.lineStart=r}};return S}function i(t,e,r,a,l,c,f,s,h,p,g,v,d,y){var m=f-t,M=s-e,x=m*m+M*M;if(x>4*u&&d--){var b=a+p,_=l+g,w=c+v,S=Math.sqrt(b*b+_*_+w*w),k=Math.asin(w/=S),N=xo(xo(w)-1)u||xo((m*z+M*L)/x-.5)>.3||o>a*p+l*g+c*v)&&(i(t,e,r,a,l,c,A,C,N,b/=S,_/=S,w,d,y),y.point(A,C),i(A,C,N,b,_,w,f,s,h,p,g,v,d,y))}}var u=.5,o=Math.cos(30*Yo),a=16;return t.precision=function(n){return arguments.length?(a=(u=n*n)>0&&16,t):Math.sqrt(u)},t}function re(n){var t=ee(function(t,e){return n([t*Zo,e*Zo])});return function(n){return le(t(n))}}function ie(n){this.stream=n}function ue(n,t){return{point:t,sphere:function(){n.sphere()},lineStart:function(){n.lineStart()},lineEnd:function(){n.lineEnd()},polygonStart:function(){n.polygonStart()},polygonEnd:function(){n.polygonEnd()}}}function oe(n){return ae(function(){return n})()}function ae(n){function t(n){return n=a(n[0]*Yo,n[1]*Yo),[n[0]*h+l,c-n[1]*h]}function e(n){return n=a.invert((n[0]-l)/h,(c-n[1])/h),n&&[n[0]*Zo,n[1]*Zo]}function r(){a=Ct(o=se(y,M,x),u);var n=u(v,d);return l=p-n[0]*h,c=g+n[1]*h,i()}function i(){return f&&(f.valid=!1,f=null),t}var u,o,a,l,c,f,s=ee(function(n,t){return n=u(n,t),[n[0]*h+l,c-n[1]*h]}),h=150,p=480,g=250,v=0,d=0,y=0,M=0,x=0,b=Fa,_=m,w=null,S=null;return t.stream=function(n){return f&&(f.valid=!1),f=le(b(o,s(_(n)))),f.valid=!0,f},t.clipAngle=function(n){return arguments.length?(b=null==n?(w=n,Fa):It((w=+n)*Yo),i()):w},t.clipExtent=function(n){return arguments.length?(S=n,_=n?Zt(n[0][0],n[0][1],n[1][0],n[1][1]):m,i()):S},t.scale=function(n){return arguments.length?(h=+n,r()):h},t.translate=function(n){return arguments.length?(p=+n[0],g=+n[1],r()):[p,g]},t.center=function(n){return arguments.length?(v=n[0]%360*Yo,d=n[1]%360*Yo,r()):[v*Zo,d*Zo]},t.rotate=function(n){return arguments.length?(y=n[0]%360*Yo,M=n[1]%360*Yo,x=n.length>2?n[2]%360*Yo:0,r()):[y*Zo,M*Zo,x*Zo]},ao.rebind(t,s,"precision"),function(){return u=n.apply(this,arguments),t.invert=u.invert&&e,r()}}function le(n){return ue(n,function(t,e){n.point(t*Yo,e*Yo)})}function ce(n,t){return[n,t]}function fe(n,t){return[n>Fo?n-Ho:-Fo>n?n+Ho:n,t]}function se(n,t,e){return n?t||e?Ct(pe(n),ge(t,e)):pe(n):t||e?ge(t,e):fe}function he(n){return function(t,e){return t+=n,[t>Fo?t-Ho:-Fo>t?t+Ho:t,e]}}function pe(n){var t=he(n);return t.invert=he(-n),t}function ge(n,t){function e(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*r+a*i;return[Math.atan2(l*u-f*o,a*r-c*i),tn(f*u+l*o)]}var r=Math.cos(n),i=Math.sin(n),u=Math.cos(t),o=Math.sin(t);return e.invert=function(n,t){var e=Math.cos(t),a=Math.cos(n)*e,l=Math.sin(n)*e,c=Math.sin(t),f=c*u-l*o;return[Math.atan2(l*u+c*o,a*r+f*i),tn(f*r-a*i)]},e}function ve(n,t){var e=Math.cos(n),r=Math.sin(n);return function(i,u,o,a){var l=o*t;null!=i?(i=de(e,i),u=de(e,u),(o>0?u>i:i>u)&&(i+=o*Ho)):(i=n+o*Ho,u=n-.5*l);for(var c,f=i;o>0?f>u:u>f;f-=l)a.point((c=_t([e,-r*Math.cos(f),-r*Math.sin(f)]))[0],c[1])}}function de(n,t){var e=dt(t);e[0]-=n,bt(e);var r=nn(-e[1]);return((-e[2]<0?-r:r)+2*Math.PI-Uo)%(2*Math.PI)}function ye(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[n,t]})}}function me(n,t,e){var r=ao.range(n,t-Uo,e).concat(t);return function(n){return r.map(function(t){return[t,n]})}}function Me(n){return n.source}function xe(n){return n.target}function be(n,t,e,r){var i=Math.cos(t),u=Math.sin(t),o=Math.cos(r),a=Math.sin(r),l=i*Math.cos(n),c=i*Math.sin(n),f=o*Math.cos(e),s=o*Math.sin(e),h=2*Math.asin(Math.sqrt(on(r-t)+i*o*on(e-n))),p=1/Math.sin(h),g=h?function(n){var t=Math.sin(n*=h)*p,e=Math.sin(h-n)*p,r=e*l+t*f,i=e*c+t*s,o=e*u+t*a;return[Math.atan2(i,r)*Zo,Math.atan2(o,Math.sqrt(r*r+i*i))*Zo]}:function(){return[n*Zo,t*Zo]};return g.distance=h,g}function _e(){function n(n,i){var u=Math.sin(i*=Yo),o=Math.cos(i),a=xo((n*=Yo)-t),l=Math.cos(a);Ja+=Math.atan2(Math.sqrt((a=o*Math.sin(a))*a+(a=r*u-e*o*l)*a),e*u+r*o*l),t=n,e=u,r=o}var t,e,r;Ga.point=function(i,u){t=i*Yo,e=Math.sin(u*=Yo),r=Math.cos(u),Ga.point=n},Ga.lineEnd=function(){Ga.point=Ga.lineEnd=b}}function we(n,t){function e(t,e){var r=Math.cos(t),i=Math.cos(e),u=n(r*i);return[u*i*Math.sin(t),u*Math.sin(e)]}return e.invert=function(n,e){var r=Math.sqrt(n*n+e*e),i=t(r),u=Math.sin(i),o=Math.cos(i);return[Math.atan2(n*u,r*o),Math.asin(r&&e*u/r)]},e}function Se(n,t){function e(n,t){o>0?-Io+Uo>t&&(t=-Io+Uo):t>Io-Uo&&(t=Io-Uo);var e=o/Math.pow(i(t),u);return[e*Math.sin(u*n),o-e*Math.cos(u*n)]}var r=Math.cos(n),i=function(n){return Math.tan(Fo/4+n/2)},u=n===t?Math.sin(n):Math.log(r/Math.cos(t))/Math.log(i(t)/i(n)),o=r*Math.pow(i(n),u)/u;return u?(e.invert=function(n,t){var e=o-t,r=K(u)*Math.sqrt(n*n+e*e);return[Math.atan2(n,e)/u,2*Math.atan(Math.pow(o/r,1/u))-Io]},e):Ne}function ke(n,t){function e(n,t){var e=u-t;return[e*Math.sin(i*n),u-e*Math.cos(i*n)]}var r=Math.cos(n),i=n===t?Math.sin(n):(r-Math.cos(t))/(t-n),u=r/i+n;return xo(i)i;i++){for(;r>1&&Q(n[e[r-2]],n[e[r-1]],n[i])<=0;)--r;e[r++]=i}return e.slice(0,r)}function qe(n,t){return n[0]-t[0]||n[1]-t[1]}function Te(n,t,e){return(e[0]-t[0])*(n[1]-t[1])<(e[1]-t[1])*(n[0]-t[0])}function Re(n,t,e,r){var i=n[0],u=e[0],o=t[0]-i,a=r[0]-u,l=n[1],c=e[1],f=t[1]-l,s=r[1]-c,h=(a*(l-c)-s*(i-u))/(s*o-a*f);return[i+h*o,l+h*f]}function De(n){var t=n[0],e=n[n.length-1];return!(t[0]-e[0]||t[1]-e[1])}function Pe(){rr(this),this.edge=this.site=this.circle=null}function Ue(n){var t=cl.pop()||new Pe;return t.site=n,t}function je(n){Be(n),ol.remove(n),cl.push(n),rr(n)}function Fe(n){var t=n.circle,e=t.x,r=t.cy,i={x:e,y:r},u=n.P,o=n.N,a=[n];je(n);for(var l=u;l.circle&&xo(e-l.circle.x)f;++f)c=a[f],l=a[f-1],nr(c.edge,l.site,c.site,i);l=a[0],c=a[s-1],c.edge=Ke(l.site,c.site,null,i),$e(l),$e(c)}function He(n){for(var t,e,r,i,u=n.x,o=n.y,a=ol._;a;)if(r=Oe(a,o)-u,r>Uo)a=a.L;else{if(i=u-Ie(a,o),!(i>Uo)){r>-Uo?(t=a.P,e=a):i>-Uo?(t=a,e=a.N):t=e=a;break}if(!a.R){t=a;break}a=a.R}var l=Ue(n);if(ol.insert(t,l),t||e){if(t===e)return Be(t),e=Ue(t.site),ol.insert(l,e),l.edge=e.edge=Ke(t.site,l.site),$e(t),void $e(e);if(!e)return void(l.edge=Ke(t.site,l.site));Be(t),Be(e);var c=t.site,f=c.x,s=c.y,h=n.x-f,p=n.y-s,g=e.site,v=g.x-f,d=g.y-s,y=2*(h*d-p*v),m=h*h+p*p,M=v*v+d*d,x={x:(d*m-p*M)/y+f,y:(h*M-v*m)/y+s};nr(e.edge,c,g,x),l.edge=Ke(c,n,null,x),e.edge=Ke(n,g,null,x),$e(t),$e(e)}}function Oe(n,t){var e=n.site,r=e.x,i=e.y,u=i-t;if(!u)return r;var o=n.P;if(!o)return-(1/0);e=o.site;var a=e.x,l=e.y,c=l-t;if(!c)return a;var f=a-r,s=1/u-1/c,h=f/c;return s?(-h+Math.sqrt(h*h-2*s*(f*f/(-2*c)-l+c/2+i-u/2)))/s+r:(r+a)/2}function Ie(n,t){var e=n.N;if(e)return Oe(e,t);var r=n.site;return r.y===t?r.x:1/0}function Ye(n){this.site=n,this.edges=[]}function Ze(n){for(var t,e,r,i,u,o,a,l,c,f,s=n[0][0],h=n[1][0],p=n[0][1],g=n[1][1],v=ul,d=v.length;d--;)if(u=v[d],u&&u.prepare())for(a=u.edges,l=a.length,o=0;l>o;)f=a[o].end(),r=f.x,i=f.y,c=a[++o%l].start(),t=c.x,e=c.y,(xo(r-t)>Uo||xo(i-e)>Uo)&&(a.splice(o,0,new tr(Qe(u.site,f,xo(r-s)Uo?{x:s,y:xo(t-s)Uo?{x:xo(e-g)Uo?{x:h,y:xo(t-h)Uo?{x:xo(e-p)=-jo)){var p=l*l+c*c,g=f*f+s*s,v=(s*p-c*g)/h,d=(l*g-f*p)/h,s=d+a,y=fl.pop()||new Xe;y.arc=n,y.site=i,y.x=v+o,y.y=s+Math.sqrt(v*v+d*d),y.cy=s,n.circle=y;for(var m=null,M=ll._;M;)if(y.yd||d>=a)return;if(h>g){if(u){if(u.y>=c)return}else u={x:d,y:l};e={x:d,y:c}}else{if(u){if(u.yr||r>1)if(h>g){if(u){if(u.y>=c)return}else u={x:(l-i)/r,y:l};e={x:(c-i)/r,y:c}}else{if(u){if(u.yp){if(u){if(u.x>=a)return}else u={x:o,y:r*o+i};e={x:a,y:r*a+i}}else{if(u){if(u.xu||s>o||r>h||i>p)){if(g=n.point){var g,v=t-n.x,d=e-n.y,y=v*v+d*d;if(l>y){var m=Math.sqrt(l=y);r=t-m,i=e-m,u=t+m,o=e+m,a=g}}for(var M=n.nodes,x=.5*(f+h),b=.5*(s+p),_=t>=x,w=e>=b,S=w<<1|_,k=S+4;k>S;++S)if(n=M[3&S])switch(3&S){case 0:c(n,f,s,x,b);break;case 1:c(n,x,s,h,b);break;case 2:c(n,f,b,x,p);break;case 3:c(n,x,b,h,p)}}}(n,r,i,u,o),a}function vr(n,t){n=ao.rgb(n),t=ao.rgb(t);var e=n.r,r=n.g,i=n.b,u=t.r-e,o=t.g-r,a=t.b-i;return function(n){return"#"+bn(Math.round(e+u*n))+bn(Math.round(r+o*n))+bn(Math.round(i+a*n))}}function dr(n,t){var e,r={},i={};for(e in n)e in t?r[e]=Mr(n[e],t[e]):i[e]=n[e];for(e in t)e in n||(i[e]=t[e]);return function(n){for(e in r)i[e]=r[e](n);return i}}function yr(n,t){return n=+n,t=+t,function(e){return n*(1-e)+t*e}}function mr(n,t){var e,r,i,u=hl.lastIndex=pl.lastIndex=0,o=-1,a=[],l=[];for(n+="",t+="";(e=hl.exec(n))&&(r=pl.exec(t));)(i=r.index)>u&&(i=t.slice(u,i),a[o]?a[o]+=i:a[++o]=i),(e=e[0])===(r=r[0])?a[o]?a[o]+=r:a[++o]=r:(a[++o]=null,l.push({i:o,x:yr(e,r)})),u=pl.lastIndex;return ur;++r)a[(e=l[r]).i]=e.x(n);return a.join("")})}function Mr(n,t){for(var e,r=ao.interpolators.length;--r>=0&&!(e=ao.interpolators[r](n,t)););return e}function xr(n,t){var e,r=[],i=[],u=n.length,o=t.length,a=Math.min(n.length,t.length);for(e=0;a>e;++e)r.push(Mr(n[e],t[e]));for(;u>e;++e)i[e]=n[e];for(;o>e;++e)i[e]=t[e];return function(n){for(e=0;a>e;++e)i[e]=r[e](n);return i}}function br(n){return function(t){return 0>=t?0:t>=1?1:n(t)}}function _r(n){return function(t){return 1-n(1-t)}}function wr(n){return function(t){return.5*(.5>t?n(2*t):2-n(2-2*t))}}function Sr(n){return n*n}function kr(n){return n*n*n}function Nr(n){if(0>=n)return 0;if(n>=1)return 1;var t=n*n,e=t*n;return 4*(.5>n?e:3*(n-t)+e-.75)}function Er(n){return function(t){return Math.pow(t,n)}}function Ar(n){return 1-Math.cos(n*Io)}function Cr(n){return Math.pow(2,10*(n-1))}function zr(n){return 1-Math.sqrt(1-n*n)}function Lr(n,t){var e;return arguments.length<2&&(t=.45),arguments.length?e=t/Ho*Math.asin(1/n):(n=1,e=t/4),function(r){return 1+n*Math.pow(2,-10*r)*Math.sin((r-e)*Ho/t)}}function qr(n){return n||(n=1.70158),function(t){return t*t*((n+1)*t-n)}}function Tr(n){return 1/2.75>n?7.5625*n*n:2/2.75>n?7.5625*(n-=1.5/2.75)*n+.75:2.5/2.75>n?7.5625*(n-=2.25/2.75)*n+.9375:7.5625*(n-=2.625/2.75)*n+.984375}function Rr(n,t){n=ao.hcl(n),t=ao.hcl(t);var e=n.h,r=n.c,i=n.l,u=t.h-e,o=t.c-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.c:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return sn(e+u*n,r+o*n,i+a*n)+""}}function Dr(n,t){n=ao.hsl(n),t=ao.hsl(t);var e=n.h,r=n.s,i=n.l,u=t.h-e,o=t.s-r,a=t.l-i;return isNaN(o)&&(o=0,r=isNaN(r)?t.s:r),isNaN(u)?(u=0,e=isNaN(e)?t.h:e):u>180?u-=360:-180>u&&(u+=360),function(n){return cn(e+u*n,r+o*n,i+a*n)+""}}function Pr(n,t){n=ao.lab(n),t=ao.lab(t);var e=n.l,r=n.a,i=n.b,u=t.l-e,o=t.a-r,a=t.b-i;return function(n){return pn(e+u*n,r+o*n,i+a*n)+""}}function Ur(n,t){return t-=n,function(e){return Math.round(n+t*e)}}function jr(n){var t=[n.a,n.b],e=[n.c,n.d],r=Hr(t),i=Fr(t,e),u=Hr(Or(e,t,-i))||0;t[0]*e[1]180?t+=360:t-n>180&&(n+=360),r.push({i:e.push(Ir(e)+"rotate(",null,")")-2,x:yr(n,t)})):t&&e.push(Ir(e)+"rotate("+t+")")}function Vr(n,t,e,r){n!==t?r.push({i:e.push(Ir(e)+"skewX(",null,")")-2,x:yr(n,t)}):t&&e.push(Ir(e)+"skewX("+t+")")}function Xr(n,t,e,r){if(n[0]!==t[0]||n[1]!==t[1]){var i=e.push(Ir(e)+"scale(",null,",",null,")");r.push({i:i-4,x:yr(n[0],t[0])},{i:i-2,x:yr(n[1],t[1])})}else 1===t[0]&&1===t[1]||e.push(Ir(e)+"scale("+t+")")}function $r(n,t){var e=[],r=[];return n=ao.transform(n),t=ao.transform(t),Yr(n.translate,t.translate,e,r),Zr(n.rotate,t.rotate,e,r),Vr(n.skew,t.skew,e,r),Xr(n.scale,t.scale,e,r),n=t=null,function(n){for(var t,i=-1,u=r.length;++i=0;)e.push(i[r])}function oi(n,t){for(var e=[n],r=[];null!=(n=e.pop());)if(r.push(n),(u=n.children)&&(i=u.length))for(var i,u,o=-1;++oe;++e)(t=n[e][1])>i&&(r=e,i=t);return r}function yi(n){return n.reduce(mi,0)}function mi(n,t){return n+t[1]}function Mi(n,t){return xi(n,Math.ceil(Math.log(t.length)/Math.LN2+1))}function xi(n,t){for(var e=-1,r=+n[0],i=(n[1]-r)/t,u=[];++e<=t;)u[e]=i*e+r;return u}function bi(n){return[ao.min(n),ao.max(n)]}function _i(n,t){return n.value-t.value}function wi(n,t){var e=n._pack_next;n._pack_next=t,t._pack_prev=n,t._pack_next=e,e._pack_prev=t}function Si(n,t){n._pack_next=t,t._pack_prev=n}function ki(n,t){var e=t.x-n.x,r=t.y-n.y,i=n.r+t.r;return.999*i*i>e*e+r*r}function Ni(n){function t(n){f=Math.min(n.x-n.r,f),s=Math.max(n.x+n.r,s),h=Math.min(n.y-n.r,h),p=Math.max(n.y+n.r,p)}if((e=n.children)&&(c=e.length)){var e,r,i,u,o,a,l,c,f=1/0,s=-(1/0),h=1/0,p=-(1/0);if(e.forEach(Ei),r=e[0],r.x=-r.r,r.y=0,t(r),c>1&&(i=e[1],i.x=i.r,i.y=0,t(i),c>2))for(u=e[2],zi(r,i,u),t(u),wi(r,u),r._pack_prev=u,wi(u,i),i=r._pack_next,o=3;c>o;o++){zi(r,i,u=e[o]);var g=0,v=1,d=1;for(a=i._pack_next;a!==i;a=a._pack_next,v++)if(ki(a,u)){g=1;break}if(1==g)for(l=r._pack_prev;l!==a._pack_prev&&!ki(l,u);l=l._pack_prev,d++);g?(d>v||v==d&&i.ro;o++)u=e[o],u.x-=y,u.y-=m,M=Math.max(M,u.r+Math.sqrt(u.x*u.x+u.y*u.y));n.r=M,e.forEach(Ai)}}function Ei(n){n._pack_next=n._pack_prev=n}function Ai(n){delete n._pack_next,delete n._pack_prev}function Ci(n,t,e,r){var i=n.children;if(n.x=t+=r*n.x,n.y=e+=r*n.y,n.r*=r,i)for(var u=-1,o=i.length;++u=0;)t=i[u],t.z+=e,t.m+=e,e+=t.s+(r+=t.c)}function Pi(n,t,e){return n.a.parent===t.parent?n.a:e}function Ui(n){return 1+ao.max(n,function(n){return n.y})}function ji(n){return n.reduce(function(n,t){return n+t.x},0)/n.length}function Fi(n){var t=n.children;return t&&t.length?Fi(t[0]):n}function Hi(n){var t,e=n.children;return e&&(t=e.length)?Hi(e[t-1]):n}function Oi(n){return{x:n.x,y:n.y,dx:n.dx,dy:n.dy}}function Ii(n,t){var e=n.x+t[3],r=n.y+t[0],i=n.dx-t[1]-t[3],u=n.dy-t[0]-t[2];return 0>i&&(e+=i/2,i=0),0>u&&(r+=u/2,u=0),{x:e,y:r,dx:i,dy:u}}function Yi(n){var t=n[0],e=n[n.length-1];return e>t?[t,e]:[e,t]}function Zi(n){return n.rangeExtent?n.rangeExtent():Yi(n.range())}function Vi(n,t,e,r){var i=e(n[0],n[1]),u=r(t[0],t[1]);return function(n){return u(i(n))}}function Xi(n,t){var e,r=0,i=n.length-1,u=n[r],o=n[i];return u>o&&(e=r,r=i,i=e,e=u,u=o,o=e),n[r]=t.floor(u),n[i]=t.ceil(o),n}function $i(n){return n?{floor:function(t){return Math.floor(t/n)*n},ceil:function(t){return Math.ceil(t/n)*n}}:Sl}function Bi(n,t,e,r){var i=[],u=[],o=0,a=Math.min(n.length,t.length)-1;for(n[a]2?Bi:Vi,l=r?Wr:Br;return o=i(n,t,l,e),a=i(t,n,l,Mr),u}function u(n){return o(n)}var o,a;return u.invert=function(n){return a(n)},u.domain=function(t){return arguments.length?(n=t.map(Number),i()):n},u.range=function(n){return arguments.length?(t=n,i()):t},u.rangeRound=function(n){return u.range(n).interpolate(Ur)},u.clamp=function(n){return arguments.length?(r=n,i()):r},u.interpolate=function(n){return arguments.length?(e=n,i()):e},u.ticks=function(t){return Qi(n,t)},u.tickFormat=function(t,e){return nu(n,t,e)},u.nice=function(t){return Gi(n,t),i()},u.copy=function(){return Wi(n,t,e,r)},i()}function Ji(n,t){return ao.rebind(n,t,"range","rangeRound","interpolate","clamp")}function Gi(n,t){return Xi(n,$i(Ki(n,t)[2])),Xi(n,$i(Ki(n,t)[2])),n}function Ki(n,t){null==t&&(t=10);var e=Yi(n),r=e[1]-e[0],i=Math.pow(10,Math.floor(Math.log(r/t)/Math.LN10)),u=t/r*i;return.15>=u?i*=10:.35>=u?i*=5:.75>=u&&(i*=2),e[0]=Math.ceil(e[0]/i)*i,e[1]=Math.floor(e[1]/i)*i+.5*i,e[2]=i,e}function Qi(n,t){return ao.range.apply(ao,Ki(n,t))}function nu(n,t,e){var r=Ki(n,t);if(e){var i=ha.exec(e);if(i.shift(),"s"===i[8]){var u=ao.formatPrefix(Math.max(xo(r[0]),xo(r[1])));return i[7]||(i[7]="."+tu(u.scale(r[2]))),i[8]="f",e=ao.format(i.join("")),function(n){return e(u.scale(n))+u.symbol}}i[7]||(i[7]="."+eu(i[8],r)),e=i.join("")}else e=",."+tu(r[2])+"f";return ao.format(e)}function tu(n){return-Math.floor(Math.log(n)/Math.LN10+.01)}function eu(n,t){var e=tu(t[2]);return n in kl?Math.abs(e-tu(Math.max(xo(t[0]),xo(t[1]))))+ +("e"!==n):e-2*("%"===n)}function ru(n,t,e,r){function i(n){return(e?Math.log(0>n?0:n):-Math.log(n>0?0:-n))/Math.log(t)}function u(n){return e?Math.pow(t,n):-Math.pow(t,-n)}function o(t){return n(i(t))}return o.invert=function(t){return u(n.invert(t))},o.domain=function(t){return arguments.length?(e=t[0]>=0,n.domain((r=t.map(Number)).map(i)),o):r},o.base=function(e){return arguments.length?(t=+e,n.domain(r.map(i)),o):t},o.nice=function(){var t=Xi(r.map(i),e?Math:El);return n.domain(t),r=t.map(u),o},o.ticks=function(){var n=Yi(r),o=[],a=n[0],l=n[1],c=Math.floor(i(a)),f=Math.ceil(i(l)),s=t%1?2:t;if(isFinite(f-c)){if(e){for(;f>c;c++)for(var h=1;s>h;h++)o.push(u(c)*h);o.push(u(c))}else for(o.push(u(c));c++0;h--)o.push(u(c)*h);for(c=0;o[c]l;f--);o=o.slice(c,f)}return o},o.tickFormat=function(n,e){if(!arguments.length)return Nl;arguments.length<2?e=Nl:"function"!=typeof e&&(e=ao.format(e));var r=Math.max(1,t*n/o.ticks().length);return function(n){var o=n/u(Math.round(i(n)));return t-.5>o*t&&(o*=t),r>=o?e(n):""}},o.copy=function(){return ru(n.copy(),t,e,r)},Ji(o,n)}function iu(n,t,e){function r(t){return n(i(t))}var i=uu(t),u=uu(1/t);return r.invert=function(t){return u(n.invert(t))},r.domain=function(t){return arguments.length?(n.domain((e=t.map(Number)).map(i)),r):e},r.ticks=function(n){return Qi(e,n)},r.tickFormat=function(n,t){return nu(e,n,t)},r.nice=function(n){return r.domain(Gi(e,n))},r.exponent=function(o){return arguments.length?(i=uu(t=o),u=uu(1/t),n.domain(e.map(i)),r):t},r.copy=function(){return iu(n.copy(),t,e)},Ji(r,n)}function uu(n){return function(t){return 0>t?-Math.pow(-t,n):Math.pow(t,n)}}function ou(n,t){function e(e){return u[((i.get(e)||("range"===t.t?i.set(e,n.push(e)):NaN))-1)%u.length]}function r(t,e){return ao.range(n.length).map(function(n){return t+e*n})}var i,u,o;return e.domain=function(r){if(!arguments.length)return n;n=[],i=new c;for(var u,o=-1,a=r.length;++oe?[NaN,NaN]:[e>0?a[e-1]:n[0],et?NaN:t/u+n,[t,t+1/u]},r.copy=function(){return lu(n,t,e)},i()}function cu(n,t){function e(e){return e>=e?t[ao.bisect(n,e)]:void 0}return e.domain=function(t){return arguments.length?(n=t,e):n},e.range=function(n){return arguments.length?(t=n,e):t},e.invertExtent=function(e){return e=t.indexOf(e),[n[e-1],n[e]]},e.copy=function(){return cu(n,t)},e}function fu(n){function t(n){return+n}return t.invert=t,t.domain=t.range=function(e){return arguments.length?(n=e.map(t),t):n},t.ticks=function(t){return Qi(n,t)},t.tickFormat=function(t,e){return nu(n,t,e)},t.copy=function(){return fu(n)},t}function su(){return 0}function hu(n){return n.innerRadius}function pu(n){return n.outerRadius}function gu(n){return n.startAngle}function vu(n){return n.endAngle}function du(n){return n&&n.padAngle}function yu(n,t,e,r){return(n-e)*t-(t-r)*n>0?0:1}function mu(n,t,e,r,i){var u=n[0]-t[0],o=n[1]-t[1],a=(i?r:-r)/Math.sqrt(u*u+o*o),l=a*o,c=-a*u,f=n[0]+l,s=n[1]+c,h=t[0]+l,p=t[1]+c,g=(f+h)/2,v=(s+p)/2,d=h-f,y=p-s,m=d*d+y*y,M=e-r,x=f*p-h*s,b=(0>y?-1:1)*Math.sqrt(Math.max(0,M*M*m-x*x)),_=(x*y-d*b)/m,w=(-x*d-y*b)/m,S=(x*y+d*b)/m,k=(-x*d+y*b)/m,N=_-g,E=w-v,A=S-g,C=k-v;return N*N+E*E>A*A+C*C&&(_=S,w=k),[[_-l,w-c],[_*e/M,w*e/M]]}function Mu(n){function t(t){function o(){c.push("M",u(n(f),a))}for(var l,c=[],f=[],s=-1,h=t.length,p=En(e),g=En(r);++s1?n.join("L"):n+"Z"}function bu(n){return n.join("L")+"Z"}function _u(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1&&i.push("H",r[0]),i.join("")}function wu(n){for(var t=0,e=n.length,r=n[0],i=[r[0],",",r[1]];++t1){a=t[1],u=n[l],l++,r+="C"+(i[0]+o[0])+","+(i[1]+o[1])+","+(u[0]-a[0])+","+(u[1]-a[1])+","+u[0]+","+u[1];for(var c=2;c9&&(i=3*t/Math.sqrt(i),o[a]=i*e,o[a+1]=i*r));for(a=-1;++a<=l;)i=(n[Math.min(l,a+1)][0]-n[Math.max(0,a-1)][0])/(6*(1+o[a]*o[a])),u.push([i||0,o[a]*i||0]);return u}function Fu(n){return n.length<3?xu(n):n[0]+Au(n,ju(n))}function Hu(n){for(var t,e,r,i=-1,u=n.length;++i=t?o(n-t):void(f.c=o)}function o(e){var i=g.active,u=g[i];u&&(u.timer.c=null,u.timer.t=NaN,--g.count,delete g[i],u.event&&u.event.interrupt.call(n,n.__data__,u.index));for(var o in g)if(r>+o){var c=g[o];c.timer.c=null,c.timer.t=NaN,--g.count,delete g[o]}f.c=a,qn(function(){return f.c&&a(e||1)&&(f.c=null,f.t=NaN),1},0,l),g.active=r,v.event&&v.event.start.call(n,n.__data__,t),p=[],v.tween.forEach(function(e,r){(r=r.call(n,n.__data__,t))&&p.push(r)}),h=v.ease,s=v.duration}function a(i){for(var u=i/s,o=h(u),a=p.length;a>0;)p[--a].call(n,o);return u>=1?(v.event&&v.event.end.call(n,n.__data__,t),--g.count?delete g[r]:delete n[e],1):void 0}var l,f,s,h,p,g=n[e]||(n[e]={active:0,count:0}),v=g[r];v||(l=i.time,f=qn(u,0,l),v=g[r]={tween:new c,time:l,timer:f,delay:i.delay,duration:i.duration,ease:i.ease,index:t},i=null,++g.count)}function no(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate("+(isFinite(r)?r:e(n))+",0)"})}function to(n,t,e){n.attr("transform",function(n){var r=t(n);return"translate(0,"+(isFinite(r)?r:e(n))+")"})}function eo(n){return n.toISOString()}function ro(n,t,e){function r(t){return n(t)}function i(n,e){var r=n[1]-n[0],i=r/e,u=ao.bisect(Kl,i);return u==Kl.length?[t.year,Ki(n.map(function(n){return n/31536e6}),e)[2]]:u?t[i/Kl[u-1]1?{floor:function(t){for(;e(t=n.floor(t));)t=io(t-1);return t},ceil:function(t){for(;e(t=n.ceil(t));)t=io(+t+1);return t}}:n))},r.ticks=function(n,t){var e=Yi(r.domain()),u=null==n?i(e,10):"number"==typeof n?i(e,n):!n.range&&[{range:n},t];return u&&(n=u[0],t=u[1]),n.range(e[0],io(+e[1]+1),1>t?1:t)},r.tickFormat=function(){return e},r.copy=function(){return ro(n.copy(),t,e)},Ji(r,n)}function io(n){return new Date(n)}function uo(n){return JSON.parse(n.responseText)}function oo(n){var t=fo.createRange();return t.selectNode(fo.body),t.createContextualFragment(n.responseText)}var ao={version:"3.5.17"},lo=[].slice,co=function(n){return lo.call(n)},fo=this.document;if(fo)try{co(fo.documentElement.childNodes)[0].nodeType}catch(so){co=function(n){for(var t=n.length,e=new Array(t);t--;)e[t]=n[t];return e}}if(Date.now||(Date.now=function(){return+new Date}),fo)try{fo.createElement("DIV").style.setProperty("opacity",0,"")}catch(ho){var po=this.Element.prototype,go=po.setAttribute,vo=po.setAttributeNS,yo=this.CSSStyleDeclaration.prototype,mo=yo.setProperty;po.setAttribute=function(n,t){go.call(this,n,t+"")},po.setAttributeNS=function(n,t,e){vo.call(this,n,t,e+"")},yo.setProperty=function(n,t,e){mo.call(this,n,t+"",e)}}ao.ascending=e,ao.descending=function(n,t){return n>t?-1:t>n?1:t>=n?0:NaN},ao.min=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ir&&(e=r)}else{for(;++i=r){e=r;break}for(;++ir&&(e=r)}return e},ao.max=function(n,t){var e,r,i=-1,u=n.length;if(1===arguments.length){for(;++i=r){e=r;break}for(;++ie&&(e=r)}else{for(;++i=r){e=r;break}for(;++ie&&(e=r)}return e},ao.extent=function(n,t){var e,r,i,u=-1,o=n.length;if(1===arguments.length){for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}else{for(;++u=r){e=i=r;break}for(;++ur&&(e=r),r>i&&(i=r))}return[e,i]},ao.sum=function(n,t){var e,r=0,u=n.length,o=-1;if(1===arguments.length)for(;++o1?l/(f-1):void 0},ao.deviation=function(){var n=ao.variance.apply(this,arguments);return n?Math.sqrt(n):n};var Mo=u(e);ao.bisectLeft=Mo.left,ao.bisect=ao.bisectRight=Mo.right,ao.bisector=function(n){return u(1===n.length?function(t,r){return e(n(t),r)}:n)},ao.shuffle=function(n,t,e){(u=arguments.length)<3&&(e=n.length,2>u&&(t=0));for(var r,i,u=e-t;u;)i=Math.random()*u--|0,r=n[u+t],n[u+t]=n[i+t],n[i+t]=r;return n},ao.permute=function(n,t){for(var e=t.length,r=new Array(e);e--;)r[e]=n[t[e]];return r},ao.pairs=function(n){for(var t,e=0,r=n.length-1,i=n[0],u=new Array(0>r?0:r);r>e;)u[e]=[t=i,i=n[++e]];return u},ao.transpose=function(n){if(!(i=n.length))return[];for(var t=-1,e=ao.min(n,o),r=new Array(e);++t=0;)for(r=n[i],t=r.length;--t>=0;)e[--o]=r[t];return e};var xo=Math.abs;ao.range=function(n,t,e){if(arguments.length<3&&(e=1,arguments.length<2&&(t=n,n=0)),(t-n)/e===1/0)throw new Error("infinite range");var r,i=[],u=a(xo(e)),o=-1;if(n*=u,t*=u,e*=u,0>e)for(;(r=n+e*++o)>t;)i.push(r/u);else for(;(r=n+e*++o)=u.length)return r?r.call(i,o):e?o.sort(e):o;for(var l,f,s,h,p=-1,g=o.length,v=u[a++],d=new c;++p=u.length)return n;var r=[],i=o[e++];return n.forEach(function(n,i){r.push({key:n,values:t(i,e)})}),i?r.sort(function(n,t){return i(n.key,t.key)}):r}var e,r,i={},u=[],o=[];return i.map=function(t,e){return n(e,t,0)},i.entries=function(e){return t(n(ao.map,e,0),0)},i.key=function(n){return u.push(n),i},i.sortKeys=function(n){return o[u.length-1]=n,i},i.sortValues=function(n){return e=n,i},i.rollup=function(n){return r=n,i},i},ao.set=function(n){var t=new y;if(n)for(var e=0,r=n.length;r>e;++e)t.add(n[e]);return t},l(y,{has:h,add:function(n){return this._[f(n+="")]=!0,n},remove:p,values:g,size:v,empty:d,forEach:function(n){for(var t in this._)n.call(this,s(t))}}),ao.behavior={},ao.rebind=function(n,t){for(var e,r=1,i=arguments.length;++r=0&&(r=n.slice(e+1),n=n.slice(0,e)),n)return arguments.length<2?this[n].on(r):this[n].on(r,t);if(2===arguments.length){if(null==t)for(n in this)this.hasOwnProperty(n)&&this[n].on(r,null);return this}},ao.event=null,ao.requote=function(n){return n.replace(So,"\\$&")};var So=/[\\\^\$\*\+\?\|\[\]\(\)\.\{\}]/g,ko={}.__proto__?function(n,t){n.__proto__=t}:function(n,t){for(var e in t)n[e]=t[e]},No=function(n,t){return t.querySelector(n)},Eo=function(n,t){return t.querySelectorAll(n)},Ao=function(n,t){var e=n.matches||n[x(n,"matchesSelector")];return(Ao=function(n,t){return e.call(n,t)})(n,t)};"function"==typeof Sizzle&&(No=function(n,t){return Sizzle(n,t)[0]||null},Eo=Sizzle,Ao=Sizzle.matchesSelector),ao.selection=function(){return ao.select(fo.documentElement)};var Co=ao.selection.prototype=[];Co.select=function(n){var t,e,r,i,u=[];n=A(n);for(var o=-1,a=this.length;++o=0&&"xmlns"!==(e=n.slice(0,t))&&(n=n.slice(t+1)),Lo.hasOwnProperty(e)?{space:Lo[e],local:n}:n}},Co.attr=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node();return n=ao.ns.qualify(n),n.local?e.getAttributeNS(n.space,n.local):e.getAttribute(n)}for(t in n)this.each(z(t,n[t]));return this}return this.each(z(n,t))},Co.classed=function(n,t){if(arguments.length<2){if("string"==typeof n){var e=this.node(),r=(n=T(n)).length,i=-1;if(t=e.classList){for(;++ii){if("string"!=typeof n){2>i&&(e="");for(r in n)this.each(P(r,n[r],e));return this}if(2>i){var u=this.node();return t(u).getComputedStyle(u,null).getPropertyValue(n)}r=""}return this.each(P(n,e,r))},Co.property=function(n,t){if(arguments.length<2){if("string"==typeof n)return this.node()[n];for(t in n)this.each(U(t,n[t]));return this}return this.each(U(n,t))},Co.text=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.textContent=null==t?"":t}:null==n?function(){this.textContent=""}:function(){this.textContent=n}):this.node().textContent},Co.html=function(n){return arguments.length?this.each("function"==typeof n?function(){var t=n.apply(this,arguments);this.innerHTML=null==t?"":t}:null==n?function(){this.innerHTML=""}:function(){this.innerHTML=n}):this.node().innerHTML},Co.append=function(n){return n=j(n),this.select(function(){return this.appendChild(n.apply(this,arguments))})},Co.insert=function(n,t){return n=j(n),t=A(t),this.select(function(){return this.insertBefore(n.apply(this,arguments),t.apply(this,arguments)||null)})},Co.remove=function(){return this.each(F)},Co.data=function(n,t){function e(n,e){var r,i,u,o=n.length,s=e.length,h=Math.min(o,s),p=new Array(s),g=new Array(s),v=new Array(o);if(t){var d,y=new c,m=new Array(o);for(r=-1;++rr;++r)g[r]=H(e[r]);for(;o>r;++r)v[r]=n[r]}g.update=p,g.parentNode=p.parentNode=v.parentNode=n.parentNode,a.push(g),l.push(p),f.push(v)}var r,i,u=-1,o=this.length;if(!arguments.length){for(n=new Array(o=(r=this[0]).length);++uu;u++){i.push(t=[]),t.parentNode=(e=this[u]).parentNode;for(var a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return E(i)},Co.order=function(){for(var n=-1,t=this.length;++n=0;)(e=r[i])&&(u&&u!==e.nextSibling&&u.parentNode.insertBefore(e,u),u=e);return this},Co.sort=function(n){n=I.apply(this,arguments);for(var t=-1,e=this.length;++tn;n++)for(var e=this[n],r=0,i=e.length;i>r;r++){var u=e[r];if(u)return u}return null},Co.size=function(){var n=0;return Y(this,function(){++n}),n};var qo=[];ao.selection.enter=Z,ao.selection.enter.prototype=qo,qo.append=Co.append,qo.empty=Co.empty,qo.node=Co.node,qo.call=Co.call,qo.size=Co.size,qo.select=function(n){for(var t,e,r,i,u,o=[],a=-1,l=this.length;++ar){if("string"!=typeof n){2>r&&(t=!1);for(e in n)this.each(X(e,n[e],t));return this}if(2>r)return(r=this.node()["__on"+n])&&r._;e=!1}return this.each(X(n,t,e))};var To=ao.map({mouseenter:"mouseover",mouseleave:"mouseout"});fo&&To.forEach(function(n){"on"+n in fo&&To.remove(n)});var Ro,Do=0;ao.mouse=function(n){return J(n,k())};var Po=this.navigator&&/WebKit/.test(this.navigator.userAgent)?-1:0;ao.touch=function(n,t,e){if(arguments.length<3&&(e=t,t=k().changedTouches),t)for(var r,i=0,u=t.length;u>i;++i)if((r=t[i]).identifier===e)return J(n,r)},ao.behavior.drag=function(){function n(){this.on("mousedown.drag",u).on("touchstart.drag",o)}function e(n,t,e,u,o){return function(){function a(){var n,e,r=t(h,v);r&&(n=r[0]-M[0],e=r[1]-M[1],g|=n|e,M=r,p({type:"drag",x:r[0]+c[0],y:r[1]+c[1],dx:n,dy:e}))}function l(){t(h,v)&&(y.on(u+d,null).on(o+d,null),m(g),p({type:"dragend"}))}var c,f=this,s=ao.event.target.correspondingElement||ao.event.target,h=f.parentNode,p=r.of(f,arguments),g=0,v=n(),d=".drag"+(null==v?"":"-"+v),y=ao.select(e(s)).on(u+d,a).on(o+d,l),m=W(s),M=t(h,v);i?(c=i.apply(f,arguments),c=[c.x-M[0],c.y-M[1]]):c=[0,0],p({type:"dragstart"})}}var r=N(n,"drag","dragstart","dragend"),i=null,u=e(b,ao.mouse,t,"mousemove","mouseup"),o=e(G,ao.touch,m,"touchmove","touchend");return n.origin=function(t){return arguments.length?(i=t,n):i},ao.rebind(n,r,"on")},ao.touches=function(n,t){return arguments.length<2&&(t=k().touches),t?co(t).map(function(t){var e=J(n,t);return e.identifier=t.identifier,e}):[]};var Uo=1e-6,jo=Uo*Uo,Fo=Math.PI,Ho=2*Fo,Oo=Ho-Uo,Io=Fo/2,Yo=Fo/180,Zo=180/Fo,Vo=Math.SQRT2,Xo=2,$o=4;ao.interpolateZoom=function(n,t){var e,r,i=n[0],u=n[1],o=n[2],a=t[0],l=t[1],c=t[2],f=a-i,s=l-u,h=f*f+s*s;if(jo>h)r=Math.log(c/o)/Vo,e=function(n){return[i+n*f,u+n*s,o*Math.exp(Vo*n*r)]};else{var p=Math.sqrt(h),g=(c*c-o*o+$o*h)/(2*o*Xo*p),v=(c*c-o*o-$o*h)/(2*c*Xo*p),d=Math.log(Math.sqrt(g*g+1)-g),y=Math.log(Math.sqrt(v*v+1)-v);r=(y-d)/Vo,e=function(n){var t=n*r,e=rn(d),a=o/(Xo*p)*(e*un(Vo*t+d)-en(d));return[i+a*f,u+a*s,o*e/rn(Vo*t+d)]}}return e.duration=1e3*r,e},ao.behavior.zoom=function(){function n(n){n.on(L,s).on(Wo+".zoom",p).on("dblclick.zoom",g).on(R,h)}function e(n){return[(n[0]-k.x)/k.k,(n[1]-k.y)/k.k]}function r(n){return[n[0]*k.k+k.x,n[1]*k.k+k.y]}function i(n){k.k=Math.max(A[0],Math.min(A[1],n))}function u(n,t){t=r(t),k.x+=n[0]-t[0],k.y+=n[1]-t[1]}function o(t,e,r,o){t.__chart__={x:k.x,y:k.y,k:k.k},i(Math.pow(2,o)),u(d=e,r),t=ao.select(t),C>0&&(t=t.transition().duration(C)),t.call(n.event)}function a(){b&&b.domain(x.range().map(function(n){return(n-k.x)/k.k}).map(x.invert)),w&&w.domain(_.range().map(function(n){return(n-k.y)/k.k}).map(_.invert))}function l(n){z++||n({type:"zoomstart"})}function c(n){a(),n({type:"zoom",scale:k.k,translate:[k.x,k.y]})}function f(n){--z||(n({type:"zoomend"}),d=null)}function s(){function n(){a=1,u(ao.mouse(i),h),c(o)}function r(){s.on(q,null).on(T,null),p(a),f(o)}var i=this,o=D.of(i,arguments),a=0,s=ao.select(t(i)).on(q,n).on(T,r),h=e(ao.mouse(i)),p=W(i);Il.call(i),l(o)}function h(){function n(){var n=ao.touches(g);return p=k.k,n.forEach(function(n){n.identifier in d&&(d[n.identifier]=e(n))}),n}function t(){var t=ao.event.target;ao.select(t).on(x,r).on(b,a),_.push(t);for(var e=ao.event.changedTouches,i=0,u=e.length;u>i;++i)d[e[i].identifier]=null;var l=n(),c=Date.now();if(1===l.length){if(500>c-M){var f=l[0];o(g,f,d[f.identifier],Math.floor(Math.log(k.k)/Math.LN2)+1),S()}M=c}else if(l.length>1){var f=l[0],s=l[1],h=f[0]-s[0],p=f[1]-s[1];y=h*h+p*p}}function r(){var n,t,e,r,o=ao.touches(g);Il.call(g);for(var a=0,l=o.length;l>a;++a,r=null)if(e=o[a],r=d[e.identifier]){if(t)break;n=e,t=r}if(r){var f=(f=e[0]-n[0])*f+(f=e[1]-n[1])*f,s=y&&Math.sqrt(f/y);n=[(n[0]+e[0])/2,(n[1]+e[1])/2],t=[(t[0]+r[0])/2,(t[1]+r[1])/2],i(s*p)}M=null,u(n,t),c(v)}function a(){if(ao.event.touches.length){for(var t=ao.event.changedTouches,e=0,r=t.length;r>e;++e)delete d[t[e].identifier];for(var i in d)return void n()}ao.selectAll(_).on(m,null),w.on(L,s).on(R,h),N(),f(v)}var p,g=this,v=D.of(g,arguments),d={},y=0,m=".zoom-"+ao.event.changedTouches[0].identifier,x="touchmove"+m,b="touchend"+m,_=[],w=ao.select(g),N=W(g);t(),l(v),w.on(L,null).on(R,t)}function p(){var n=D.of(this,arguments);m?clearTimeout(m):(Il.call(this),v=e(d=y||ao.mouse(this)),l(n)),m=setTimeout(function(){m=null,f(n)},50),S(),i(Math.pow(2,.002*Bo())*k.k),u(d,v),c(n)}function g(){var n=ao.mouse(this),t=Math.log(k.k)/Math.LN2;o(this,n,e(n),ao.event.shiftKey?Math.ceil(t)-1:Math.floor(t)+1)}var v,d,y,m,M,x,b,_,w,k={x:0,y:0,k:1},E=[960,500],A=Jo,C=250,z=0,L="mousedown.zoom",q="mousemove.zoom",T="mouseup.zoom",R="touchstart.zoom",D=N(n,"zoomstart","zoom","zoomend");return Wo||(Wo="onwheel"in fo?(Bo=function(){return-ao.event.deltaY*(ao.event.deltaMode?120:1)},"wheel"):"onmousewheel"in fo?(Bo=function(){return ao.event.wheelDelta},"mousewheel"):(Bo=function(){return-ao.event.detail},"MozMousePixelScroll")),n.event=function(n){n.each(function(){var n=D.of(this,arguments),t=k;Hl?ao.select(this).transition().each("start.zoom",function(){k=this.__chart__||{x:0,y:0,k:1},l(n)}).tween("zoom:zoom",function(){var e=E[0],r=E[1],i=d?d[0]:e/2,u=d?d[1]:r/2,o=ao.interpolateZoom([(i-k.x)/k.k,(u-k.y)/k.k,e/k.k],[(i-t.x)/t.k,(u-t.y)/t.k,e/t.k]);return function(t){var r=o(t),a=e/r[2];this.__chart__=k={x:i-r[0]*a,y:u-r[1]*a,k:a},c(n)}}).each("interrupt.zoom",function(){f(n)}).each("end.zoom",function(){f(n)}):(this.__chart__=k,l(n),c(n),f(n))})},n.translate=function(t){return arguments.length?(k={x:+t[0],y:+t[1],k:k.k},a(),n):[k.x,k.y]},n.scale=function(t){return arguments.length?(k={x:k.x,y:k.y,k:null},i(+t),a(),n):k.k},n.scaleExtent=function(t){return arguments.length?(A=null==t?Jo:[+t[0],+t[1]],n):A},n.center=function(t){return arguments.length?(y=t&&[+t[0],+t[1]],n):y},n.size=function(t){return arguments.length?(E=t&&[+t[0],+t[1]],n):E},n.duration=function(t){return arguments.length?(C=+t,n):C},n.x=function(t){return arguments.length?(b=t,x=t.copy(),k={x:0,y:0,k:1},n):b},n.y=function(t){return arguments.length?(w=t,_=t.copy(),k={x:0,y:0,k:1},n):w},ao.rebind(n,D,"on")};var Bo,Wo,Jo=[0,1/0];ao.color=an,an.prototype.toString=function(){return this.rgb()+""},ao.hsl=ln;var Go=ln.prototype=new an;Go.brighter=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,this.l/n)},Go.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new ln(this.h,this.s,n*this.l)},Go.rgb=function(){return cn(this.h,this.s,this.l)},ao.hcl=fn;var Ko=fn.prototype=new an;Ko.brighter=function(n){return new fn(this.h,this.c,Math.min(100,this.l+Qo*(arguments.length?n:1)))},Ko.darker=function(n){return new fn(this.h,this.c,Math.max(0,this.l-Qo*(arguments.length?n:1)))},Ko.rgb=function(){return sn(this.h,this.c,this.l).rgb()},ao.lab=hn;var Qo=18,na=.95047,ta=1,ea=1.08883,ra=hn.prototype=new an;ra.brighter=function(n){return new hn(Math.min(100,this.l+Qo*(arguments.length?n:1)),this.a,this.b)},ra.darker=function(n){return new hn(Math.max(0,this.l-Qo*(arguments.length?n:1)),this.a,this.b)},ra.rgb=function(){return pn(this.l,this.a,this.b)},ao.rgb=mn;var ia=mn.prototype=new an;ia.brighter=function(n){n=Math.pow(.7,arguments.length?n:1);var t=this.r,e=this.g,r=this.b,i=30;return t||e||r?(t&&i>t&&(t=i),e&&i>e&&(e=i),r&&i>r&&(r=i),new mn(Math.min(255,t/n),Math.min(255,e/n),Math.min(255,r/n))):new mn(i,i,i)},ia.darker=function(n){return n=Math.pow(.7,arguments.length?n:1),new mn(n*this.r,n*this.g,n*this.b)},ia.hsl=function(){return wn(this.r,this.g,this.b)},ia.toString=function(){return"#"+bn(this.r)+bn(this.g)+bn(this.b)};var ua=ao.map({aliceblue:15792383,antiquewhite:16444375,aqua:65535,aquamarine:8388564,azure:15794175,beige:16119260,bisque:16770244,black:0,blanchedalmond:16772045,blue:255,blueviolet:9055202,brown:10824234,burlywood:14596231,cadetblue:6266528,chartreuse:8388352,chocolate:13789470,coral:16744272,cornflowerblue:6591981,cornsilk:16775388,crimson:14423100,cyan:65535,darkblue:139,darkcyan:35723,darkgoldenrod:12092939,darkgray:11119017,darkgreen:25600,darkgrey:11119017,darkkhaki:12433259,darkmagenta:9109643,darkolivegreen:5597999,darkorange:16747520,darkorchid:10040012,darkred:9109504,darksalmon:15308410,darkseagreen:9419919,darkslateblue:4734347,darkslategray:3100495,darkslategrey:3100495,darkturquoise:52945,darkviolet:9699539,deeppink:16716947,deepskyblue:49151,dimgray:6908265,dimgrey:6908265,dodgerblue:2003199,firebrick:11674146,floralwhite:16775920,forestgreen:2263842,fuchsia:16711935,gainsboro:14474460,ghostwhite:16316671,gold:16766720,goldenrod:14329120,gray:8421504,green:32768,greenyellow:11403055,grey:8421504,honeydew:15794160,hotpink:16738740,indianred:13458524,indigo:4915330,ivory:16777200,khaki:15787660,lavender:15132410,lavenderblush:16773365,lawngreen:8190976,lemonchiffon:16775885,lightblue:11393254,lightcoral:15761536,lightcyan:14745599,lightgoldenrodyellow:16448210,lightgray:13882323,lightgreen:9498256,lightgrey:13882323,lightpink:16758465,lightsalmon:16752762,lightseagreen:2142890,lightskyblue:8900346,lightslategray:7833753,lightslategrey:7833753,lightsteelblue:11584734,lightyellow:16777184,lime:65280,limegreen:3329330,linen:16445670,magenta:16711935,maroon:8388608,mediumaquamarine:6737322,mediumblue:205,mediumorchid:12211667,mediumpurple:9662683,mediumseagreen:3978097,mediumslateblue:8087790,mediumspringgreen:64154,mediumturquoise:4772300,mediumvioletred:13047173,midnightblue:1644912,mintcream:16121850,mistyrose:16770273,moccasin:16770229,navajowhite:16768685,navy:128,oldlace:16643558,olive:8421376,olivedrab:7048739,orange:16753920,orangered:16729344,orchid:14315734,palegoldenrod:15657130,palegreen:10025880,paleturquoise:11529966,palevioletred:14381203,papayawhip:16773077,peachpuff:16767673,peru:13468991,pink:16761035,plum:14524637,powderblue:11591910,purple:8388736,rebeccapurple:6697881,red:16711680,rosybrown:12357519,royalblue:4286945,saddlebrown:9127187,salmon:16416882,sandybrown:16032864,seagreen:3050327,seashell:16774638,sienna:10506797,silver:12632256,skyblue:8900331,slateblue:6970061,slategray:7372944,slategrey:7372944,snow:16775930,springgreen:65407,steelblue:4620980,tan:13808780,teal:32896,thistle:14204888,tomato:16737095,turquoise:4251856,violet:15631086,wheat:16113331,white:16777215,whitesmoke:16119285,yellow:16776960,yellowgreen:10145074});ua.forEach(function(n,t){ua.set(n,Mn(t))}),ao.functor=En,ao.xhr=An(m),ao.dsv=function(n,t){function e(n,e,u){arguments.length<3&&(u=e,e=null);var o=Cn(n,t,null==e?r:i(e),u);return o.row=function(n){return arguments.length?o.response(null==(e=n)?r:i(n)):e},o}function r(n){return e.parse(n.responseText)}function i(n){return function(t){return e.parse(t.responseText,n)}}function u(t){return t.map(o).join(n)}function o(n){return a.test(n)?'"'+n.replace(/\"/g,'""')+'"':n}var a=new RegExp('["'+n+"\n]"),l=n.charCodeAt(0);return e.parse=function(n,t){var r;return e.parseRows(n,function(n,e){if(r)return r(n,e-1);var i=new Function("d","return {"+n.map(function(n,t){return JSON.stringify(n)+": d["+t+"]"}).join(",")+"}");r=t?function(n,e){return t(i(n),e)}:i})},e.parseRows=function(n,t){function e(){if(f>=c)return o;if(i)return i=!1,u;var t=f;if(34===n.charCodeAt(t)){for(var e=t;e++f;){var r=n.charCodeAt(f++),a=1;if(10===r)i=!0;else if(13===r)i=!0,10===n.charCodeAt(f)&&(++f,++a);else if(r!==l)continue;return n.slice(t,f-a)}return n.slice(t)}for(var r,i,u={},o={},a=[],c=n.length,f=0,s=0;(r=e())!==o;){for(var h=[];r!==u&&r!==o;)h.push(r),r=e();t&&null==(h=t(h,s++))||a.push(h)}return a},e.format=function(t){if(Array.isArray(t[0]))return e.formatRows(t);var r=new y,i=[];return t.forEach(function(n){for(var t in n)r.has(t)||i.push(r.add(t))}),[i.map(o).join(n)].concat(t.map(function(t){return i.map(function(n){return o(t[n])}).join(n)})).join("\n")},e.formatRows=function(n){return n.map(u).join("\n")},e},ao.csv=ao.dsv(",","text/csv"),ao.tsv=ao.dsv(" ","text/tab-separated-values");var oa,aa,la,ca,fa=this[x(this,"requestAnimationFrame")]||function(n){setTimeout(n,17)};ao.timer=function(){qn.apply(this,arguments)},ao.timer.flush=function(){Rn(),Dn()},ao.round=function(n,t){return t?Math.round(n*(t=Math.pow(10,t)))/t:Math.round(n)};var sa=["y","z","a","f","p","n","\xb5","m","","k","M","G","T","P","E","Z","Y"].map(Un);ao.formatPrefix=function(n,t){var e=0;return(n=+n)&&(0>n&&(n*=-1),t&&(n=ao.round(n,Pn(n,t))),e=1+Math.floor(1e-12+Math.log(n)/Math.LN10),e=Math.max(-24,Math.min(24,3*Math.floor((e-1)/3)))),sa[8+e/3]};var ha=/(?:([^{])?([<>=^]))?([+\- ])?([$#])?(0)?(\d+)?(,)?(\.-?\d+)?([a-z%])?/i,pa=ao.map({b:function(n){return n.toString(2)},c:function(n){return String.fromCharCode(n)},o:function(n){return n.toString(8)},x:function(n){return n.toString(16)},X:function(n){return n.toString(16).toUpperCase()},g:function(n,t){return n.toPrecision(t)},e:function(n,t){return n.toExponential(t)},f:function(n,t){return n.toFixed(t)},r:function(n,t){return(n=ao.round(n,Pn(n,t))).toFixed(Math.max(0,Math.min(20,Pn(n*(1+1e-15),t))))}}),ga=ao.time={},va=Date;Hn.prototype={getDate:function(){return this._.getUTCDate()},getDay:function(){return this._.getUTCDay()},getFullYear:function(){return this._.getUTCFullYear()},getHours:function(){return this._.getUTCHours()},getMilliseconds:function(){return this._.getUTCMilliseconds()},getMinutes:function(){return this._.getUTCMinutes()},getMonth:function(){return this._.getUTCMonth()},getSeconds:function(){return this._.getUTCSeconds()},getTime:function(){return this._.getTime()},getTimezoneOffset:function(){return 0},valueOf:function(){return this._.valueOf()},setDate:function(){da.setUTCDate.apply(this._,arguments)},setDay:function(){da.setUTCDay.apply(this._,arguments)},setFullYear:function(){da.setUTCFullYear.apply(this._,arguments)},setHours:function(){da.setUTCHours.apply(this._,arguments)},setMilliseconds:function(){da.setUTCMilliseconds.apply(this._,arguments)},setMinutes:function(){da.setUTCMinutes.apply(this._,arguments)},setMonth:function(){da.setUTCMonth.apply(this._,arguments)},setSeconds:function(){da.setUTCSeconds.apply(this._,arguments)},setTime:function(){da.setTime.apply(this._,arguments)}};var da=Date.prototype;ga.year=On(function(n){return n=ga.day(n),n.setMonth(0,1),n},function(n,t){n.setFullYear(n.getFullYear()+t)},function(n){return n.getFullYear()}),ga.years=ga.year.range,ga.years.utc=ga.year.utc.range,ga.day=On(function(n){var t=new va(2e3,0);return t.setFullYear(n.getFullYear(),n.getMonth(),n.getDate()),t},function(n,t){n.setDate(n.getDate()+t)},function(n){return n.getDate()-1}),ga.days=ga.day.range,ga.days.utc=ga.day.utc.range,ga.dayOfYear=function(n){var t=ga.year(n);return Math.floor((n-t-6e4*(n.getTimezoneOffset()-t.getTimezoneOffset()))/864e5)},["sunday","monday","tuesday","wednesday","thursday","friday","saturday"].forEach(function(n,t){t=7-t;var e=ga[n]=On(function(n){return(n=ga.day(n)).setDate(n.getDate()-(n.getDay()+t)%7),n},function(n,t){n.setDate(n.getDate()+7*Math.floor(t))},function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)-(e!==t)});ga[n+"s"]=e.range,ga[n+"s"].utc=e.utc.range,ga[n+"OfYear"]=function(n){var e=ga.year(n).getDay();return Math.floor((ga.dayOfYear(n)+(e+t)%7)/7)}}),ga.week=ga.sunday,ga.weeks=ga.sunday.range,ga.weeks.utc=ga.sunday.utc.range,ga.weekOfYear=ga.sundayOfYear;var ya={"-":"",_:" ",0:"0"},ma=/^\s*\d+/,Ma=/^%/;ao.locale=function(n){return{numberFormat:jn(n),timeFormat:Yn(n)}};var xa=ao.locale({decimal:".",thousands:",",grouping:[3],currency:["$",""],dateTime:"%a %b %e %X %Y",date:"%m/%d/%Y",time:"%H:%M:%S",periods:["AM","PM"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"], +shortDays:["Sun","Mon","Tue","Wed","Thu","Fri","Sat"],months:["January","February","March","April","May","June","July","August","September","October","November","December"],shortMonths:["Jan","Feb","Mar","Apr","May","Jun","Jul","Aug","Sep","Oct","Nov","Dec"]});ao.format=xa.numberFormat,ao.geo={},ft.prototype={s:0,t:0,add:function(n){st(n,this.t,ba),st(ba.s,this.s,this),this.s?this.t+=ba.t:this.s=ba.t},reset:function(){this.s=this.t=0},valueOf:function(){return this.s}};var ba=new ft;ao.geo.stream=function(n,t){n&&_a.hasOwnProperty(n.type)?_a[n.type](n,t):ht(n,t)};var _a={Feature:function(n,t){ht(n.geometry,t)},FeatureCollection:function(n,t){for(var e=n.features,r=-1,i=e.length;++rn?4*Fo+n:n,Na.lineStart=Na.lineEnd=Na.point=b}};ao.geo.bounds=function(){function n(n,t){M.push(x=[f=n,h=n]),s>t&&(s=t),t>p&&(p=t)}function t(t,e){var r=dt([t*Yo,e*Yo]);if(y){var i=mt(y,r),u=[i[1],-i[0],0],o=mt(u,i);bt(o),o=_t(o);var l=t-g,c=l>0?1:-1,v=o[0]*Zo*c,d=xo(l)>180;if(d^(v>c*g&&c*t>v)){var m=o[1]*Zo;m>p&&(p=m)}else if(v=(v+360)%360-180,d^(v>c*g&&c*t>v)){var m=-o[1]*Zo;s>m&&(s=m)}else s>e&&(s=e),e>p&&(p=e);d?g>t?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t):h>=f?(f>t&&(f=t),t>h&&(h=t)):t>g?a(f,t)>a(f,h)&&(h=t):a(t,h)>a(f,h)&&(f=t)}else n(t,e);y=r,g=t}function e(){b.point=t}function r(){x[0]=f,x[1]=h,b.point=n,y=null}function i(n,e){if(y){var r=n-g;m+=xo(r)>180?r+(r>0?360:-360):r}else v=n,d=e;Na.point(n,e),t(n,e)}function u(){Na.lineStart()}function o(){i(v,d),Na.lineEnd(),xo(m)>Uo&&(f=-(h=180)),x[0]=f,x[1]=h,y=null}function a(n,t){return(t-=n)<0?t+360:t}function l(n,t){return n[0]-t[0]}function c(n,t){return t[0]<=t[1]?t[0]<=n&&n<=t[1]:nka?(f=-(h=180),s=-(p=90)):m>Uo?p=90:-Uo>m&&(s=-90),x[0]=f,x[1]=h}};return function(n){p=h=-(f=s=1/0),M=[],ao.geo.stream(n,b);var t=M.length;if(t){M.sort(l);for(var e,r=1,i=M[0],u=[i];t>r;++r)e=M[r],c(e[0],i)||c(e[1],i)?(a(i[0],e[1])>a(i[0],i[1])&&(i[1]=e[1]),a(e[0],i[1])>a(i[0],i[1])&&(i[0]=e[0])):u.push(i=e);for(var o,e,g=-(1/0),t=u.length-1,r=0,i=u[t];t>=r;i=e,++r)e=u[r],(o=a(i[1],e[0]))>g&&(g=o,f=e[0],h=i[1])}return M=x=null,f===1/0||s===1/0?[[NaN,NaN],[NaN,NaN]]:[[f,s],[h,p]]}}(),ao.geo.centroid=function(n){Ea=Aa=Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,ja);var t=Da,e=Pa,r=Ua,i=t*t+e*e+r*r;return jo>i&&(t=qa,e=Ta,r=Ra,Uo>Aa&&(t=Ca,e=za,r=La),i=t*t+e*e+r*r,jo>i)?[NaN,NaN]:[Math.atan2(e,t)*Zo,tn(r/Math.sqrt(i))*Zo]};var Ea,Aa,Ca,za,La,qa,Ta,Ra,Da,Pa,Ua,ja={sphere:b,point:St,lineStart:Nt,lineEnd:Et,polygonStart:function(){ja.lineStart=At},polygonEnd:function(){ja.lineStart=Nt}},Fa=Rt(zt,jt,Ht,[-Fo,-Fo/2]),Ha=1e9;ao.geo.clipExtent=function(){var n,t,e,r,i,u,o={stream:function(n){return i&&(i.valid=!1),i=u(n),i.valid=!0,i},extent:function(a){return arguments.length?(u=Zt(n=+a[0][0],t=+a[0][1],e=+a[1][0],r=+a[1][1]),i&&(i.valid=!1,i=null),o):[[n,t],[e,r]]}};return o.extent([[0,0],[960,500]])},(ao.geo.conicEqualArea=function(){return Vt(Xt)}).raw=Xt,ao.geo.albers=function(){return ao.geo.conicEqualArea().rotate([96,0]).center([-.6,38.7]).parallels([29.5,45.5]).scale(1070)},ao.geo.albersUsa=function(){function n(n){var u=n[0],o=n[1];return t=null,e(u,o),t||(r(u,o),t)||i(u,o),t}var t,e,r,i,u=ao.geo.albers(),o=ao.geo.conicEqualArea().rotate([154,0]).center([-2,58.5]).parallels([55,65]),a=ao.geo.conicEqualArea().rotate([157,0]).center([-3,19.9]).parallels([8,18]),l={point:function(n,e){t=[n,e]}};return n.invert=function(n){var t=u.scale(),e=u.translate(),r=(n[0]-e[0])/t,i=(n[1]-e[1])/t;return(i>=.12&&.234>i&&r>=-.425&&-.214>r?o:i>=.166&&.234>i&&r>=-.214&&-.115>r?a:u).invert(n)},n.stream=function(n){var t=u.stream(n),e=o.stream(n),r=a.stream(n);return{point:function(n,i){t.point(n,i),e.point(n,i),r.point(n,i)},sphere:function(){t.sphere(),e.sphere(),r.sphere()},lineStart:function(){t.lineStart(),e.lineStart(),r.lineStart()},lineEnd:function(){t.lineEnd(),e.lineEnd(),r.lineEnd()},polygonStart:function(){t.polygonStart(),e.polygonStart(),r.polygonStart()},polygonEnd:function(){t.polygonEnd(),e.polygonEnd(),r.polygonEnd()}}},n.precision=function(t){return arguments.length?(u.precision(t),o.precision(t),a.precision(t),n):u.precision()},n.scale=function(t){return arguments.length?(u.scale(t),o.scale(.35*t),a.scale(t),n.translate(u.translate())):u.scale()},n.translate=function(t){if(!arguments.length)return u.translate();var c=u.scale(),f=+t[0],s=+t[1];return e=u.translate(t).clipExtent([[f-.455*c,s-.238*c],[f+.455*c,s+.238*c]]).stream(l).point,r=o.translate([f-.307*c,s+.201*c]).clipExtent([[f-.425*c+Uo,s+.12*c+Uo],[f-.214*c-Uo,s+.234*c-Uo]]).stream(l).point,i=a.translate([f-.205*c,s+.212*c]).clipExtent([[f-.214*c+Uo,s+.166*c+Uo],[f-.115*c-Uo,s+.234*c-Uo]]).stream(l).point,n},n.scale(1070)};var Oa,Ia,Ya,Za,Va,Xa,$a={point:b,lineStart:b,lineEnd:b,polygonStart:function(){Ia=0,$a.lineStart=$t},polygonEnd:function(){$a.lineStart=$a.lineEnd=$a.point=b,Oa+=xo(Ia/2)}},Ba={point:Bt,lineStart:b,lineEnd:b,polygonStart:b,polygonEnd:b},Wa={point:Gt,lineStart:Kt,lineEnd:Qt,polygonStart:function(){Wa.lineStart=ne},polygonEnd:function(){Wa.point=Gt,Wa.lineStart=Kt,Wa.lineEnd=Qt}};ao.geo.path=function(){function n(n){return n&&("function"==typeof a&&u.pointRadius(+a.apply(this,arguments)),o&&o.valid||(o=i(u)),ao.geo.stream(n,o)),u.result()}function t(){return o=null,n}var e,r,i,u,o,a=4.5;return n.area=function(n){return Oa=0,ao.geo.stream(n,i($a)),Oa},n.centroid=function(n){return Ca=za=La=qa=Ta=Ra=Da=Pa=Ua=0,ao.geo.stream(n,i(Wa)),Ua?[Da/Ua,Pa/Ua]:Ra?[qa/Ra,Ta/Ra]:La?[Ca/La,za/La]:[NaN,NaN]},n.bounds=function(n){return Va=Xa=-(Ya=Za=1/0),ao.geo.stream(n,i(Ba)),[[Ya,Za],[Va,Xa]]},n.projection=function(n){return arguments.length?(i=(e=n)?n.stream||re(n):m,t()):e},n.context=function(n){return arguments.length?(u=null==(r=n)?new Wt:new te(n),"function"!=typeof a&&u.pointRadius(a),t()):r},n.pointRadius=function(t){return arguments.length?(a="function"==typeof t?t:(u.pointRadius(+t),+t),n):a},n.projection(ao.geo.albersUsa()).context(null)},ao.geo.transform=function(n){return{stream:function(t){var e=new ie(t);for(var r in n)e[r]=n[r];return e}}},ie.prototype={point:function(n,t){this.stream.point(n,t)},sphere:function(){this.stream.sphere()},lineStart:function(){this.stream.lineStart()},lineEnd:function(){this.stream.lineEnd()},polygonStart:function(){this.stream.polygonStart()},polygonEnd:function(){this.stream.polygonEnd()}},ao.geo.projection=oe,ao.geo.projectionMutator=ae,(ao.geo.equirectangular=function(){return oe(ce)}).raw=ce.invert=ce,ao.geo.rotation=function(n){function t(t){return t=n(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t}return n=se(n[0]%360*Yo,n[1]*Yo,n.length>2?n[2]*Yo:0),t.invert=function(t){return t=n.invert(t[0]*Yo,t[1]*Yo),t[0]*=Zo,t[1]*=Zo,t},t},fe.invert=ce,ao.geo.circle=function(){function n(){var n="function"==typeof r?r.apply(this,arguments):r,t=se(-n[0]*Yo,-n[1]*Yo,0).invert,i=[];return e(null,null,1,{point:function(n,e){i.push(n=t(n,e)),n[0]*=Zo,n[1]*=Zo}}),{type:"Polygon",coordinates:[i]}}var t,e,r=[0,0],i=6;return n.origin=function(t){return arguments.length?(r=t,n):r},n.angle=function(r){return arguments.length?(e=ve((t=+r)*Yo,i*Yo),n):t},n.precision=function(r){return arguments.length?(e=ve(t*Yo,(i=+r)*Yo),n):i},n.angle(90)},ao.geo.distance=function(n,t){var e,r=(t[0]-n[0])*Yo,i=n[1]*Yo,u=t[1]*Yo,o=Math.sin(r),a=Math.cos(r),l=Math.sin(i),c=Math.cos(i),f=Math.sin(u),s=Math.cos(u);return Math.atan2(Math.sqrt((e=s*o)*e+(e=c*f-l*s*a)*e),l*f+c*s*a)},ao.geo.graticule=function(){function n(){return{type:"MultiLineString",coordinates:t()}}function t(){return ao.range(Math.ceil(u/d)*d,i,d).map(h).concat(ao.range(Math.ceil(c/y)*y,l,y).map(p)).concat(ao.range(Math.ceil(r/g)*g,e,g).filter(function(n){return xo(n%d)>Uo}).map(f)).concat(ao.range(Math.ceil(a/v)*v,o,v).filter(function(n){return xo(n%y)>Uo}).map(s))}var e,r,i,u,o,a,l,c,f,s,h,p,g=10,v=g,d=90,y=360,m=2.5;return n.lines=function(){return t().map(function(n){return{type:"LineString",coordinates:n}})},n.outline=function(){return{type:"Polygon",coordinates:[h(u).concat(p(l).slice(1),h(i).reverse().slice(1),p(c).reverse().slice(1))]}},n.extent=function(t){return arguments.length?n.majorExtent(t).minorExtent(t):n.minorExtent()},n.majorExtent=function(t){return arguments.length?(u=+t[0][0],i=+t[1][0],c=+t[0][1],l=+t[1][1],u>i&&(t=u,u=i,i=t),c>l&&(t=c,c=l,l=t),n.precision(m)):[[u,c],[i,l]]},n.minorExtent=function(t){return arguments.length?(r=+t[0][0],e=+t[1][0],a=+t[0][1],o=+t[1][1],r>e&&(t=r,r=e,e=t),a>o&&(t=a,a=o,o=t),n.precision(m)):[[r,a],[e,o]]},n.step=function(t){return arguments.length?n.majorStep(t).minorStep(t):n.minorStep()},n.majorStep=function(t){return arguments.length?(d=+t[0],y=+t[1],n):[d,y]},n.minorStep=function(t){return arguments.length?(g=+t[0],v=+t[1],n):[g,v]},n.precision=function(t){return arguments.length?(m=+t,f=ye(a,o,90),s=me(r,e,m),h=ye(c,l,90),p=me(u,i,m),n):m},n.majorExtent([[-180,-90+Uo],[180,90-Uo]]).minorExtent([[-180,-80-Uo],[180,80+Uo]])},ao.geo.greatArc=function(){function n(){return{type:"LineString",coordinates:[t||r.apply(this,arguments),e||i.apply(this,arguments)]}}var t,e,r=Me,i=xe;return n.distance=function(){return ao.geo.distance(t||r.apply(this,arguments),e||i.apply(this,arguments))},n.source=function(e){return arguments.length?(r=e,t="function"==typeof e?null:e,n):r},n.target=function(t){return arguments.length?(i=t,e="function"==typeof t?null:t,n):i},n.precision=function(){return arguments.length?n:0},n},ao.geo.interpolate=function(n,t){return be(n[0]*Yo,n[1]*Yo,t[0]*Yo,t[1]*Yo)},ao.geo.length=function(n){return Ja=0,ao.geo.stream(n,Ga),Ja};var Ja,Ga={sphere:b,point:b,lineStart:_e,lineEnd:b,polygonStart:b,polygonEnd:b},Ka=we(function(n){return Math.sqrt(2/(1+n))},function(n){return 2*Math.asin(n/2)});(ao.geo.azimuthalEqualArea=function(){return oe(Ka)}).raw=Ka;var Qa=we(function(n){var t=Math.acos(n);return t&&t/Math.sin(t)},m);(ao.geo.azimuthalEquidistant=function(){return oe(Qa)}).raw=Qa,(ao.geo.conicConformal=function(){return Vt(Se)}).raw=Se,(ao.geo.conicEquidistant=function(){return Vt(ke)}).raw=ke;var nl=we(function(n){return 1/n},Math.atan);(ao.geo.gnomonic=function(){return oe(nl)}).raw=nl,Ne.invert=function(n,t){return[n,2*Math.atan(Math.exp(t))-Io]},(ao.geo.mercator=function(){return Ee(Ne)}).raw=Ne;var tl=we(function(){return 1},Math.asin);(ao.geo.orthographic=function(){return oe(tl)}).raw=tl;var el=we(function(n){return 1/(1+n)},function(n){return 2*Math.atan(n)});(ao.geo.stereographic=function(){return oe(el)}).raw=el,Ae.invert=function(n,t){return[-t,2*Math.atan(Math.exp(n))-Io]},(ao.geo.transverseMercator=function(){var n=Ee(Ae),t=n.center,e=n.rotate;return n.center=function(n){return n?t([-n[1],n[0]]):(n=t(),[n[1],-n[0]])},n.rotate=function(n){return n?e([n[0],n[1],n.length>2?n[2]+90:90]):(n=e(),[n[0],n[1],n[2]-90])},e([0,0,90])}).raw=Ae,ao.geom={},ao.geom.hull=function(n){function t(n){if(n.length<3)return[];var t,i=En(e),u=En(r),o=n.length,a=[],l=[];for(t=0;o>t;t++)a.push([+i.call(this,n[t],t),+u.call(this,n[t],t),t]);for(a.sort(qe),t=0;o>t;t++)l.push([a[t][0],-a[t][1]]);var c=Le(a),f=Le(l),s=f[0]===c[0],h=f[f.length-1]===c[c.length-1],p=[];for(t=c.length-1;t>=0;--t)p.push(n[a[c[t]][2]]);for(t=+s;t=r&&c.x<=u&&c.y>=i&&c.y<=o?[[r,o],[u,o],[u,i],[r,i]]:[];f.point=n[a]}),t}function e(n){return n.map(function(n,t){return{x:Math.round(u(n,t)/Uo)*Uo,y:Math.round(o(n,t)/Uo)*Uo,i:t}})}var r=Ce,i=ze,u=r,o=i,a=sl;return n?t(n):(t.links=function(n){return ar(e(n)).edges.filter(function(n){return n.l&&n.r}).map(function(t){return{source:n[t.l.i],target:n[t.r.i]}})},t.triangles=function(n){var t=[];return ar(e(n)).cells.forEach(function(e,r){for(var i,u,o=e.site,a=e.edges.sort(Ve),l=-1,c=a.length,f=a[c-1].edge,s=f.l===o?f.r:f.l;++l=c,h=r>=f,p=h<<1|s;n.leaf=!1,n=n.nodes[p]||(n.nodes[p]=hr()),s?i=c:a=c,h?o=f:l=f,u(n,t,e,r,i,o,a,l)}var f,s,h,p,g,v,d,y,m,M=En(a),x=En(l);if(null!=t)v=t,d=e,y=r,m=i;else if(y=m=-(v=d=1/0),s=[],h=[],g=n.length,o)for(p=0;g>p;++p)f=n[p],f.xy&&(y=f.x),f.y>m&&(m=f.y),s.push(f.x),h.push(f.y);else for(p=0;g>p;++p){var b=+M(f=n[p],p),_=+x(f,p);v>b&&(v=b),d>_&&(d=_),b>y&&(y=b),_>m&&(m=_),s.push(b),h.push(_)}var w=y-v,S=m-d;w>S?m=d+w:y=v+S;var k=hr();if(k.add=function(n){u(k,n,+M(n,++p),+x(n,p),v,d,y,m)},k.visit=function(n){pr(n,k,v,d,y,m)},k.find=function(n){return gr(k,n[0],n[1],v,d,y,m)},p=-1,null==t){for(;++p=0?n.slice(0,t):n,r=t>=0?n.slice(t+1):"in";return e=vl.get(e)||gl,r=dl.get(r)||m,br(r(e.apply(null,lo.call(arguments,1))))},ao.interpolateHcl=Rr,ao.interpolateHsl=Dr,ao.interpolateLab=Pr,ao.interpolateRound=Ur,ao.transform=function(n){var t=fo.createElementNS(ao.ns.prefix.svg,"g");return(ao.transform=function(n){if(null!=n){t.setAttribute("transform",n);var e=t.transform.baseVal.consolidate()}return new jr(e?e.matrix:yl)})(n)},jr.prototype.toString=function(){return"translate("+this.translate+")rotate("+this.rotate+")skewX("+this.skew+")scale("+this.scale+")"};var yl={a:1,b:0,c:0,d:1,e:0,f:0};ao.interpolateTransform=$r,ao.layout={},ao.layout.bundle=function(){return function(n){for(var t=[],e=-1,r=n.length;++ea*a/y){if(v>l){var c=t.charge/l;n.px-=u*c,n.py-=o*c}return!0}if(t.point&&l&&v>l){var c=t.pointCharge/l;n.px-=u*c,n.py-=o*c}}return!t.charge}}function t(n){n.px=ao.event.x,n.py=ao.event.y,l.resume()}var e,r,i,u,o,a,l={},c=ao.dispatch("start","tick","end"),f=[1,1],s=.9,h=ml,p=Ml,g=-30,v=xl,d=.1,y=.64,M=[],x=[];return l.tick=function(){if((i*=.99)<.005)return e=null,c.end({type:"end",alpha:i=0}),!0;var t,r,l,h,p,v,y,m,b,_=M.length,w=x.length;for(r=0;w>r;++r)l=x[r],h=l.source,p=l.target,m=p.x-h.x,b=p.y-h.y,(v=m*m+b*b)&&(v=i*o[r]*((v=Math.sqrt(v))-u[r])/v,m*=v,b*=v,p.x-=m*(y=h.weight+p.weight?h.weight/(h.weight+p.weight):.5),p.y-=b*y,h.x+=m*(y=1-y),h.y+=b*y);if((y=i*d)&&(m=f[0]/2,b=f[1]/2,r=-1,y))for(;++r<_;)l=M[r],l.x+=(m-l.x)*y,l.y+=(b-l.y)*y;if(g)for(ri(t=ao.geom.quadtree(M),i,a),r=-1;++r<_;)(l=M[r]).fixed||t.visit(n(l));for(r=-1;++r<_;)l=M[r],l.fixed?(l.x=l.px,l.y=l.py):(l.x-=(l.px-(l.px=l.x))*s,l.y-=(l.py-(l.py=l.y))*s);c.tick({type:"tick",alpha:i})},l.nodes=function(n){return arguments.length?(M=n,l):M},l.links=function(n){return arguments.length?(x=n,l):x},l.size=function(n){return arguments.length?(f=n,l):f},l.linkDistance=function(n){return arguments.length?(h="function"==typeof n?n:+n,l):h},l.distance=l.linkDistance,l.linkStrength=function(n){return arguments.length?(p="function"==typeof n?n:+n,l):p},l.friction=function(n){return arguments.length?(s=+n,l):s},l.charge=function(n){return arguments.length?(g="function"==typeof n?n:+n,l):g},l.chargeDistance=function(n){return arguments.length?(v=n*n,l):Math.sqrt(v)},l.gravity=function(n){return arguments.length?(d=+n,l):d},l.theta=function(n){return arguments.length?(y=n*n,l):Math.sqrt(y)},l.alpha=function(n){return arguments.length?(n=+n,i?n>0?i=n:(e.c=null,e.t=NaN,e=null,c.end({type:"end",alpha:i=0})):n>0&&(c.start({type:"start",alpha:i=n}),e=qn(l.tick)),l):i},l.start=function(){function n(n,r){if(!e){for(e=new Array(i),l=0;i>l;++l)e[l]=[];for(l=0;c>l;++l){var u=x[l];e[u.source.index].push(u.target),e[u.target.index].push(u.source)}}for(var o,a=e[t],l=-1,f=a.length;++lt;++t)(r=M[t]).index=t,r.weight=0;for(t=0;c>t;++t)r=x[t],"number"==typeof r.source&&(r.source=M[r.source]),"number"==typeof r.target&&(r.target=M[r.target]),++r.source.weight,++r.target.weight;for(t=0;i>t;++t)r=M[t],isNaN(r.x)&&(r.x=n("x",s)),isNaN(r.y)&&(r.y=n("y",v)),isNaN(r.px)&&(r.px=r.x),isNaN(r.py)&&(r.py=r.y);if(u=[],"function"==typeof h)for(t=0;c>t;++t)u[t]=+h.call(this,x[t],t);else for(t=0;c>t;++t)u[t]=h;if(o=[],"function"==typeof p)for(t=0;c>t;++t)o[t]=+p.call(this,x[t],t);else for(t=0;c>t;++t)o[t]=p;if(a=[],"function"==typeof g)for(t=0;i>t;++t)a[t]=+g.call(this,M[t],t);else for(t=0;i>t;++t)a[t]=g;return l.resume()},l.resume=function(){return l.alpha(.1)},l.stop=function(){return l.alpha(0)},l.drag=function(){return r||(r=ao.behavior.drag().origin(m).on("dragstart.force",Qr).on("drag.force",t).on("dragend.force",ni)),arguments.length?void this.on("mouseover.force",ti).on("mouseout.force",ei).call(r):r},ao.rebind(l,c,"on")};var ml=20,Ml=1,xl=1/0;ao.layout.hierarchy=function(){function n(i){var u,o=[i],a=[];for(i.depth=0;null!=(u=o.pop());)if(a.push(u),(c=e.call(n,u,u.depth))&&(l=c.length)){for(var l,c,f;--l>=0;)o.push(f=c[l]),f.parent=u,f.depth=u.depth+1;r&&(u.value=0),u.children=c}else r&&(u.value=+r.call(n,u,u.depth)||0),delete u.children;return oi(i,function(n){var e,i;t&&(e=n.children)&&e.sort(t),r&&(i=n.parent)&&(i.value+=n.value)}),a}var t=ci,e=ai,r=li;return n.sort=function(e){return arguments.length?(t=e,n):t},n.children=function(t){return arguments.length?(e=t,n):e},n.value=function(t){return arguments.length?(r=t,n):r},n.revalue=function(t){return r&&(ui(t,function(n){n.children&&(n.value=0)}),oi(t,function(t){var e;t.children||(t.value=+r.call(n,t,t.depth)||0),(e=t.parent)&&(e.value+=t.value)})),t},n},ao.layout.partition=function(){function n(t,e,r,i){var u=t.children;if(t.x=e,t.y=t.depth*i,t.dx=r,t.dy=i,u&&(o=u.length)){var o,a,l,c=-1;for(r=t.value?r/t.value:0;++cs?-1:1),g=ao.sum(c),v=g?(s-l*p)/g:0,d=ao.range(l),y=[];return null!=e&&d.sort(e===bl?function(n,t){return c[t]-c[n]}:function(n,t){return e(o[n],o[t])}),d.forEach(function(n){y[n]={data:o[n],value:a=c[n],startAngle:f,endAngle:f+=a*v+p,padAngle:h}}),y}var t=Number,e=bl,r=0,i=Ho,u=0;return n.value=function(e){return arguments.length?(t=e,n):t},n.sort=function(t){return arguments.length?(e=t,n):e},n.startAngle=function(t){return arguments.length?(r=t,n):r},n.endAngle=function(t){return arguments.length?(i=t,n):i},n.padAngle=function(t){return arguments.length?(u=t,n):u},n};var bl={};ao.layout.stack=function(){function n(a,l){if(!(h=a.length))return a;var c=a.map(function(e,r){return t.call(n,e,r)}),f=c.map(function(t){return t.map(function(t,e){return[u.call(n,t,e),o.call(n,t,e)]})}),s=e.call(n,f,l);c=ao.permute(c,s),f=ao.permute(f,s);var h,p,g,v,d=r.call(n,f,l),y=c[0].length;for(g=0;y>g;++g)for(i.call(n,c[0][g],v=d[g],f[0][g][1]),p=1;h>p;++p)i.call(n,c[p][g],v+=f[p-1][g][1],f[p][g][1]);return a}var t=m,e=gi,r=vi,i=pi,u=si,o=hi;return n.values=function(e){return arguments.length?(t=e,n):t},n.order=function(t){return arguments.length?(e="function"==typeof t?t:_l.get(t)||gi,n):e},n.offset=function(t){return arguments.length?(r="function"==typeof t?t:wl.get(t)||vi,n):r},n.x=function(t){return arguments.length?(u=t,n):u},n.y=function(t){return arguments.length?(o=t,n):o},n.out=function(t){return arguments.length?(i=t,n):i},n};var _l=ao.map({"inside-out":function(n){var t,e,r=n.length,i=n.map(di),u=n.map(yi),o=ao.range(r).sort(function(n,t){return i[n]-i[t]}),a=0,l=0,c=[],f=[];for(t=0;r>t;++t)e=o[t],l>a?(a+=u[e],c.push(e)):(l+=u[e],f.push(e));return f.reverse().concat(c)},reverse:function(n){return ao.range(n.length).reverse()},"default":gi}),wl=ao.map({silhouette:function(n){var t,e,r,i=n.length,u=n[0].length,o=[],a=0,l=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];r>a&&(a=r),o.push(r)}for(e=0;u>e;++e)l[e]=(a-o[e])/2;return l},wiggle:function(n){var t,e,r,i,u,o,a,l,c,f=n.length,s=n[0],h=s.length,p=[];for(p[0]=l=c=0,e=1;h>e;++e){for(t=0,i=0;f>t;++t)i+=n[t][e][1];for(t=0,u=0,a=s[e][0]-s[e-1][0];f>t;++t){for(r=0,o=(n[t][e][1]-n[t][e-1][1])/(2*a);t>r;++r)o+=(n[r][e][1]-n[r][e-1][1])/a;u+=o*n[t][e][1]}p[e]=l-=i?u/i*a:0,c>l&&(c=l)}for(e=0;h>e;++e)p[e]-=c;return p},expand:function(n){var t,e,r,i=n.length,u=n[0].length,o=1/i,a=[];for(e=0;u>e;++e){for(t=0,r=0;i>t;t++)r+=n[t][e][1];if(r)for(t=0;i>t;t++)n[t][e][1]/=r;else for(t=0;i>t;t++)n[t][e][1]=o}for(e=0;u>e;++e)a[e]=0;return a},zero:vi});ao.layout.histogram=function(){function n(n,u){for(var o,a,l=[],c=n.map(e,this),f=r.call(this,c,u),s=i.call(this,f,c,u),u=-1,h=c.length,p=s.length-1,g=t?1:1/h;++u0)for(u=-1;++u=f[0]&&a<=f[1]&&(o=l[ao.bisect(s,a,1,p)-1],o.y+=g,o.push(n[u]));return l}var t=!0,e=Number,r=bi,i=Mi;return n.value=function(t){return arguments.length?(e=t,n):e},n.range=function(t){return arguments.length?(r=En(t),n):r},n.bins=function(t){return arguments.length?(i="number"==typeof t?function(n){return xi(n,t)}:En(t),n):i},n.frequency=function(e){return arguments.length?(t=!!e,n):t},n},ao.layout.pack=function(){function n(n,u){var o=e.call(this,n,u),a=o[0],l=i[0],c=i[1],f=null==t?Math.sqrt:"function"==typeof t?t:function(){return t};if(a.x=a.y=0,oi(a,function(n){n.r=+f(n.value)}),oi(a,Ni),r){var s=r*(t?1:Math.max(2*a.r/l,2*a.r/c))/2;oi(a,function(n){n.r+=s}),oi(a,Ni),oi(a,function(n){n.r-=s})}return Ci(a,l/2,c/2,t?1:1/Math.max(2*a.r/l,2*a.r/c)),o}var t,e=ao.layout.hierarchy().sort(_i),r=0,i=[1,1];return n.size=function(t){return arguments.length?(i=t,n):i},n.radius=function(e){return arguments.length?(t=null==e||"function"==typeof e?e:+e,n):t},n.padding=function(t){return arguments.length?(r=+t,n):r},ii(n,e)},ao.layout.tree=function(){function n(n,i){var f=o.call(this,n,i),s=f[0],h=t(s);if(oi(h,e),h.parent.m=-h.z,ui(h,r),c)ui(s,u);else{var p=s,g=s,v=s;ui(s,function(n){n.xg.x&&(g=n),n.depth>v.depth&&(v=n)});var d=a(p,g)/2-p.x,y=l[0]/(g.x+a(g,p)/2+d),m=l[1]/(v.depth||1);ui(s,function(n){n.x=(n.x+d)*y,n.y=n.depth*m})}return f}function t(n){for(var t,e={A:null,children:[n]},r=[e];null!=(t=r.pop());)for(var i,u=t.children,o=0,a=u.length;a>o;++o)r.push((u[o]=i={_:u[o],parent:t,children:(i=u[o].children)&&i.slice()||[],A:null,a:null,z:0,m:0,c:0,s:0,t:null,i:o}).a=i);return e.children[0]}function e(n){var t=n.children,e=n.parent.children,r=n.i?e[n.i-1]:null;if(t.length){Di(n);var u=(t[0].z+t[t.length-1].z)/2;r?(n.z=r.z+a(n._,r._),n.m=n.z-u):n.z=u}else r&&(n.z=r.z+a(n._,r._));n.parent.A=i(n,r,n.parent.A||e[0])}function r(n){n._.x=n.z+n.parent.m,n.m+=n.parent.m}function i(n,t,e){if(t){for(var r,i=n,u=n,o=t,l=i.parent.children[0],c=i.m,f=u.m,s=o.m,h=l.m;o=Ti(o),i=qi(i),o&&i;)l=qi(l),u=Ti(u),u.a=n,r=o.z+s-i.z-c+a(o._,i._),r>0&&(Ri(Pi(o,n,e),n,r),c+=r,f+=r),s+=o.m,c+=i.m,h+=l.m,f+=u.m;o&&!Ti(u)&&(u.t=o,u.m+=s-f),i&&!qi(l)&&(l.t=i,l.m+=c-h,e=n)}return e}function u(n){n.x*=l[0],n.y=n.depth*l[1]}var o=ao.layout.hierarchy().sort(null).value(null),a=Li,l=[1,1],c=null;return n.separation=function(t){return arguments.length?(a=t,n):a},n.size=function(t){return arguments.length?(c=null==(l=t)?u:null,n):c?null:l},n.nodeSize=function(t){return arguments.length?(c=null==(l=t)?null:u,n):c?l:null},ii(n,o)},ao.layout.cluster=function(){function n(n,u){var o,a=t.call(this,n,u),l=a[0],c=0;oi(l,function(n){var t=n.children;t&&t.length?(n.x=ji(t),n.y=Ui(t)):(n.x=o?c+=e(n,o):0,n.y=0,o=n)});var f=Fi(l),s=Hi(l),h=f.x-e(f,s)/2,p=s.x+e(s,f)/2;return oi(l,i?function(n){n.x=(n.x-l.x)*r[0],n.y=(l.y-n.y)*r[1]}:function(n){n.x=(n.x-h)/(p-h)*r[0],n.y=(1-(l.y?n.y/l.y:1))*r[1]}),a}var t=ao.layout.hierarchy().sort(null).value(null),e=Li,r=[1,1],i=!1;return n.separation=function(t){return arguments.length?(e=t,n):e},n.size=function(t){return arguments.length?(i=null==(r=t),n):i?null:r},n.nodeSize=function(t){return arguments.length?(i=null!=(r=t),n):i?r:null},ii(n,t)},ao.layout.treemap=function(){function n(n,t){for(var e,r,i=-1,u=n.length;++it?0:t),e.area=isNaN(r)||0>=r?0:r}function t(e){var u=e.children;if(u&&u.length){var o,a,l,c=s(e),f=[],h=u.slice(),g=1/0,v="slice"===p?c.dx:"dice"===p?c.dy:"slice-dice"===p?1&e.depth?c.dy:c.dx:Math.min(c.dx,c.dy);for(n(h,c.dx*c.dy/e.value),f.area=0;(l=h.length)>0;)f.push(o=h[l-1]),f.area+=o.area,"squarify"!==p||(a=r(f,v))<=g?(h.pop(),g=a):(f.area-=f.pop().area,i(f,v,c,!1),v=Math.min(c.dx,c.dy),f.length=f.area=0,g=1/0);f.length&&(i(f,v,c,!0),f.length=f.area=0),u.forEach(t)}}function e(t){var r=t.children;if(r&&r.length){var u,o=s(t),a=r.slice(),l=[];for(n(a,o.dx*o.dy/t.value),l.area=0;u=a.pop();)l.push(u),l.area+=u.area,null!=u.z&&(i(l,u.z?o.dx:o.dy,o,!a.length),l.length=l.area=0);r.forEach(e)}}function r(n,t){for(var e,r=n.area,i=0,u=1/0,o=-1,a=n.length;++oe&&(u=e),e>i&&(i=e));return r*=r,t*=t,r?Math.max(t*i*g/r,r/(t*u*g)):1/0}function i(n,t,e,r){var i,u=-1,o=n.length,a=e.x,c=e.y,f=t?l(n.area/t):0; +if(t==e.dx){for((r||f>e.dy)&&(f=e.dy);++ue.dx)&&(f=e.dx);++ue&&(t=1),1>e&&(n=0),function(){var e,r,i;do e=2*Math.random()-1,r=2*Math.random()-1,i=e*e+r*r;while(!i||i>1);return n+t*e*Math.sqrt(-2*Math.log(i)/i)}},logNormal:function(){var n=ao.random.normal.apply(ao,arguments);return function(){return Math.exp(n())}},bates:function(n){var t=ao.random.irwinHall(n);return function(){return t()/n}},irwinHall:function(n){return function(){for(var t=0,e=0;n>e;e++)t+=Math.random();return t}}},ao.scale={};var Sl={floor:m,ceil:m};ao.scale.linear=function(){return Wi([0,1],[0,1],Mr,!1)};var kl={s:1,g:1,p:1,r:1,e:1};ao.scale.log=function(){return ru(ao.scale.linear().domain([0,1]),10,!0,[1,10])};var Nl=ao.format(".0e"),El={floor:function(n){return-Math.ceil(-n)},ceil:function(n){return-Math.floor(-n)}};ao.scale.pow=function(){return iu(ao.scale.linear(),1,[0,1])},ao.scale.sqrt=function(){return ao.scale.pow().exponent(.5)},ao.scale.ordinal=function(){return ou([],{t:"range",a:[[]]})},ao.scale.category10=function(){return ao.scale.ordinal().range(Al)},ao.scale.category20=function(){return ao.scale.ordinal().range(Cl)},ao.scale.category20b=function(){return ao.scale.ordinal().range(zl)},ao.scale.category20c=function(){return ao.scale.ordinal().range(Ll)};var Al=[2062260,16744206,2924588,14034728,9725885,9197131,14907330,8355711,12369186,1556175].map(xn),Cl=[2062260,11454440,16744206,16759672,2924588,10018698,14034728,16750742,9725885,12955861,9197131,12885140,14907330,16234194,8355711,13092807,12369186,14408589,1556175,10410725].map(xn),zl=[3750777,5395619,7040719,10264286,6519097,9216594,11915115,13556636,9202993,12426809,15186514,15190932,8666169,11356490,14049643,15177372,8077683,10834324,13528509,14589654].map(xn),Ll=[3244733,7057110,10406625,13032431,15095053,16616764,16625259,16634018,3253076,7652470,10607003,13101504,7695281,10394312,12369372,14342891,6513507,9868950,12434877,14277081].map(xn);ao.scale.quantile=function(){return au([],[])},ao.scale.quantize=function(){return lu(0,1,[0,1])},ao.scale.threshold=function(){return cu([.5],[0,1])},ao.scale.identity=function(){return fu([0,1])},ao.svg={},ao.svg.arc=function(){function n(){var n=Math.max(0,+e.apply(this,arguments)),c=Math.max(0,+r.apply(this,arguments)),f=o.apply(this,arguments)-Io,s=a.apply(this,arguments)-Io,h=Math.abs(s-f),p=f>s?0:1;if(n>c&&(g=c,c=n,n=g),h>=Oo)return t(c,p)+(n?t(n,1-p):"")+"Z";var g,v,d,y,m,M,x,b,_,w,S,k,N=0,E=0,A=[];if((y=(+l.apply(this,arguments)||0)/2)&&(d=u===ql?Math.sqrt(n*n+c*c):+u.apply(this,arguments),p||(E*=-1),c&&(E=tn(d/c*Math.sin(y))),n&&(N=tn(d/n*Math.sin(y)))),c){m=c*Math.cos(f+E),M=c*Math.sin(f+E),x=c*Math.cos(s-E),b=c*Math.sin(s-E);var C=Math.abs(s-f-2*E)<=Fo?0:1;if(E&&yu(m,M,x,b)===p^C){var z=(f+s)/2;m=c*Math.cos(z),M=c*Math.sin(z),x=b=null}}else m=M=0;if(n){_=n*Math.cos(s-N),w=n*Math.sin(s-N),S=n*Math.cos(f+N),k=n*Math.sin(f+N);var L=Math.abs(f-s+2*N)<=Fo?0:1;if(N&&yu(_,w,S,k)===1-p^L){var q=(f+s)/2;_=n*Math.cos(q),w=n*Math.sin(q),S=k=null}}else _=w=0;if(h>Uo&&(g=Math.min(Math.abs(c-n)/2,+i.apply(this,arguments)))>.001){v=c>n^p?0:1;var T=g,R=g;if(Fo>h){var D=null==S?[_,w]:null==x?[m,M]:Re([m,M],[S,k],[x,b],[_,w]),P=m-D[0],U=M-D[1],j=x-D[0],F=b-D[1],H=1/Math.sin(Math.acos((P*j+U*F)/(Math.sqrt(P*P+U*U)*Math.sqrt(j*j+F*F)))/2),O=Math.sqrt(D[0]*D[0]+D[1]*D[1]);R=Math.min(g,(n-O)/(H-1)),T=Math.min(g,(c-O)/(H+1))}if(null!=x){var I=mu(null==S?[_,w]:[S,k],[m,M],c,T,p),Y=mu([x,b],[_,w],c,T,p);g===T?A.push("M",I[0],"A",T,",",T," 0 0,",v," ",I[1],"A",c,",",c," 0 ",1-p^yu(I[1][0],I[1][1],Y[1][0],Y[1][1]),",",p," ",Y[1],"A",T,",",T," 0 0,",v," ",Y[0]):A.push("M",I[0],"A",T,",",T," 0 1,",v," ",Y[0])}else A.push("M",m,",",M);if(null!=S){var Z=mu([m,M],[S,k],n,-R,p),V=mu([_,w],null==x?[m,M]:[x,b],n,-R,p);g===R?A.push("L",V[0],"A",R,",",R," 0 0,",v," ",V[1],"A",n,",",n," 0 ",p^yu(V[1][0],V[1][1],Z[1][0],Z[1][1]),",",1-p," ",Z[1],"A",R,",",R," 0 0,",v," ",Z[0]):A.push("L",V[0],"A",R,",",R," 0 0,",v," ",Z[0])}else A.push("L",_,",",w)}else A.push("M",m,",",M),null!=x&&A.push("A",c,",",c," 0 ",C,",",p," ",x,",",b),A.push("L",_,",",w),null!=S&&A.push("A",n,",",n," 0 ",L,",",1-p," ",S,",",k);return A.push("Z"),A.join("")}function t(n,t){return"M0,"+n+"A"+n+","+n+" 0 1,"+t+" 0,"+-n+"A"+n+","+n+" 0 1,"+t+" 0,"+n}var e=hu,r=pu,i=su,u=ql,o=gu,a=vu,l=du;return n.innerRadius=function(t){return arguments.length?(e=En(t),n):e},n.outerRadius=function(t){return arguments.length?(r=En(t),n):r},n.cornerRadius=function(t){return arguments.length?(i=En(t),n):i},n.padRadius=function(t){return arguments.length?(u=t==ql?ql:En(t),n):u},n.startAngle=function(t){return arguments.length?(o=En(t),n):o},n.endAngle=function(t){return arguments.length?(a=En(t),n):a},n.padAngle=function(t){return arguments.length?(l=En(t),n):l},n.centroid=function(){var n=(+e.apply(this,arguments)+ +r.apply(this,arguments))/2,t=(+o.apply(this,arguments)+ +a.apply(this,arguments))/2-Io;return[Math.cos(t)*n,Math.sin(t)*n]},n};var ql="auto";ao.svg.line=function(){return Mu(m)};var Tl=ao.map({linear:xu,"linear-closed":bu,step:_u,"step-before":wu,"step-after":Su,basis:zu,"basis-open":Lu,"basis-closed":qu,bundle:Tu,cardinal:Eu,"cardinal-open":ku,"cardinal-closed":Nu,monotone:Fu});Tl.forEach(function(n,t){t.key=n,t.closed=/-closed$/.test(n)});var Rl=[0,2/3,1/3,0],Dl=[0,1/3,2/3,0],Pl=[0,1/6,2/3,1/6];ao.svg.line.radial=function(){var n=Mu(Hu);return n.radius=n.x,delete n.x,n.angle=n.y,delete n.y,n},wu.reverse=Su,Su.reverse=wu,ao.svg.area=function(){return Ou(m)},ao.svg.area.radial=function(){var n=Ou(Hu);return n.radius=n.x,delete n.x,n.innerRadius=n.x0,delete n.x0,n.outerRadius=n.x1,delete n.x1,n.angle=n.y,delete n.y,n.startAngle=n.y0,delete n.y0,n.endAngle=n.y1,delete n.y1,n},ao.svg.chord=function(){function n(n,a){var l=t(this,u,n,a),c=t(this,o,n,a);return"M"+l.p0+r(l.r,l.p1,l.a1-l.a0)+(e(l,c)?i(l.r,l.p1,l.r,l.p0):i(l.r,l.p1,c.r,c.p0)+r(c.r,c.p1,c.a1-c.a0)+i(c.r,c.p1,l.r,l.p0))+"Z"}function t(n,t,e,r){var i=t.call(n,e,r),u=a.call(n,i,r),o=l.call(n,i,r)-Io,f=c.call(n,i,r)-Io;return{r:u,a0:o,a1:f,p0:[u*Math.cos(o),u*Math.sin(o)],p1:[u*Math.cos(f),u*Math.sin(f)]}}function e(n,t){return n.a0==t.a0&&n.a1==t.a1}function r(n,t,e){return"A"+n+","+n+" 0 "+ +(e>Fo)+",1 "+t}function i(n,t,e,r){return"Q 0,0 "+r}var u=Me,o=xe,a=Iu,l=gu,c=vu;return n.radius=function(t){return arguments.length?(a=En(t),n):a},n.source=function(t){return arguments.length?(u=En(t),n):u},n.target=function(t){return arguments.length?(o=En(t),n):o},n.startAngle=function(t){return arguments.length?(l=En(t),n):l},n.endAngle=function(t){return arguments.length?(c=En(t),n):c},n},ao.svg.diagonal=function(){function n(n,i){var u=t.call(this,n,i),o=e.call(this,n,i),a=(u.y+o.y)/2,l=[u,{x:u.x,y:a},{x:o.x,y:a},o];return l=l.map(r),"M"+l[0]+"C"+l[1]+" "+l[2]+" "+l[3]}var t=Me,e=xe,r=Yu;return n.source=function(e){return arguments.length?(t=En(e),n):t},n.target=function(t){return arguments.length?(e=En(t),n):e},n.projection=function(t){return arguments.length?(r=t,n):r},n},ao.svg.diagonal.radial=function(){var n=ao.svg.diagonal(),t=Yu,e=n.projection;return n.projection=function(n){return arguments.length?e(Zu(t=n)):t},n},ao.svg.symbol=function(){function n(n,r){return(Ul.get(t.call(this,n,r))||$u)(e.call(this,n,r))}var t=Xu,e=Vu;return n.type=function(e){return arguments.length?(t=En(e),n):t},n.size=function(t){return arguments.length?(e=En(t),n):e},n};var Ul=ao.map({circle:$u,cross:function(n){var t=Math.sqrt(n/5)/2;return"M"+-3*t+","+-t+"H"+-t+"V"+-3*t+"H"+t+"V"+-t+"H"+3*t+"V"+t+"H"+t+"V"+3*t+"H"+-t+"V"+t+"H"+-3*t+"Z"},diamond:function(n){var t=Math.sqrt(n/(2*Fl)),e=t*Fl;return"M0,"+-t+"L"+e+",0 0,"+t+" "+-e+",0Z"},square:function(n){var t=Math.sqrt(n)/2;return"M"+-t+","+-t+"L"+t+","+-t+" "+t+","+t+" "+-t+","+t+"Z"},"triangle-down":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+e+"L"+t+","+-e+" "+-t+","+-e+"Z"},"triangle-up":function(n){var t=Math.sqrt(n/jl),e=t*jl/2;return"M0,"+-e+"L"+t+","+e+" "+-t+","+e+"Z"}});ao.svg.symbolTypes=Ul.keys();var jl=Math.sqrt(3),Fl=Math.tan(30*Yo);Co.transition=function(n){for(var t,e,r=Hl||++Zl,i=Ku(n),u=[],o=Ol||{time:Date.now(),ease:Nr,delay:0,duration:250},a=-1,l=this.length;++au;u++){i.push(t=[]);for(var e=this[u],a=0,l=e.length;l>a;a++)(r=e[a])&&n.call(r,r.__data__,a,u)&&t.push(r)}return Wu(i,this.namespace,this.id)},Yl.tween=function(n,t){var e=this.id,r=this.namespace;return arguments.length<2?this.node()[r][e].tween.get(n):Y(this,null==t?function(t){t[r][e].tween.remove(n)}:function(i){i[r][e].tween.set(n,t)})},Yl.attr=function(n,t){function e(){this.removeAttribute(a)}function r(){this.removeAttributeNS(a.space,a.local)}function i(n){return null==n?e:(n+="",function(){var t,e=this.getAttribute(a);return e!==n&&(t=o(e,n),function(n){this.setAttribute(a,t(n))})})}function u(n){return null==n?r:(n+="",function(){var t,e=this.getAttributeNS(a.space,a.local);return e!==n&&(t=o(e,n),function(n){this.setAttributeNS(a.space,a.local,t(n))})})}if(arguments.length<2){for(t in n)this.attr(t,n[t]);return this}var o="transform"==n?$r:Mr,a=ao.ns.qualify(n);return Ju(this,"attr."+n,t,a.local?u:i)},Yl.attrTween=function(n,t){function e(n,e){var r=t.call(this,n,e,this.getAttribute(i));return r&&function(n){this.setAttribute(i,r(n))}}function r(n,e){var r=t.call(this,n,e,this.getAttributeNS(i.space,i.local));return r&&function(n){this.setAttributeNS(i.space,i.local,r(n))}}var i=ao.ns.qualify(n);return this.tween("attr."+n,i.local?r:e)},Yl.style=function(n,e,r){function i(){this.style.removeProperty(n)}function u(e){return null==e?i:(e+="",function(){var i,u=t(this).getComputedStyle(this,null).getPropertyValue(n);return u!==e&&(i=Mr(u,e),function(t){this.style.setProperty(n,i(t),r)})})}var o=arguments.length;if(3>o){if("string"!=typeof n){2>o&&(e="");for(r in n)this.style(r,n[r],e);return this}r=""}return Ju(this,"style."+n,e,u)},Yl.styleTween=function(n,e,r){function i(i,u){var o=e.call(this,i,u,t(this).getComputedStyle(this,null).getPropertyValue(n));return o&&function(t){this.style.setProperty(n,o(t),r)}}return arguments.length<3&&(r=""),this.tween("style."+n,i)},Yl.text=function(n){return Ju(this,"text",n,Gu)},Yl.remove=function(){var n=this.namespace;return this.each("end.transition",function(){var t;this[n].count<2&&(t=this.parentNode)&&t.removeChild(this)})},Yl.ease=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].ease:("function"!=typeof n&&(n=ao.ease.apply(ao,arguments)),Y(this,function(r){r[e][t].ease=n}))},Yl.delay=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].delay:Y(this,"function"==typeof n?function(r,i,u){r[e][t].delay=+n.call(r,r.__data__,i,u)}:(n=+n,function(r){r[e][t].delay=n}))},Yl.duration=function(n){var t=this.id,e=this.namespace;return arguments.length<1?this.node()[e][t].duration:Y(this,"function"==typeof n?function(r,i,u){r[e][t].duration=Math.max(1,n.call(r,r.__data__,i,u))}:(n=Math.max(1,n),function(r){r[e][t].duration=n}))},Yl.each=function(n,t){var e=this.id,r=this.namespace;if(arguments.length<2){var i=Ol,u=Hl;try{Hl=e,Y(this,function(t,i,u){Ol=t[r][e],n.call(t,t.__data__,i,u)})}finally{Ol=i,Hl=u}}else Y(this,function(i){var u=i[r][e];(u.event||(u.event=ao.dispatch("start","end","interrupt"))).on(n,t)});return this},Yl.transition=function(){for(var n,t,e,r,i=this.id,u=++Zl,o=this.namespace,a=[],l=0,c=this.length;c>l;l++){a.push(n=[]);for(var t=this[l],f=0,s=t.length;s>f;f++)(e=t[f])&&(r=e[o][i],Qu(e,f,o,u,{time:r.time,ease:r.ease,delay:r.delay+r.duration,duration:r.duration})),n.push(e)}return Wu(a,o,u)},ao.svg.axis=function(){function n(n){n.each(function(){var n,c=ao.select(this),f=this.__chart__||e,s=this.__chart__=e.copy(),h=null==l?s.ticks?s.ticks.apply(s,a):s.domain():l,p=null==t?s.tickFormat?s.tickFormat.apply(s,a):m:t,g=c.selectAll(".tick").data(h,s),v=g.enter().insert("g",".domain").attr("class","tick").style("opacity",Uo),d=ao.transition(g.exit()).style("opacity",Uo).remove(),y=ao.transition(g.order()).style("opacity",1),M=Math.max(i,0)+o,x=Zi(s),b=c.selectAll(".domain").data([0]),_=(b.enter().append("path").attr("class","domain"),ao.transition(b));v.append("line"),v.append("text");var w,S,k,N,E=v.select("line"),A=y.select("line"),C=g.select("text").text(p),z=v.select("text"),L=y.select("text"),q="top"===r||"left"===r?-1:1;if("bottom"===r||"top"===r?(n=no,w="x",k="y",S="x2",N="y2",C.attr("dy",0>q?"0em":".71em").style("text-anchor","middle"),_.attr("d","M"+x[0]+","+q*u+"V0H"+x[1]+"V"+q*u)):(n=to,w="y",k="x",S="y2",N="x2",C.attr("dy",".32em").style("text-anchor",0>q?"end":"start"),_.attr("d","M"+q*u+","+x[0]+"H0V"+x[1]+"H"+q*u)),E.attr(N,q*i),z.attr(k,q*M),A.attr(S,0).attr(N,q*i),L.attr(w,0).attr(k,q*M),s.rangeBand){var T=s,R=T.rangeBand()/2;f=s=function(n){return T(n)+R}}else f.rangeBand?f=s:d.call(n,s,f);v.call(n,f,s),y.call(n,s,s)})}var t,e=ao.scale.linear(),r=Vl,i=6,u=6,o=3,a=[10],l=null;return n.scale=function(t){return arguments.length?(e=t,n):e},n.orient=function(t){return arguments.length?(r=t in Xl?t+"":Vl,n):r},n.ticks=function(){return arguments.length?(a=co(arguments),n):a},n.tickValues=function(t){return arguments.length?(l=t,n):l},n.tickFormat=function(e){return arguments.length?(t=e,n):t},n.tickSize=function(t){var e=arguments.length;return e?(i=+t,u=+arguments[e-1],n):i},n.innerTickSize=function(t){return arguments.length?(i=+t,n):i},n.outerTickSize=function(t){return arguments.length?(u=+t,n):u},n.tickPadding=function(t){return arguments.length?(o=+t,n):o},n.tickSubdivide=function(){return arguments.length&&n},n};var Vl="bottom",Xl={top:1,right:1,bottom:1,left:1};ao.svg.brush=function(){function n(t){t.each(function(){var t=ao.select(this).style("pointer-events","all").style("-webkit-tap-highlight-color","rgba(0,0,0,0)").on("mousedown.brush",u).on("touchstart.brush",u),o=t.selectAll(".background").data([0]);o.enter().append("rect").attr("class","background").style("visibility","hidden").style("cursor","crosshair"),t.selectAll(".extent").data([0]).enter().append("rect").attr("class","extent").style("cursor","move");var a=t.selectAll(".resize").data(v,m);a.exit().remove(),a.enter().append("g").attr("class",function(n){return"resize "+n}).style("cursor",function(n){return $l[n]}).append("rect").attr("x",function(n){return/[ew]$/.test(n)?-3:null}).attr("y",function(n){return/^[ns]/.test(n)?-3:null}).attr("width",6).attr("height",6).style("visibility","hidden"),a.style("display",n.empty()?"none":null);var l,s=ao.transition(t),h=ao.transition(o);c&&(l=Zi(c),h.attr("x",l[0]).attr("width",l[1]-l[0]),r(s)),f&&(l=Zi(f),h.attr("y",l[0]).attr("height",l[1]-l[0]),i(s)),e(s)})}function e(n){n.selectAll(".resize").attr("transform",function(n){return"translate("+s[+/e$/.test(n)]+","+h[+/^s/.test(n)]+")"})}function r(n){n.select(".extent").attr("x",s[0]),n.selectAll(".extent,.n>rect,.s>rect").attr("width",s[1]-s[0])}function i(n){n.select(".extent").attr("y",h[0]),n.selectAll(".extent,.e>rect,.w>rect").attr("height",h[1]-h[0])}function u(){function u(){32==ao.event.keyCode&&(C||(M=null,L[0]-=s[1],L[1]-=h[1],C=2),S())}function v(){32==ao.event.keyCode&&2==C&&(L[0]+=s[1],L[1]+=h[1],C=0,S())}function d(){var n=ao.mouse(b),t=!1;x&&(n[0]+=x[0],n[1]+=x[1]),C||(ao.event.altKey?(M||(M=[(s[0]+s[1])/2,(h[0]+h[1])/2]),L[0]=s[+(n[0]f?(i=r,r=f):i=f),v[0]!=r||v[1]!=i?(e?a=null:o=null,v[0]=r,v[1]=i,!0):void 0}function m(){d(),k.style("pointer-events","all").selectAll(".resize").style("display",n.empty()?"none":null),ao.select("body").style("cursor",null),q.on("mousemove.brush",null).on("mouseup.brush",null).on("touchmove.brush",null).on("touchend.brush",null).on("keydown.brush",null).on("keyup.brush",null),z(),w({type:"brushend"})}var M,x,b=this,_=ao.select(ao.event.target),w=l.of(b,arguments),k=ao.select(b),N=_.datum(),E=!/^(n|s)$/.test(N)&&c,A=!/^(e|w)$/.test(N)&&f,C=_.classed("extent"),z=W(b),L=ao.mouse(b),q=ao.select(t(b)).on("keydown.brush",u).on("keyup.brush",v);if(ao.event.changedTouches?q.on("touchmove.brush",d).on("touchend.brush",m):q.on("mousemove.brush",d).on("mouseup.brush",m),k.interrupt().selectAll("*").interrupt(),C)L[0]=s[0]-L[0],L[1]=h[0]-L[1];else if(N){var T=+/w$/.test(N),R=+/^n/.test(N);x=[s[1-T]-L[0],h[1-R]-L[1]],L[0]=s[T],L[1]=h[R]}else ao.event.altKey&&(M=L.slice());k.style("pointer-events","none").selectAll(".resize").style("display",null),ao.select("body").style("cursor",_.style("cursor")),w({type:"brushstart"}),d()}var o,a,l=N(n,"brushstart","brush","brushend"),c=null,f=null,s=[0,0],h=[0,0],p=!0,g=!0,v=Bl[0];return n.event=function(n){n.each(function(){var n=l.of(this,arguments),t={x:s,y:h,i:o,j:a},e=this.__chart__||t;this.__chart__=t,Hl?ao.select(this).transition().each("start.brush",function(){o=e.i,a=e.j,s=e.x,h=e.y,n({type:"brushstart"})}).tween("brush:brush",function(){var e=xr(s,t.x),r=xr(h,t.y);return o=a=null,function(i){s=t.x=e(i),h=t.y=r(i),n({type:"brush",mode:"resize"})}}).each("end.brush",function(){o=t.i,a=t.j,n({type:"brush",mode:"resize"}),n({type:"brushend"})}):(n({type:"brushstart"}),n({type:"brush",mode:"resize"}),n({type:"brushend"}))})},n.x=function(t){return arguments.length?(c=t,v=Bl[!c<<1|!f],n):c},n.y=function(t){return arguments.length?(f=t,v=Bl[!c<<1|!f],n):f},n.clamp=function(t){return arguments.length?(c&&f?(p=!!t[0],g=!!t[1]):c?p=!!t:f&&(g=!!t),n):c&&f?[p,g]:c?p:f?g:null},n.extent=function(t){var e,r,i,u,l;return arguments.length?(c&&(e=t[0],r=t[1],f&&(e=e[0],r=r[0]),o=[e,r],c.invert&&(e=c(e),r=c(r)),e>r&&(l=e,e=r,r=l),e==s[0]&&r==s[1]||(s=[e,r])),f&&(i=t[0],u=t[1],c&&(i=i[1],u=u[1]),a=[i,u],f.invert&&(i=f(i),u=f(u)),i>u&&(l=i,i=u,u=l),i==h[0]&&u==h[1]||(h=[i,u])),n):(c&&(o?(e=o[0],r=o[1]):(e=s[0],r=s[1],c.invert&&(e=c.invert(e),r=c.invert(r)),e>r&&(l=e,e=r,r=l))),f&&(a?(i=a[0],u=a[1]):(i=h[0],u=h[1],f.invert&&(i=f.invert(i),u=f.invert(u)),i>u&&(l=i,i=u,u=l))),c&&f?[[e,i],[r,u]]:c?[e,r]:f&&[i,u])},n.clear=function(){return n.empty()||(s=[0,0],h=[0,0],o=a=null),n},n.empty=function(){return!!c&&s[0]==s[1]||!!f&&h[0]==h[1]},ao.rebind(n,l,"on")};var $l={n:"ns-resize",e:"ew-resize",s:"ns-resize",w:"ew-resize",nw:"nwse-resize",ne:"nesw-resize",se:"nwse-resize",sw:"nesw-resize"},Bl=[["n","e","s","w","nw","ne","se","sw"],["e","w"],["n","s"],[]],Wl=ga.format=xa.timeFormat,Jl=Wl.utc,Gl=Jl("%Y-%m-%dT%H:%M:%S.%LZ");Wl.iso=Date.prototype.toISOString&&+new Date("2000-01-01T00:00:00.000Z")?eo:Gl,eo.parse=function(n){var t=new Date(n);return isNaN(t)?null:t},eo.toString=Gl.toString,ga.second=On(function(n){return new va(1e3*Math.floor(n/1e3))},function(n,t){n.setTime(n.getTime()+1e3*Math.floor(t))},function(n){return n.getSeconds()}),ga.seconds=ga.second.range,ga.seconds.utc=ga.second.utc.range,ga.minute=On(function(n){return new va(6e4*Math.floor(n/6e4))},function(n,t){n.setTime(n.getTime()+6e4*Math.floor(t))},function(n){return n.getMinutes()}),ga.minutes=ga.minute.range,ga.minutes.utc=ga.minute.utc.range,ga.hour=On(function(n){var t=n.getTimezoneOffset()/60;return new va(36e5*(Math.floor(n/36e5-t)+t))},function(n,t){n.setTime(n.getTime()+36e5*Math.floor(t))},function(n){return n.getHours()}),ga.hours=ga.hour.range,ga.hours.utc=ga.hour.utc.range,ga.month=On(function(n){return n=ga.day(n),n.setDate(1),n},function(n,t){n.setMonth(n.getMonth()+t)},function(n){return n.getMonth()}),ga.months=ga.month.range,ga.months.utc=ga.month.utc.range;var Kl=[1e3,5e3,15e3,3e4,6e4,3e5,9e5,18e5,36e5,108e5,216e5,432e5,864e5,1728e5,6048e5,2592e6,7776e6,31536e6],Ql=[[ga.second,1],[ga.second,5],[ga.second,15],[ga.second,30],[ga.minute,1],[ga.minute,5],[ga.minute,15],[ga.minute,30],[ga.hour,1],[ga.hour,3],[ga.hour,6],[ga.hour,12],[ga.day,1],[ga.day,2],[ga.week,1],[ga.month,1],[ga.month,3],[ga.year,1]],nc=Wl.multi([[".%L",function(n){return n.getMilliseconds()}],[":%S",function(n){return n.getSeconds()}],["%I:%M",function(n){return n.getMinutes()}],["%I %p",function(n){return n.getHours()}],["%a %d",function(n){return n.getDay()&&1!=n.getDate()}],["%b %d",function(n){return 1!=n.getDate()}],["%B",function(n){return n.getMonth()}],["%Y",zt]]),tc={range:function(n,t,e){return ao.range(Math.ceil(n/e)*e,+t,e).map(io)},floor:m,ceil:m};Ql.year=ga.year,ga.scale=function(){return ro(ao.scale.linear(),Ql,nc)};var ec=Ql.map(function(n){return[n[0].utc,n[1]]}),rc=Jl.multi([[".%L",function(n){return n.getUTCMilliseconds()}],[":%S",function(n){return n.getUTCSeconds()}],["%I:%M",function(n){return n.getUTCMinutes()}],["%I %p",function(n){return n.getUTCHours()}],["%a %d",function(n){return n.getUTCDay()&&1!=n.getUTCDate()}],["%b %d",function(n){return 1!=n.getUTCDate()}],["%B",function(n){return n.getUTCMonth()}],["%Y",zt]]);ec.year=ga.year.utc,ga.scale.utc=function(){return ro(ao.scale.linear(),ec,rc)},ao.text=An(function(n){return n.responseText}),ao.json=function(n,t){return Cn(n,"application/json",uo,t)},ao.html=function(n,t){return Cn(n,"text/html",oo,t)},ao.xml=An(function(n){return n.responseXML}),"function"==typeof define&&define.amd?(this.d3=ao,define(ao)):"object"==typeof module&&module.exports?module.exports=ao:this.d3=ao}(); \ No newline at end of file diff --git a/src/main/resources/res/functions.js b/src/main/resources/res/functions.js new file mode 100644 index 00000000..93da9ff1 --- /dev/null +++ b/src/main/resources/res/functions.js @@ -0,0 +1,31 @@ + +function getRandomColor() { + const letters = '0123456789ABCDEF'; + var color = '#'; + for (var i = 0; i < 6; i++) { + color += letters[Math.floor(Math.random() * 16)]; + } + return color; +} + +function shadeColor(color, amount) { + return '#' + color.replace(/^#/, '') + .replace(/../g, color => ('0'+Math.min(255, Math.max(0, parseInt(color, 16) + amount)) + .toString(16)) + .substr(-2)); +} + +let resetFlag = 0 +let randomColor = getRandomColor() + +function getColor(single = false) { + if(single) { + return getRandomColor(); + } + if(resetFlag > 1) { + resetFlag = 0; + randomColor = getRandomColor(); + } + resetFlag++; + return randomColor; +} diff --git a/src/main/resources/res/mermaid.js b/src/main/resources/res/mermaid.js new file mode 100644 index 00000000..14ed8f04 --- /dev/null +++ b/src/main/resources/res/mermaid.js @@ -0,0 +1,3 @@ +/*! For license information please see mermaid.min.js.LICENSE.txt */ +!function(t,e){"object"==typeof exports&&"object"==typeof module?module.exports=e():"function"==typeof define&&define.amd?define([],e):"object"==typeof exports?exports.mermaid=e():t.mermaid=e()}("undefined"!=typeof self?self:this,(()=>(()=>{var t={2536:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,6],n=[1,7],r=[1,8],i=[1,9],a=[1,16],o=[1,11],s=[1,12],l=[1,13],u=[1,14],h=[1,15],f=[1,27],d=[1,33],p=[1,34],g=[1,35],y=[1,36],m=[1,37],b=[1,72],v=[1,73],_=[1,74],x=[1,75],k=[1,76],w=[1,77],T=[1,78],E=[1,38],C=[1,39],S=[1,40],A=[1,41],M=[1,42],N=[1,43],D=[1,44],O=[1,45],B=[1,46],L=[1,47],I=[1,48],F=[1,49],R=[1,50],P=[1,51],j=[1,52],z=[1,53],Y=[1,54],U=[1,55],$=[1,56],W=[1,57],q=[1,59],H=[1,60],V=[1,61],G=[1,62],X=[1,63],Z=[1,64],Q=[1,65],K=[1,66],J=[1,67],tt=[1,68],et=[1,69],nt=[24,52],rt=[24,44,46,47,48,49,50,51,52,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84],it=[15,24,44,46,47,48,49,50,51,52,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84],at=[1,94],ot=[1,95],st=[1,96],ct=[1,97],lt=[15,24,52],ut=[7,8,9,10,18,22,25,26,27,28],ht=[15,24,43,52],ft=[15,24,43,52,86,87,89,90],dt=[15,43],pt=[44,46,47,48,49,50,51,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84],gt={trace:function(){},yy:{},symbols_:{error:2,start:3,mermaidDoc:4,direction:5,directive:6,direction_tb:7,direction_bt:8,direction_rl:9,direction_lr:10,graphConfig:11,openDirective:12,typeDirective:13,closeDirective:14,NEWLINE:15,":":16,argDirective:17,open_directive:18,type_directive:19,arg_directive:20,close_directive:21,C4_CONTEXT:22,statements:23,EOF:24,C4_CONTAINER:25,C4_COMPONENT:26,C4_DYNAMIC:27,C4_DEPLOYMENT:28,otherStatements:29,diagramStatements:30,otherStatement:31,title:32,accDescription:33,acc_title:34,acc_title_value:35,acc_descr:36,acc_descr_value:37,acc_descr_multiline_value:38,boundaryStatement:39,boundaryStartStatement:40,boundaryStopStatement:41,boundaryStart:42,LBRACE:43,ENTERPRISE_BOUNDARY:44,attributes:45,SYSTEM_BOUNDARY:46,BOUNDARY:47,CONTAINER_BOUNDARY:48,NODE:49,NODE_L:50,NODE_R:51,RBRACE:52,diagramStatement:53,PERSON:54,PERSON_EXT:55,SYSTEM:56,SYSTEM_DB:57,SYSTEM_QUEUE:58,SYSTEM_EXT:59,SYSTEM_EXT_DB:60,SYSTEM_EXT_QUEUE:61,CONTAINER:62,CONTAINER_DB:63,CONTAINER_QUEUE:64,CONTAINER_EXT:65,CONTAINER_EXT_DB:66,CONTAINER_EXT_QUEUE:67,COMPONENT:68,COMPONENT_DB:69,COMPONENT_QUEUE:70,COMPONENT_EXT:71,COMPONENT_EXT_DB:72,COMPONENT_EXT_QUEUE:73,REL:74,BIREL:75,REL_U:76,REL_D:77,REL_L:78,REL_R:79,REL_B:80,REL_INDEX:81,UPDATE_EL_STYLE:82,UPDATE_REL_STYLE:83,UPDATE_LAYOUT_CONFIG:84,attribute:85,STR:86,STR_KEY:87,STR_VALUE:88,ATTRIBUTE:89,ATTRIBUTE_EMPTY:90,$accept:0,$end:1},terminals_:{2:"error",7:"direction_tb",8:"direction_bt",9:"direction_rl",10:"direction_lr",15:"NEWLINE",16:":",18:"open_directive",19:"type_directive",20:"arg_directive",21:"close_directive",22:"C4_CONTEXT",24:"EOF",25:"C4_CONTAINER",26:"C4_COMPONENT",27:"C4_DYNAMIC",28:"C4_DEPLOYMENT",32:"title",33:"accDescription",34:"acc_title",35:"acc_title_value",36:"acc_descr",37:"acc_descr_value",38:"acc_descr_multiline_value",43:"LBRACE",44:"ENTERPRISE_BOUNDARY",46:"SYSTEM_BOUNDARY",47:"BOUNDARY",48:"CONTAINER_BOUNDARY",49:"NODE",50:"NODE_L",51:"NODE_R",52:"RBRACE",54:"PERSON",55:"PERSON_EXT",56:"SYSTEM",57:"SYSTEM_DB",58:"SYSTEM_QUEUE",59:"SYSTEM_EXT",60:"SYSTEM_EXT_DB",61:"SYSTEM_EXT_QUEUE",62:"CONTAINER",63:"CONTAINER_DB",64:"CONTAINER_QUEUE",65:"CONTAINER_EXT",66:"CONTAINER_EXT_DB",67:"CONTAINER_EXT_QUEUE",68:"COMPONENT",69:"COMPONENT_DB",70:"COMPONENT_QUEUE",71:"COMPONENT_EXT",72:"COMPONENT_EXT_DB",73:"COMPONENT_EXT_QUEUE",74:"REL",75:"BIREL",76:"REL_U",77:"REL_D",78:"REL_L",79:"REL_R",80:"REL_B",81:"REL_INDEX",82:"UPDATE_EL_STYLE",83:"UPDATE_REL_STYLE",84:"UPDATE_LAYOUT_CONFIG",86:"STR",87:"STR_KEY",88:"STR_VALUE",89:"ATTRIBUTE",90:"ATTRIBUTE_EMPTY"},productions_:[0,[3,1],[3,1],[3,2],[5,1],[5,1],[5,1],[5,1],[4,1],[6,4],[6,6],[12,1],[13,1],[17,1],[14,1],[11,4],[11,4],[11,4],[11,4],[11,4],[23,1],[23,1],[23,2],[29,1],[29,2],[29,3],[31,1],[31,1],[31,2],[31,2],[31,1],[39,3],[40,3],[40,3],[40,4],[42,2],[42,2],[42,2],[42,2],[42,2],[42,2],[42,2],[41,1],[30,1],[30,2],[30,3],[53,2],[53,2],[53,2],[53,2],[53,2],[53,2],[53,2],[53,2],[53,2],[53,2],[53,2],[53,2],[53,2],[53,2],[53,2],[53,2],[53,2],[53,2],[53,2],[53,2],[53,1],[53,2],[53,2],[53,2],[53,2],[53,2],[53,2],[53,2],[53,2],[53,2],[53,2],[53,2],[45,1],[45,2],[85,1],[85,2],[85,1],[85,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 4:r.setDirection("TB");break;case 5:r.setDirection("BT");break;case 6:r.setDirection("RL");break;case 7:r.setDirection("LR");break;case 11:console.log("open_directive: ",a[s]),r.parseDirective("%%{","open_directive");break;case 12:break;case 13:a[s]=a[s].trim().replace(/'/g,'"'),console.log("arg_directive: ",a[s]),r.parseDirective(a[s],"arg_directive");break;case 14:console.log("close_directive: ",a[s]),r.parseDirective("}%%","close_directive","c4Context");break;case 15:case 16:case 17:case 18:case 19:r.setC4Type(a[s-3]);break;case 26:r.setTitle(a[s].substring(6)),this.$=a[s].substring(6);break;case 27:r.setAccDescription(a[s].substring(15)),this.$=a[s].substring(15);break;case 28:this.$=a[s].trim(),r.setTitle(this.$);break;case 29:case 30:this.$=a[s].trim(),r.setAccDescription(this.$);break;case 35:case 36:console.log(a[s-1],JSON.stringify(a[s])),a[s].splice(2,0,"ENTERPRISE"),r.addPersonOrSystemBoundary(...a[s]),this.$=a[s];break;case 37:console.log(a[s-1],JSON.stringify(a[s])),r.addPersonOrSystemBoundary(...a[s]),this.$=a[s];break;case 38:console.log(a[s-1],JSON.stringify(a[s])),a[s].splice(2,0,"CONTAINER"),r.addContainerBoundary(...a[s]),this.$=a[s];break;case 39:console.log(a[s-1],JSON.stringify(a[s])),r.addDeploymentNode("node",...a[s]),this.$=a[s];break;case 40:console.log(a[s-1],JSON.stringify(a[s])),r.addDeploymentNode("nodeL",...a[s]),this.$=a[s];break;case 41:console.log(a[s-1],JSON.stringify(a[s])),r.addDeploymentNode("nodeR",...a[s]),this.$=a[s];break;case 42:r.popBoundaryParseStack();break;case 46:console.log(a[s-1],JSON.stringify(a[s])),r.addPersonOrSystem("person",...a[s]),this.$=a[s];break;case 47:console.log(a[s-1],JSON.stringify(a[s])),r.addPersonOrSystem("external_person",...a[s]),this.$=a[s];break;case 48:console.log(a[s-1],JSON.stringify(a[s])),r.addPersonOrSystem("system",...a[s]),this.$=a[s];break;case 49:console.log(a[s-1],JSON.stringify(a[s])),r.addPersonOrSystem("system_db",...a[s]),this.$=a[s];break;case 50:console.log(a[s-1],JSON.stringify(a[s])),r.addPersonOrSystem("system_queue",...a[s]),this.$=a[s];break;case 51:console.log(a[s-1],JSON.stringify(a[s])),r.addPersonOrSystem("external_system",...a[s]),this.$=a[s];break;case 52:console.log(a[s-1],JSON.stringify(a[s])),r.addPersonOrSystem("external_system_db",...a[s]),this.$=a[s];break;case 53:console.log(a[s-1],JSON.stringify(a[s])),r.addPersonOrSystem("external_system_queue",...a[s]),this.$=a[s];break;case 54:console.log(a[s-1],JSON.stringify(a[s])),r.addContainer("container",...a[s]),this.$=a[s];break;case 55:console.log(a[s-1],JSON.stringify(a[s])),r.addContainer("container_db",...a[s]),this.$=a[s];break;case 56:console.log(a[s-1],JSON.stringify(a[s])),r.addContainer("container_queue",...a[s]),this.$=a[s];break;case 57:console.log(a[s-1],JSON.stringify(a[s])),r.addContainer("external_container",...a[s]),this.$=a[s];break;case 58:console.log(a[s-1],JSON.stringify(a[s])),r.addContainer("external_container_db",...a[s]),this.$=a[s];break;case 59:console.log(a[s-1],JSON.stringify(a[s])),r.addContainer("external_container_queue",...a[s]),this.$=a[s];break;case 60:console.log(a[s-1],JSON.stringify(a[s])),r.addComponent("component",...a[s]),this.$=a[s];break;case 61:console.log(a[s-1],JSON.stringify(a[s])),r.addComponent("component_db",...a[s]),this.$=a[s];break;case 62:console.log(a[s-1],JSON.stringify(a[s])),r.addComponent("component_queue",...a[s]),this.$=a[s];break;case 63:console.log(a[s-1],JSON.stringify(a[s])),r.addComponent("external_component",...a[s]),this.$=a[s];break;case 64:console.log(a[s-1],JSON.stringify(a[s])),r.addComponent("external_component_db",...a[s]),this.$=a[s];break;case 65:console.log(a[s-1],JSON.stringify(a[s])),r.addComponent("external_component_queue",...a[s]),this.$=a[s];break;case 67:console.log(a[s-1],JSON.stringify(a[s])),r.addRel("rel",...a[s]),this.$=a[s];break;case 68:console.log(a[s-1],JSON.stringify(a[s])),r.addRel("birel",...a[s]),this.$=a[s];break;case 69:console.log(a[s-1],JSON.stringify(a[s])),r.addRel("rel_u",...a[s]),this.$=a[s];break;case 70:console.log(a[s-1],JSON.stringify(a[s])),r.addRel("rel_d",...a[s]),this.$=a[s];break;case 71:console.log(a[s-1],JSON.stringify(a[s])),r.addRel("rel_l",...a[s]),this.$=a[s];break;case 72:console.log(a[s-1],JSON.stringify(a[s])),r.addRel("rel_r",...a[s]),this.$=a[s];break;case 73:console.log(a[s-1],JSON.stringify(a[s])),r.addRel("rel_b",...a[s]),this.$=a[s];break;case 74:console.log(a[s-1],JSON.stringify(a[s])),a[s].splice(0,1),r.addRel("rel",...a[s]),this.$=a[s];break;case 75:console.log(a[s-1],JSON.stringify(a[s])),r.updateElStyle("update_el_style",...a[s]),this.$=a[s];break;case 76:console.log(a[s-1],JSON.stringify(a[s])),r.updateRelStyle("update_rel_style",...a[s]),this.$=a[s];break;case 77:console.log(a[s-1],JSON.stringify(a[s])),r.updateLayoutConfig("update_layout_config",...a[s]),this.$=a[s];break;case 78:console.log("PUSH ATTRIBUTE: ",a[s]),this.$=[a[s]];break;case 79:console.log("PUSH ATTRIBUTE: ",a[s-1]),a[s].unshift(a[s-1]),this.$=a[s];break;case 80:case 82:this.$=a[s].trim();break;case 81:console.log("kv: ",a[s-1],a[s]);let t={};t[a[s-1].trim()]=a[s].trim(),this.$=t;break;case 83:this.$=""}},table:[{3:1,4:2,5:3,6:4,7:e,8:n,9:r,10:i,11:5,12:10,18:a,22:o,25:s,26:l,27:u,28:h},{1:[3]},{1:[2,1]},{1:[2,2]},{3:17,4:2,5:3,6:4,7:e,8:n,9:r,10:i,11:5,12:10,18:a,22:o,25:s,26:l,27:u,28:h},{1:[2,8]},{1:[2,4]},{1:[2,5]},{1:[2,6]},{1:[2,7]},{13:18,19:[1,19]},{15:[1,20]},{15:[1,21]},{15:[1,22]},{15:[1,23]},{15:[1,24]},{19:[2,11]},{1:[2,3]},{14:25,16:[1,26],21:f},t([16,21],[2,12]),{23:28,29:29,30:30,31:31,32:d,33:p,34:g,36:y,38:m,39:58,40:70,42:71,44:b,46:v,47:_,48:x,49:k,50:w,51:T,53:32,54:E,55:C,56:S,57:A,58:M,59:N,60:D,61:O,62:B,63:L,64:I,65:F,66:R,67:P,68:j,69:z,70:Y,71:U,72:$,73:W,74:q,75:H,76:V,77:G,78:X,79:Z,80:Q,81:K,82:J,83:tt,84:et},{23:79,29:29,30:30,31:31,32:d,33:p,34:g,36:y,38:m,39:58,40:70,42:71,44:b,46:v,47:_,48:x,49:k,50:w,51:T,53:32,54:E,55:C,56:S,57:A,58:M,59:N,60:D,61:O,62:B,63:L,64:I,65:F,66:R,67:P,68:j,69:z,70:Y,71:U,72:$,73:W,74:q,75:H,76:V,77:G,78:X,79:Z,80:Q,81:K,82:J,83:tt,84:et},{23:80,29:29,30:30,31:31,32:d,33:p,34:g,36:y,38:m,39:58,40:70,42:71,44:b,46:v,47:_,48:x,49:k,50:w,51:T,53:32,54:E,55:C,56:S,57:A,58:M,59:N,60:D,61:O,62:B,63:L,64:I,65:F,66:R,67:P,68:j,69:z,70:Y,71:U,72:$,73:W,74:q,75:H,76:V,77:G,78:X,79:Z,80:Q,81:K,82:J,83:tt,84:et},{23:81,29:29,30:30,31:31,32:d,33:p,34:g,36:y,38:m,39:58,40:70,42:71,44:b,46:v,47:_,48:x,49:k,50:w,51:T,53:32,54:E,55:C,56:S,57:A,58:M,59:N,60:D,61:O,62:B,63:L,64:I,65:F,66:R,67:P,68:j,69:z,70:Y,71:U,72:$,73:W,74:q,75:H,76:V,77:G,78:X,79:Z,80:Q,81:K,82:J,83:tt,84:et},{23:82,29:29,30:30,31:31,32:d,33:p,34:g,36:y,38:m,39:58,40:70,42:71,44:b,46:v,47:_,48:x,49:k,50:w,51:T,53:32,54:E,55:C,56:S,57:A,58:M,59:N,60:D,61:O,62:B,63:L,64:I,65:F,66:R,67:P,68:j,69:z,70:Y,71:U,72:$,73:W,74:q,75:H,76:V,77:G,78:X,79:Z,80:Q,81:K,82:J,83:tt,84:et},{15:[1,83]},{17:84,20:[1,85]},{15:[2,14]},{24:[1,86]},t(nt,[2,20],{53:32,39:58,40:70,42:71,30:87,44:b,46:v,47:_,48:x,49:k,50:w,51:T,54:E,55:C,56:S,57:A,58:M,59:N,60:D,61:O,62:B,63:L,64:I,65:F,66:R,67:P,68:j,69:z,70:Y,71:U,72:$,73:W,74:q,75:H,76:V,77:G,78:X,79:Z,80:Q,81:K,82:J,83:tt,84:et}),t(nt,[2,21]),t(rt,[2,23],{15:[1,88]}),t(nt,[2,43],{15:[1,89]}),t(it,[2,26]),t(it,[2,27]),{35:[1,90]},{37:[1,91]},t(it,[2,30]),{45:92,85:93,86:at,87:ot,89:st,90:ct},{45:98,85:93,86:at,87:ot,89:st,90:ct},{45:99,85:93,86:at,87:ot,89:st,90:ct},{45:100,85:93,86:at,87:ot,89:st,90:ct},{45:101,85:93,86:at,87:ot,89:st,90:ct},{45:102,85:93,86:at,87:ot,89:st,90:ct},{45:103,85:93,86:at,87:ot,89:st,90:ct},{45:104,85:93,86:at,87:ot,89:st,90:ct},{45:105,85:93,86:at,87:ot,89:st,90:ct},{45:106,85:93,86:at,87:ot,89:st,90:ct},{45:107,85:93,86:at,87:ot,89:st,90:ct},{45:108,85:93,86:at,87:ot,89:st,90:ct},{45:109,85:93,86:at,87:ot,89:st,90:ct},{45:110,85:93,86:at,87:ot,89:st,90:ct},{45:111,85:93,86:at,87:ot,89:st,90:ct},{45:112,85:93,86:at,87:ot,89:st,90:ct},{45:113,85:93,86:at,87:ot,89:st,90:ct},{45:114,85:93,86:at,87:ot,89:st,90:ct},{45:115,85:93,86:at,87:ot,89:st,90:ct},{45:116,85:93,86:at,87:ot,89:st,90:ct},t(lt,[2,66]),{45:117,85:93,86:at,87:ot,89:st,90:ct},{45:118,85:93,86:at,87:ot,89:st,90:ct},{45:119,85:93,86:at,87:ot,89:st,90:ct},{45:120,85:93,86:at,87:ot,89:st,90:ct},{45:121,85:93,86:at,87:ot,89:st,90:ct},{45:122,85:93,86:at,87:ot,89:st,90:ct},{45:123,85:93,86:at,87:ot,89:st,90:ct},{45:124,85:93,86:at,87:ot,89:st,90:ct},{45:125,85:93,86:at,87:ot,89:st,90:ct},{45:126,85:93,86:at,87:ot,89:st,90:ct},{45:127,85:93,86:at,87:ot,89:st,90:ct},{30:128,39:58,40:70,42:71,44:b,46:v,47:_,48:x,49:k,50:w,51:T,53:32,54:E,55:C,56:S,57:A,58:M,59:N,60:D,61:O,62:B,63:L,64:I,65:F,66:R,67:P,68:j,69:z,70:Y,71:U,72:$,73:W,74:q,75:H,76:V,77:G,78:X,79:Z,80:Q,81:K,82:J,83:tt,84:et},{15:[1,130],43:[1,129]},{45:131,85:93,86:at,87:ot,89:st,90:ct},{45:132,85:93,86:at,87:ot,89:st,90:ct},{45:133,85:93,86:at,87:ot,89:st,90:ct},{45:134,85:93,86:at,87:ot,89:st,90:ct},{45:135,85:93,86:at,87:ot,89:st,90:ct},{45:136,85:93,86:at,87:ot,89:st,90:ct},{45:137,85:93,86:at,87:ot,89:st,90:ct},{24:[1,138]},{24:[1,139]},{24:[1,140]},{24:[1,141]},t(ut,[2,9]),{14:142,21:f},{21:[2,13]},{1:[2,15]},t(nt,[2,22]),t(rt,[2,24],{31:31,29:143,32:d,33:p,34:g,36:y,38:m}),t(nt,[2,44],{29:29,30:30,31:31,53:32,39:58,40:70,42:71,23:144,32:d,33:p,34:g,36:y,38:m,44:b,46:v,47:_,48:x,49:k,50:w,51:T,54:E,55:C,56:S,57:A,58:M,59:N,60:D,61:O,62:B,63:L,64:I,65:F,66:R,67:P,68:j,69:z,70:Y,71:U,72:$,73:W,74:q,75:H,76:V,77:G,78:X,79:Z,80:Q,81:K,82:J,83:tt,84:et}),t(it,[2,28]),t(it,[2,29]),t(lt,[2,46]),t(ht,[2,78],{85:93,45:145,86:at,87:ot,89:st,90:ct}),t(ft,[2,80]),{88:[1,146]},t(ft,[2,82]),t(ft,[2,83]),t(lt,[2,47]),t(lt,[2,48]),t(lt,[2,49]),t(lt,[2,50]),t(lt,[2,51]),t(lt,[2,52]),t(lt,[2,53]),t(lt,[2,54]),t(lt,[2,55]),t(lt,[2,56]),t(lt,[2,57]),t(lt,[2,58]),t(lt,[2,59]),t(lt,[2,60]),t(lt,[2,61]),t(lt,[2,62]),t(lt,[2,63]),t(lt,[2,64]),t(lt,[2,65]),t(lt,[2,67]),t(lt,[2,68]),t(lt,[2,69]),t(lt,[2,70]),t(lt,[2,71]),t(lt,[2,72]),t(lt,[2,73]),t(lt,[2,74]),t(lt,[2,75]),t(lt,[2,76]),t(lt,[2,77]),{41:147,52:[1,148]},{15:[1,149]},{43:[1,150]},t(dt,[2,35]),t(dt,[2,36]),t(dt,[2,37]),t(dt,[2,38]),t(dt,[2,39]),t(dt,[2,40]),t(dt,[2,41]),{1:[2,16]},{1:[2,17]},{1:[2,18]},{1:[2,19]},{15:[1,151]},t(rt,[2,25]),t(nt,[2,45]),t(ht,[2,79]),t(ft,[2,81]),t(lt,[2,31]),t(lt,[2,42]),t(pt,[2,32]),t(pt,[2,33],{15:[1,152]}),t(ut,[2,10]),t(pt,[2,34])],defaultActions:{2:[2,1],3:[2,2],5:[2,8],6:[2,4],7:[2,5],8:[2,6],9:[2,7],16:[2,11],17:[2,3],27:[2,14],85:[2,13],86:[2,15],138:[2,16],139:[2,17],140:[2,18],141:[2,19]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,l=0,u=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var m=p.yylloc;a.push(m);var b=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,x,k,w,T,E,C,S,A,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==_&&(_=v()),w=o[k]&&o[k][_]),void 0===w||!w.length||!w[0]){var N="";for(E in A=[],o[k])this.terminals_[E]&&E>h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(c+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:m,expected:A})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+_);switch(w[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),_=null,x?(_=x,x=null):(l=p.yyleng,s=p.yytext,c=p.yylineno,m=p.yylloc,u>0&&u--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},b&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,l,c,g.yy,w[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},yt={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),18;case 1:return 7;case 2:return 8;case 3:return 9;case 4:return 10;case 5:return this.begin("type_directive"),19;case 6:return this.popState(),this.begin("arg_directive"),16;case 7:return this.popState(),this.popState(),21;case 8:return 20;case 9:return 32;case 10:return 33;case 11:return this.begin("acc_title"),34;case 12:return this.popState(),"acc_title_value";case 13:return this.begin("acc_descr"),36;case 14:return this.popState(),"acc_descr_value";case 15:this.begin("acc_descr_multiline");break;case 16:this.popState();break;case 17:return"acc_descr_multiline_value";case 18:case 21:break;case 19:c;break;case 20:return 15;case 22:return 22;case 23:return 25;case 24:return 26;case 25:return 27;case 26:return 28;case 27:return this.begin("person_ext"),console.log("begin person_ext"),55;case 28:return this.begin("person"),console.log("begin person"),54;case 29:return this.begin("system_ext_queue"),console.log("begin system_ext_queue"),61;case 30:return this.begin("system_ext_db"),console.log("begin system_ext_db"),60;case 31:return this.begin("system_ext"),console.log("begin system_ext"),59;case 32:return this.begin("system_queue"),console.log("begin system_queue"),58;case 33:return this.begin("system_db"),console.log("begin system_db"),57;case 34:return this.begin("system"),console.log("begin system"),56;case 35:return this.begin("boundary"),console.log("begin boundary"),47;case 36:return this.begin("enterprise_boundary"),console.log("begin enterprise_boundary"),44;case 37:return this.begin("system_boundary"),console.log("begin system_boundary"),46;case 38:return this.begin("container_ext_queue"),console.log("begin container_ext_queue"),67;case 39:return this.begin("container_ext_db"),console.log("begin container_ext_db"),66;case 40:return this.begin("container_ext"),console.log("begin container_ext"),65;case 41:return this.begin("container_queue"),console.log("begin container_queue"),64;case 42:return this.begin("container_db"),console.log("begin container_db"),63;case 43:return this.begin("container"),console.log("begin container"),62;case 44:return this.begin("container_boundary"),console.log("begin container_boundary"),48;case 45:return this.begin("component_ext_queue"),console.log("begin component_ext_queue"),73;case 46:return this.begin("component_ext_db"),console.log("begin component_ext_db"),72;case 47:return this.begin("component_ext"),console.log("begin component_ext"),71;case 48:return this.begin("component_queue"),console.log("begin component_queue"),70;case 49:return this.begin("component_db"),console.log("begin component_db"),69;case 50:return this.begin("component"),console.log("begin component"),68;case 51:case 52:return this.begin("node"),console.log("begin node"),49;case 53:return this.begin("node_l"),console.log("begin node_l"),50;case 54:return this.begin("node_r"),console.log("begin node_r"),51;case 55:return this.begin("rel"),console.log("begin rel"),74;case 56:return this.begin("birel"),console.log("begin birel"),75;case 57:case 58:return this.begin("rel_u"),console.log("begin rel_u"),76;case 59:case 60:return this.begin("rel_d"),console.log("begin rel_d"),77;case 61:case 62:return this.begin("rel_l"),console.log("begin rel_l"),78;case 63:case 64:return this.begin("rel_r"),console.log("begin rel_r"),79;case 65:return this.begin("rel_b"),console.log("begin rel_b"),80;case 66:return this.begin("rel_index"),console.log("begin rel_index"),81;case 67:return this.begin("update_el_style"),console.log("begin update_el_style"),82;case 68:return this.begin("update_rel_style"),console.log("begin update_rel_style"),83;case 69:return this.begin("update_layout_config"),console.log("begin update_layout_config"),84;case 70:return"EOF_IN_STRUCT";case 71:return console.log("begin attribute with ATTRIBUTE_EMPTY"),this.begin("attribute"),"ATTRIBUTE_EMPTY";case 72:console.log("begin attribute"),this.begin("attribute");break;case 73:console.log("STOP attribute"),this.popState(),console.log("STOP diagram"),this.popState();break;case 74:return console.log(",,"),90;case 75:console.log(",");break;case 76:return console.log("ATTRIBUTE_EMPTY"),90;case 77:console.log("begin string"),this.begin("string");break;case 78:console.log("STOP string"),this.popState();break;case 79:return console.log("STR"),"STR";case 80:console.log("begin string_kv"),this.begin("string_kv");break;case 81:return console.log("STR_KEY"),this.begin("string_kv_key"),"STR_KEY";case 82:console.log("begin string_kv_value"),this.popState(),this.begin("string_kv_value");break;case 83:return console.log("STR_VALUE"),"STR_VALUE";case 84:console.log("STOP string_kv_value"),this.popState(),this.popState();break;case 85:return console.log("not STR"),"STR";case 86:return console.log("begin boundary block"),"LBRACE";case 87:return console.log("STOP boundary block"),"RBRACE";case 88:return"SPACE";case 89:return"EOL";case 90:return 24}},rules:[/^(?:%%\{)/,/^(?:.*direction\s+TB[^\n]*)/,/^(?:.*direction\s+BT[^\n]*)/,/^(?:.*direction\s+RL[^\n]*)/,/^(?:.*direction\s+LR[^\n]*)/,/^(?:((?:(?!\}%%)[^:.])*))/,/^(?::)/,/^(?:\}%%)/,/^(?:((?:(?!\}%%).|\n)*))/,/^(?:title\s[^#\n;]+)/,/^(?:accDescription\s[^#\n;]+)/,/^(?:accTitle\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*\{\s*)/,/^(?:[\}])/,/^(?:[^\}]*)/,/^(?:%%(?!\{)*[^\n]*(\r?\n?)+)/,/^(?:%%[^\n]*(\r?\n)*)/,/^(?:\s*(\r?\n)+)/,/^(?:\s+)/,/^(?:C4Context\b)/,/^(?:C4Container\b)/,/^(?:C4Component\b)/,/^(?:C4Dynamic\b)/,/^(?:C4Deployment\b)/,/^(?:Person_Ext\b)/,/^(?:Person\b)/,/^(?:SystemQueue_Ext\b)/,/^(?:SystemDb_Ext\b)/,/^(?:System_Ext\b)/,/^(?:SystemQueue\b)/,/^(?:SystemDb\b)/,/^(?:System\b)/,/^(?:Boundary\b)/,/^(?:Enterprise_Boundary\b)/,/^(?:System_Boundary\b)/,/^(?:ContainerQueue_Ext\b)/,/^(?:ContainerDb_Ext\b)/,/^(?:Container_Ext\b)/,/^(?:ContainerQueue\b)/,/^(?:ContainerDb\b)/,/^(?:Container\b)/,/^(?:Container_Boundary\b)/,/^(?:ComponentQueue_Ext\b)/,/^(?:ComponentDb_Ext\b)/,/^(?:Component_Ext\b)/,/^(?:ComponentQueue\b)/,/^(?:ComponentDb\b)/,/^(?:Component\b)/,/^(?:Deployment_Node\b)/,/^(?:Node\b)/,/^(?:Node_L\b)/,/^(?:Node_R\b)/,/^(?:Rel\b)/,/^(?:BiRel\b)/,/^(?:Rel_Up\b)/,/^(?:Rel_U\b)/,/^(?:Rel_Down\b)/,/^(?:Rel_D\b)/,/^(?:Rel_Left\b)/,/^(?:Rel_L\b)/,/^(?:Rel_Right\b)/,/^(?:Rel_R\b)/,/^(?:Rel_Back\b)/,/^(?:RelIndex\b)/,/^(?:UpdateElementStyle\b)/,/^(?:UpdateRelStyle\b)/,/^(?:UpdateLayoutConfig\b)/,/^(?:$)/,/^(?:[(][ ]*[,])/,/^(?:[(])/,/^(?:[)])/,/^(?:,,)/,/^(?:,)/,/^(?:[ ]*["]["])/,/^(?:[ ]*["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:[ ]*[\$])/,/^(?:[^=]*)/,/^(?:[=][ ]*["])/,/^(?:[^"]+)/,/^(?:["])/,/^(?:[^,]+)/,/^(?:\{)/,/^(?:\})/,/^(?:[\s]+)/,/^(?:[\n\r]+)/,/^(?:$)/],conditions:{acc_descr_multiline:{rules:[16,17],inclusive:!1},acc_descr:{rules:[14],inclusive:!1},acc_title:{rules:[12],inclusive:!1},close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[7,8],inclusive:!1},type_directive:{rules:[6,7],inclusive:!1},open_directive:{rules:[5],inclusive:!1},string_kv_value:{rules:[83,84],inclusive:!1},string_kv_key:{rules:[82],inclusive:!1},string_kv:{rules:[81],inclusive:!1},string:{rules:[78,79],inclusive:!1},attribute:{rules:[73,74,75,76,77,80,85],inclusive:!1},update_layout_config:{rules:[70,71,72,73],inclusive:!1},update_rel_style:{rules:[70,71,72,73],inclusive:!1},update_el_style:{rules:[70,71,72,73],inclusive:!1},rel_b:{rules:[70,71,72,73],inclusive:!1},rel_r:{rules:[70,71,72,73],inclusive:!1},rel_l:{rules:[70,71,72,73],inclusive:!1},rel_d:{rules:[70,71,72,73],inclusive:!1},rel_u:{rules:[70,71,72,73],inclusive:!1},rel_bi:{rules:[],inclusive:!1},rel:{rules:[70,71,72,73],inclusive:!1},node_r:{rules:[70,71,72,73],inclusive:!1},node_l:{rules:[70,71,72,73],inclusive:!1},node:{rules:[70,71,72,73],inclusive:!1},index:{rules:[],inclusive:!1},rel_index:{rules:[70,71,72,73],inclusive:!1},component_ext_queue:{rules:[],inclusive:!1},component_ext_db:{rules:[70,71,72,73],inclusive:!1},component_ext:{rules:[70,71,72,73],inclusive:!1},component_queue:{rules:[70,71,72,73],inclusive:!1},component_db:{rules:[70,71,72,73],inclusive:!1},component:{rules:[70,71,72,73],inclusive:!1},container_boundary:{rules:[70,71,72,73],inclusive:!1},container_ext_queue:{rules:[],inclusive:!1},container_ext_db:{rules:[70,71,72,73],inclusive:!1},container_ext:{rules:[70,71,72,73],inclusive:!1},container_queue:{rules:[70,71,72,73],inclusive:!1},container_db:{rules:[70,71,72,73],inclusive:!1},container:{rules:[70,71,72,73],inclusive:!1},birel:{rules:[70,71,72,73],inclusive:!1},system_boundary:{rules:[70,71,72,73],inclusive:!1},enterprise_boundary:{rules:[70,71,72,73],inclusive:!1},boundary:{rules:[70,71,72,73],inclusive:!1},system_ext_queue:{rules:[70,71,72,73],inclusive:!1},system_ext_db:{rules:[70,71,72,73],inclusive:!1},system_ext:{rules:[70,71,72,73],inclusive:!1},system_queue:{rules:[70,71,72,73],inclusive:!1},system_db:{rules:[70,71,72,73],inclusive:!1},system:{rules:[70,71,72,73],inclusive:!1},person_ext:{rules:[70,71,72,73],inclusive:!1},person:{rules:[70,71,72,73],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,9,10,11,13,15,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,86,87,88,89,90],inclusive:!0}}};function mt(){this.yy={}}return gt.lexer=yt,mt.prototype=gt,gt.Parser=mt,new mt}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(555).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},1362:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,3],n=[1,7],r=[1,8],i=[1,9],a=[1,10],o=[1,13],s=[1,12],c=[1,16,25],l=[1,20],u=[1,31],h=[1,32],f=[1,33],d=[1,35],p=[1,38],g=[1,36],y=[1,37],m=[1,39],b=[1,40],v=[1,41],_=[1,42],x=[1,45],k=[1,46],w=[1,47],T=[1,48],E=[16,25],C=[1,62],S=[1,63],A=[1,64],M=[1,65],N=[1,66],D=[1,67],O=[16,25,32,44,45,53,56,57,58,59,60,61,66,68],B=[16,25,30,32,44,45,49,53,56,57,58,59,60,61,66,68,83,84,85,86],L=[5,8,9,10,11,16,19,23,25],I=[53,83,84,85,86],F=[53,60,61,83,84,85,86],R=[53,56,57,58,59,83,84,85,86],P=[16,25,32],j=[1,99],z={trace:function(){},yy:{},symbols_:{error:2,start:3,mermaidDoc:4,statments:5,direction:6,directive:7,direction_tb:8,direction_bt:9,direction_rl:10,direction_lr:11,graphConfig:12,openDirective:13,typeDirective:14,closeDirective:15,NEWLINE:16,":":17,argDirective:18,open_directive:19,type_directive:20,arg_directive:21,close_directive:22,CLASS_DIAGRAM:23,statements:24,EOF:25,statement:26,className:27,alphaNumToken:28,classLiteralName:29,GENERICTYPE:30,relationStatement:31,LABEL:32,classStatement:33,methodStatement:34,annotationStatement:35,clickStatement:36,cssClassStatement:37,acc_title:38,acc_title_value:39,acc_descr:40,acc_descr_value:41,acc_descr_multiline_value:42,CLASS:43,STYLE_SEPARATOR:44,STRUCT_START:45,members:46,STRUCT_STOP:47,ANNOTATION_START:48,ANNOTATION_END:49,MEMBER:50,SEPARATOR:51,relation:52,STR:53,relationType:54,lineType:55,AGGREGATION:56,EXTENSION:57,COMPOSITION:58,DEPENDENCY:59,LINE:60,DOTTED_LINE:61,CALLBACK:62,LINK:63,LINK_TARGET:64,CLICK:65,CALLBACK_NAME:66,CALLBACK_ARGS:67,HREF:68,CSSCLASS:69,commentToken:70,textToken:71,graphCodeTokens:72,textNoTagsToken:73,TAGSTART:74,TAGEND:75,"==":76,"--":77,PCT:78,DEFAULT:79,SPACE:80,MINUS:81,keywords:82,UNICODE_TEXT:83,NUM:84,ALPHA:85,BQUOTE_STR:86,$accept:0,$end:1},terminals_:{2:"error",5:"statments",8:"direction_tb",9:"direction_bt",10:"direction_rl",11:"direction_lr",16:"NEWLINE",17:":",19:"open_directive",20:"type_directive",21:"arg_directive",22:"close_directive",23:"CLASS_DIAGRAM",25:"EOF",30:"GENERICTYPE",32:"LABEL",38:"acc_title",39:"acc_title_value",40:"acc_descr",41:"acc_descr_value",42:"acc_descr_multiline_value",43:"CLASS",44:"STYLE_SEPARATOR",45:"STRUCT_START",47:"STRUCT_STOP",48:"ANNOTATION_START",49:"ANNOTATION_END",50:"MEMBER",51:"SEPARATOR",53:"STR",56:"AGGREGATION",57:"EXTENSION",58:"COMPOSITION",59:"DEPENDENCY",60:"LINE",61:"DOTTED_LINE",62:"CALLBACK",63:"LINK",64:"LINK_TARGET",65:"CLICK",66:"CALLBACK_NAME",67:"CALLBACK_ARGS",68:"HREF",69:"CSSCLASS",72:"graphCodeTokens",74:"TAGSTART",75:"TAGEND",76:"==",77:"--",78:"PCT",79:"DEFAULT",80:"SPACE",81:"MINUS",82:"keywords",83:"UNICODE_TEXT",84:"NUM",85:"ALPHA",86:"BQUOTE_STR"},productions_:[0,[3,1],[3,1],[3,1],[3,2],[6,1],[6,1],[6,1],[6,1],[4,1],[7,4],[7,6],[13,1],[14,1],[18,1],[15,1],[12,4],[24,1],[24,2],[24,3],[27,1],[27,1],[27,2],[27,2],[27,2],[26,1],[26,2],[26,1],[26,1],[26,1],[26,1],[26,1],[26,1],[26,1],[26,2],[26,2],[26,1],[33,2],[33,4],[33,5],[33,7],[35,4],[46,1],[46,2],[34,1],[34,2],[34,1],[34,1],[31,3],[31,4],[31,4],[31,5],[52,3],[52,2],[52,2],[52,1],[54,1],[54,1],[54,1],[54,1],[55,1],[55,1],[36,3],[36,4],[36,3],[36,4],[36,4],[36,5],[36,3],[36,4],[36,4],[36,5],[36,3],[36,4],[36,4],[36,5],[37,3],[70,1],[70,1],[71,1],[71,1],[71,1],[71,1],[71,1],[71,1],[71,1],[73,1],[73,1],[73,1],[73,1],[28,1],[28,1],[28,1],[29,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 5:r.setDirection("TB");break;case 6:r.setDirection("BT");break;case 7:r.setDirection("RL");break;case 8:r.setDirection("LR");break;case 12:r.parseDirective("%%{","open_directive");break;case 13:r.parseDirective(a[s],"type_directive");break;case 14:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 15:r.parseDirective("}%%","close_directive","class");break;case 20:case 21:this.$=a[s];break;case 22:this.$=a[s-1]+a[s];break;case 23:case 24:this.$=a[s-1]+"~"+a[s];break;case 25:r.addRelation(a[s]);break;case 26:a[s-1].title=r.cleanupLabel(a[s]),r.addRelation(a[s-1]);break;case 34:this.$=a[s].trim(),r.setAccTitle(this.$);break;case 35:case 36:this.$=a[s].trim(),r.setAccDescription(this.$);break;case 37:r.addClass(a[s]);break;case 38:r.addClass(a[s-2]),r.setCssClass(a[s-2],a[s]);break;case 39:r.addClass(a[s-3]),r.addMembers(a[s-3],a[s-1]);break;case 40:r.addClass(a[s-5]),r.setCssClass(a[s-5],a[s-3]),r.addMembers(a[s-5],a[s-1]);break;case 41:r.addAnnotation(a[s],a[s-2]);break;case 42:this.$=[a[s]];break;case 43:a[s].push(a[s-1]),this.$=a[s];break;case 44:case 46:case 47:break;case 45:r.addMember(a[s-1],r.cleanupLabel(a[s]));break;case 48:this.$={id1:a[s-2],id2:a[s],relation:a[s-1],relationTitle1:"none",relationTitle2:"none"};break;case 49:this.$={id1:a[s-3],id2:a[s],relation:a[s-1],relationTitle1:a[s-2],relationTitle2:"none"};break;case 50:this.$={id1:a[s-3],id2:a[s],relation:a[s-2],relationTitle1:"none",relationTitle2:a[s-1]};break;case 51:this.$={id1:a[s-4],id2:a[s],relation:a[s-2],relationTitle1:a[s-3],relationTitle2:a[s-1]};break;case 52:this.$={type1:a[s-2],type2:a[s],lineType:a[s-1]};break;case 53:this.$={type1:"none",type2:a[s],lineType:a[s-1]};break;case 54:this.$={type1:a[s-1],type2:"none",lineType:a[s]};break;case 55:this.$={type1:"none",type2:"none",lineType:a[s]};break;case 56:this.$=r.relationType.AGGREGATION;break;case 57:this.$=r.relationType.EXTENSION;break;case 58:this.$=r.relationType.COMPOSITION;break;case 59:this.$=r.relationType.DEPENDENCY;break;case 60:this.$=r.lineType.LINE;break;case 61:this.$=r.lineType.DOTTED_LINE;break;case 62:case 68:this.$=a[s-2],r.setClickEvent(a[s-1],a[s]);break;case 63:case 69:this.$=a[s-3],r.setClickEvent(a[s-2],a[s-1]),r.setTooltip(a[s-2],a[s]);break;case 64:case 72:this.$=a[s-2],r.setLink(a[s-1],a[s]);break;case 65:case 73:this.$=a[s-3],r.setLink(a[s-2],a[s-1],a[s]);break;case 66:case 74:this.$=a[s-3],r.setLink(a[s-2],a[s-1]),r.setTooltip(a[s-2],a[s]);break;case 67:case 75:this.$=a[s-4],r.setLink(a[s-3],a[s-2],a[s]),r.setTooltip(a[s-3],a[s-1]);break;case 70:this.$=a[s-3],r.setClickEvent(a[s-2],a[s-1],a[s]);break;case 71:this.$=a[s-4],r.setClickEvent(a[s-3],a[s-2],a[s-1]),r.setTooltip(a[s-3],a[s]);break;case 76:r.setCssClass(a[s-1],a[s])}},table:[{3:1,4:2,5:e,6:4,7:5,8:n,9:r,10:i,11:a,12:6,13:11,19:o,23:s},{1:[3]},{1:[2,1]},{1:[2,2]},{1:[2,3]},{3:14,4:2,5:e,6:4,7:5,8:n,9:r,10:i,11:a,12:6,13:11,19:o,23:s},{1:[2,9]},t(c,[2,5]),t(c,[2,6]),t(c,[2,7]),t(c,[2,8]),{14:15,20:[1,16]},{16:[1,17]},{20:[2,12]},{1:[2,4]},{15:18,17:[1,19],22:l},t([17,22],[2,13]),{6:30,7:29,8:n,9:r,10:i,11:a,13:11,19:o,24:21,26:22,27:34,28:43,29:44,31:23,33:24,34:25,35:26,36:27,37:28,38:u,40:h,42:f,43:d,48:p,50:g,51:y,62:m,63:b,65:v,69:_,83:x,84:k,85:w,86:T},{16:[1,49]},{18:50,21:[1,51]},{16:[2,15]},{25:[1,52]},{16:[1,53],25:[2,17]},t(E,[2,25],{32:[1,54]}),t(E,[2,27]),t(E,[2,28]),t(E,[2,29]),t(E,[2,30]),t(E,[2,31]),t(E,[2,32]),t(E,[2,33]),{39:[1,55]},{41:[1,56]},t(E,[2,36]),t(E,[2,44],{52:57,54:60,55:61,32:[1,59],53:[1,58],56:C,57:S,58:A,59:M,60:N,61:D}),{27:68,28:43,29:44,83:x,84:k,85:w,86:T},t(E,[2,46]),t(E,[2,47]),{28:69,83:x,84:k,85:w},{27:70,28:43,29:44,83:x,84:k,85:w,86:T},{27:71,28:43,29:44,83:x,84:k,85:w,86:T},{27:72,28:43,29:44,83:x,84:k,85:w,86:T},{53:[1,73]},t(O,[2,20],{28:43,29:44,27:74,30:[1,75],83:x,84:k,85:w,86:T}),t(O,[2,21],{30:[1,76]}),t(B,[2,90]),t(B,[2,91]),t(B,[2,92]),t([16,25,30,32,44,45,53,56,57,58,59,60,61,66,68],[2,93]),t(L,[2,10]),{15:77,22:l},{22:[2,14]},{1:[2,16]},{6:30,7:29,8:n,9:r,10:i,11:a,13:11,19:o,24:78,25:[2,18],26:22,27:34,28:43,29:44,31:23,33:24,34:25,35:26,36:27,37:28,38:u,40:h,42:f,43:d,48:p,50:g,51:y,62:m,63:b,65:v,69:_,83:x,84:k,85:w,86:T},t(E,[2,26]),t(E,[2,34]),t(E,[2,35]),{27:79,28:43,29:44,53:[1,80],83:x,84:k,85:w,86:T},{52:81,54:60,55:61,56:C,57:S,58:A,59:M,60:N,61:D},t(E,[2,45]),{55:82,60:N,61:D},t(I,[2,55],{54:83,56:C,57:S,58:A,59:M}),t(F,[2,56]),t(F,[2,57]),t(F,[2,58]),t(F,[2,59]),t(R,[2,60]),t(R,[2,61]),t(E,[2,37],{44:[1,84],45:[1,85]}),{49:[1,86]},{53:[1,87]},{53:[1,88]},{66:[1,89],68:[1,90]},{28:91,83:x,84:k,85:w},t(O,[2,22]),t(O,[2,23]),t(O,[2,24]),{16:[1,92]},{25:[2,19]},t(P,[2,48]),{27:93,28:43,29:44,83:x,84:k,85:w,86:T},{27:94,28:43,29:44,53:[1,95],83:x,84:k,85:w,86:T},t(I,[2,54],{54:96,56:C,57:S,58:A,59:M}),t(I,[2,53]),{28:97,83:x,84:k,85:w},{46:98,50:j},{27:100,28:43,29:44,83:x,84:k,85:w,86:T},t(E,[2,62],{53:[1,101]}),t(E,[2,64],{53:[1,103],64:[1,102]}),t(E,[2,68],{53:[1,104],67:[1,105]}),t(E,[2,72],{53:[1,107],64:[1,106]}),t(E,[2,76]),t(L,[2,11]),t(P,[2,50]),t(P,[2,49]),{27:108,28:43,29:44,83:x,84:k,85:w,86:T},t(I,[2,52]),t(E,[2,38],{45:[1,109]}),{47:[1,110]},{46:111,47:[2,42],50:j},t(E,[2,41]),t(E,[2,63]),t(E,[2,65]),t(E,[2,66],{64:[1,112]}),t(E,[2,69]),t(E,[2,70],{53:[1,113]}),t(E,[2,73]),t(E,[2,74],{64:[1,114]}),t(P,[2,51]),{46:115,50:j},t(E,[2,39]),{47:[2,43]},t(E,[2,67]),t(E,[2,71]),t(E,[2,75]),{47:[1,116]},t(E,[2,40])],defaultActions:{2:[2,1],3:[2,2],4:[2,3],6:[2,9],13:[2,12],14:[2,4],20:[2,15],51:[2,14],52:[2,16],78:[2,19],111:[2,43]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,l=0,u=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var m=p.yylloc;a.push(m);var b=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,x,k,w,T,E,C,S,A,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==_&&(_=v()),w=o[k]&&o[k][_]),void 0===w||!w.length||!w[0]){var N="";for(E in A=[],o[k])this.terminals_[E]&&E>h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(c+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:m,expected:A})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+_);switch(w[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),_=null,x?(_=x,x=null):(l=p.yyleng,s=p.yytext,c=p.yylineno,m=p.yylloc,u>0&&u--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},b&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,l,c,g.yy,w[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},Y={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),19;case 1:return 8;case 2:return 9;case 3:return 10;case 4:return 11;case 5:return this.begin("type_directive"),20;case 6:return this.popState(),this.begin("arg_directive"),17;case 7:return this.popState(),this.popState(),22;case 8:return 21;case 9:case 10:case 19:case 26:break;case 11:return this.begin("acc_title"),38;case 12:return this.popState(),"acc_title_value";case 13:return this.begin("acc_descr"),40;case 14:return this.popState(),"acc_descr_value";case 15:this.begin("acc_descr_multiline");break;case 16:case 36:case 39:case 42:case 45:case 48:case 51:this.popState();break;case 17:return"acc_descr_multiline_value";case 18:return 16;case 20:case 21:return 23;case 22:return this.begin("struct"),45;case 23:return"EOF_IN_STRUCT";case 24:return"OPEN_IN_STRUCT";case 25:return this.popState(),47;case 27:return"MEMBER";case 28:return 43;case 29:return 69;case 30:return 62;case 31:return 63;case 32:return 65;case 33:return 48;case 34:return 49;case 35:this.begin("generic");break;case 37:return"GENERICTYPE";case 38:this.begin("string");break;case 40:return"STR";case 41:this.begin("bqstring");break;case 43:return"BQUOTE_STR";case 44:this.begin("href");break;case 46:return 68;case 47:this.begin("callback_name");break;case 49:this.popState(),this.begin("callback_args");break;case 50:return 66;case 52:return 67;case 53:case 54:case 55:case 56:return 64;case 57:case 58:return 57;case 59:case 60:return 59;case 61:return 58;case 62:return 56;case 63:return 60;case 64:return 61;case 65:return 32;case 66:return 44;case 67:return 81;case 68:return"DOT";case 69:return"PLUS";case 70:return 78;case 71:case 72:return"EQUALS";case 73:return 85;case 74:return"PUNCTUATION";case 75:return 84;case 76:return 83;case 77:return 80;case 78:return 25}},rules:[/^(?:%%\{)/,/^(?:.*direction\s+TB[^\n]*)/,/^(?:.*direction\s+BT[^\n]*)/,/^(?:.*direction\s+RL[^\n]*)/,/^(?:.*direction\s+LR[^\n]*)/,/^(?:((?:(?!\}%%)[^:.])*))/,/^(?::)/,/^(?:\}%%)/,/^(?:((?:(?!\}%%).|\n)*))/,/^(?:%%(?!\{)*[^\n]*(\r?\n?)+)/,/^(?:%%[^\n]*(\r?\n)*)/,/^(?:accTitle\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*\{\s*)/,/^(?:[\}])/,/^(?:[^\}]*)/,/^(?:\s*(\r?\n)+)/,/^(?:\s+)/,/^(?:classDiagram-v2\b)/,/^(?:classDiagram\b)/,/^(?:[{])/,/^(?:$)/,/^(?:[{])/,/^(?:[}])/,/^(?:[\n])/,/^(?:[^{}\n]*)/,/^(?:class\b)/,/^(?:cssClass\b)/,/^(?:callback\b)/,/^(?:link\b)/,/^(?:click\b)/,/^(?:<<)/,/^(?:>>)/,/^(?:[~])/,/^(?:[~])/,/^(?:[^~]*)/,/^(?:["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:[`])/,/^(?:[`])/,/^(?:[^`]+)/,/^(?:href[\s]+["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:call[\s]+)/,/^(?:\([\s]*\))/,/^(?:\()/,/^(?:[^(]*)/,/^(?:\))/,/^(?:[^)]*)/,/^(?:_self\b)/,/^(?:_blank\b)/,/^(?:_parent\b)/,/^(?:_top\b)/,/^(?:\s*<\|)/,/^(?:\s*\|>)/,/^(?:\s*>)/,/^(?:\s*<)/,/^(?:\s*\*)/,/^(?:\s*o\b)/,/^(?:--)/,/^(?:\.\.)/,/^(?::{1}[^:\n;]+)/,/^(?::{3})/,/^(?:-)/,/^(?:\.)/,/^(?:\+)/,/^(?:%)/,/^(?:=)/,/^(?:=)/,/^(?:\w+)/,/^(?:[!"#$%&'*+,-.`?\\/])/,/^(?:[0-9]+)/,/^(?:[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|[\uFFD2-\uFFD7\uFFDA-\uFFDC])/,/^(?:\s)/,/^(?:$)/],conditions:{acc_descr_multiline:{rules:[16,17],inclusive:!1},acc_descr:{rules:[14],inclusive:!1},acc_title:{rules:[12],inclusive:!1},arg_directive:{rules:[7,8],inclusive:!1},type_directive:{rules:[6,7],inclusive:!1},open_directive:{rules:[5],inclusive:!1},callback_args:{rules:[51,52],inclusive:!1},callback_name:{rules:[48,49,50],inclusive:!1},href:{rules:[45,46],inclusive:!1},struct:{rules:[23,24,25,26,27],inclusive:!1},generic:{rules:[36,37],inclusive:!1},bqstring:{rules:[42,43],inclusive:!1},string:{rules:[39,40],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,9,10,11,13,15,18,19,20,21,22,28,29,30,31,32,33,34,35,38,41,44,47,53,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78],inclusive:!0}}};function U(){this.yy={}}return z.lexer=Y,U.prototype=z,z.Parser=U,new U}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(8218).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},5890:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,5],r=[6,9,11,23,25,27,29,30,49],i=[1,17],a=[1,18],o=[1,19],s=[1,20],c=[1,21],l=[1,24],u=[1,29],h=[1,30],f=[1,31],d=[1,32],p=[6,9,11,15,20,23,25,27,29,30,42,43,44,45,49],g=[1,45],y=[30,46,47],m=[4,6,9,11,23,25,27,29,30,49],b=[42,43,44,45],v=[22,37],_=[1,64],x={trace:function(){},yy:{},symbols_:{error:2,start:3,ER_DIAGRAM:4,document:5,EOF:6,directive:7,line:8,SPACE:9,statement:10,NEWLINE:11,openDirective:12,typeDirective:13,closeDirective:14,":":15,argDirective:16,entityName:17,relSpec:18,role:19,BLOCK_START:20,attributes:21,BLOCK_STOP:22,title:23,title_value:24,acc_title:25,acc_title_value:26,acc_descr:27,acc_descr_value:28,acc_descr_multiline_value:29,ALPHANUM:30,".":31,attribute:32,attributeType:33,attributeName:34,attributeKeyType:35,attributeComment:36,ATTRIBUTE_WORD:37,ATTRIBUTE_KEY:38,COMMENT:39,cardinality:40,relType:41,ZERO_OR_ONE:42,ZERO_OR_MORE:43,ONE_OR_MORE:44,ONLY_ONE:45,NON_IDENTIFYING:46,IDENTIFYING:47,WORD:48,open_directive:49,type_directive:50,arg_directive:51,close_directive:52,$accept:0,$end:1},terminals_:{2:"error",4:"ER_DIAGRAM",6:"EOF",9:"SPACE",11:"NEWLINE",15:":",20:"BLOCK_START",22:"BLOCK_STOP",23:"title",24:"title_value",25:"acc_title",26:"acc_title_value",27:"acc_descr",28:"acc_descr_value",29:"acc_descr_multiline_value",30:"ALPHANUM",31:".",37:"ATTRIBUTE_WORD",38:"ATTRIBUTE_KEY",39:"COMMENT",42:"ZERO_OR_ONE",43:"ZERO_OR_MORE",44:"ONE_OR_MORE",45:"ONLY_ONE",46:"NON_IDENTIFYING",47:"IDENTIFYING",48:"WORD",49:"open_directive",50:"type_directive",51:"arg_directive",52:"close_directive"},productions_:[0,[3,3],[3,2],[5,0],[5,2],[8,2],[8,1],[8,1],[8,1],[7,4],[7,6],[10,1],[10,5],[10,4],[10,3],[10,1],[10,2],[10,2],[10,2],[10,1],[17,1],[17,3],[21,1],[21,2],[32,2],[32,3],[32,3],[32,4],[33,1],[34,1],[35,1],[36,1],[18,3],[40,1],[40,1],[40,1],[40,1],[41,1],[41,1],[19,1],[19,1],[12,1],[13,1],[16,1],[14,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 1:break;case 3:case 7:case 8:this.$=[];break;case 4:a[s-1].push(a[s]),this.$=a[s-1];break;case 5:case 6:case 20:case 28:case 29:case 30:case 40:this.$=a[s];break;case 12:r.addEntity(a[s-4]),r.addEntity(a[s-2]),r.addRelationship(a[s-4],a[s],a[s-2],a[s-3]);break;case 13:r.addEntity(a[s-3]),r.addAttributes(a[s-3],a[s-1]);break;case 14:r.addEntity(a[s-2]);break;case 15:r.addEntity(a[s]);break;case 16:case 17:this.$=a[s].trim(),r.setAccTitle(this.$);break;case 18:case 19:this.$=a[s].trim(),r.setAccDescription(this.$);break;case 21:this.$=a[s-2]+a[s-1]+a[s];break;case 22:this.$=[a[s]];break;case 23:a[s].push(a[s-1]),this.$=a[s];break;case 24:this.$={attributeType:a[s-1],attributeName:a[s]};break;case 25:this.$={attributeType:a[s-2],attributeName:a[s-1],attributeKeyType:a[s]};break;case 26:this.$={attributeType:a[s-2],attributeName:a[s-1],attributeComment:a[s]};break;case 27:this.$={attributeType:a[s-3],attributeName:a[s-2],attributeKeyType:a[s-1],attributeComment:a[s]};break;case 31:case 39:this.$=a[s].replace(/"/g,"");break;case 32:this.$={cardA:a[s],relType:a[s-1],cardB:a[s-2]};break;case 33:this.$=r.Cardinality.ZERO_OR_ONE;break;case 34:this.$=r.Cardinality.ZERO_OR_MORE;break;case 35:this.$=r.Cardinality.ONE_OR_MORE;break;case 36:this.$=r.Cardinality.ONLY_ONE;break;case 37:this.$=r.Identification.NON_IDENTIFYING;break;case 38:this.$=r.Identification.IDENTIFYING;break;case 41:r.parseDirective("%%{","open_directive");break;case 42:r.parseDirective(a[s],"type_directive");break;case 43:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 44:r.parseDirective("}%%","close_directive","er")}},table:[{3:1,4:e,7:3,12:4,49:n},{1:[3]},t(r,[2,3],{5:6}),{3:7,4:e,7:3,12:4,49:n},{13:8,50:[1,9]},{50:[2,41]},{6:[1,10],7:15,8:11,9:[1,12],10:13,11:[1,14],12:4,17:16,23:i,25:a,27:o,29:s,30:c,49:n},{1:[2,2]},{14:22,15:[1,23],52:l},t([15,52],[2,42]),t(r,[2,8],{1:[2,1]}),t(r,[2,4]),{7:15,10:25,12:4,17:16,23:i,25:a,27:o,29:s,30:c,49:n},t(r,[2,6]),t(r,[2,7]),t(r,[2,11]),t(r,[2,15],{18:26,40:28,20:[1,27],42:u,43:h,44:f,45:d}),{24:[1,33]},{26:[1,34]},{28:[1,35]},t(r,[2,19]),t(p,[2,20],{31:[1,36]}),{11:[1,37]},{16:38,51:[1,39]},{11:[2,44]},t(r,[2,5]),{17:40,30:c},{21:41,22:[1,42],32:43,33:44,37:g},{41:46,46:[1,47],47:[1,48]},t(y,[2,33]),t(y,[2,34]),t(y,[2,35]),t(y,[2,36]),t(r,[2,16]),t(r,[2,17]),t(r,[2,18]),{17:49,30:c},t(m,[2,9]),{14:50,52:l},{52:[2,43]},{15:[1,51]},{22:[1,52]},t(r,[2,14]),{21:53,22:[2,22],32:43,33:44,37:g},{34:54,37:[1,55]},{37:[2,28]},{40:56,42:u,43:h,44:f,45:d},t(b,[2,37]),t(b,[2,38]),t(p,[2,21]),{11:[1,57]},{19:58,30:[1,60],48:[1,59]},t(r,[2,13]),{22:[2,23]},t(v,[2,24],{35:61,36:62,38:[1,63],39:_}),t([22,37,38,39],[2,29]),{30:[2,32]},t(m,[2,10]),t(r,[2,12]),t(r,[2,39]),t(r,[2,40]),t(v,[2,25],{36:65,39:_}),t(v,[2,26]),t([22,37,39],[2,30]),t(v,[2,31]),t(v,[2,27])],defaultActions:{5:[2,41],7:[2,2],24:[2,44],39:[2,43],45:[2,28],53:[2,23],56:[2,32]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,l=0,u=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var m=p.yylloc;a.push(m);var b=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,x,k,w,T,E,C,S,A,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==_&&(_=v()),w=o[k]&&o[k][_]),void 0===w||!w.length||!w[0]){var N="";for(E in A=[],o[k])this.terminals_[E]&&E>h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(c+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:m,expected:A})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+_);switch(w[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),_=null,x?(_=x,x=null):(l=p.yyleng,s=p.yytext,c=p.yylineno,m=p.yylloc,u>0&&u--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},b&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,l,c,g.yy,w[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},k={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("acc_title"),25;case 1:return this.popState(),"acc_title_value";case 2:return this.begin("acc_descr"),27;case 3:return this.popState(),"acc_descr_value";case 4:this.begin("acc_descr_multiline");break;case 5:this.popState();break;case 6:return"acc_descr_multiline_value";case 7:return this.begin("open_directive"),49;case 8:return this.begin("type_directive"),50;case 9:return this.popState(),this.begin("arg_directive"),15;case 10:return this.popState(),this.popState(),52;case 11:return 51;case 12:case 13:case 15:case 20:case 24:break;case 14:return 11;case 16:return 9;case 17:return 48;case 18:return 4;case 19:return this.begin("block"),20;case 21:return 38;case 22:return 37;case 23:return 39;case 25:return this.popState(),22;case 26:case 39:return e.yytext[0];case 27:case 31:return 42;case 28:case 32:return 43;case 29:case 33:return 44;case 30:return 45;case 34:case 36:case 37:return 46;case 35:return 47;case 38:return 30;case 40:return 6}},rules:[/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:[\s]+)/i,/^(?:"[^"]*")/i,/^(?:erDiagram\b)/i,/^(?:\{)/i,/^(?:\s+)/i,/^(?:\b((?:PK)|(?:FK))\b)/i,/^(?:[A-Za-z][A-Za-z0-9\-_]*)/i,/^(?:"[^"]*")/i,/^(?:[\n]+)/i,/^(?:\})/i,/^(?:.)/i,/^(?:\|o\b)/i,/^(?:\}o\b)/i,/^(?:\}\|)/i,/^(?:\|\|)/i,/^(?:o\|)/i,/^(?:o\{)/i,/^(?:\|\{)/i,/^(?:\.\.)/i,/^(?:--)/i,/^(?:\.-)/i,/^(?:-\.)/i,/^(?:[A-Za-z][A-Za-z0-9\-_]*)/i,/^(?:.)/i,/^(?:$)/i],conditions:{acc_descr_multiline:{rules:[5,6],inclusive:!1},acc_descr:{rules:[3],inclusive:!1},acc_title:{rules:[1],inclusive:!1},open_directive:{rules:[8],inclusive:!1},type_directive:{rules:[9,10],inclusive:!1},arg_directive:{rules:[10,11],inclusive:!1},block:{rules:[20,21,22,23,24,25,26],inclusive:!1},INITIAL:{rules:[0,2,4,7,12,13,14,15,16,17,18,19,27,28,29,30,31,32,33,34,35,36,37,38,39,40],inclusive:!0}}};function w(){this.yy={}}return x.lexer=k,w.prototype=x,x.Parser=w,new w}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(8009).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},3602:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,9],n=[1,7],r=[1,6],i=[1,8],a=[1,20,21,22,23,38,44,46,48,52,66,67,86,87,88,89,90,91,95,105,106,109,111,112,118,119,120,121,122,123,124,125,126,127],o=[2,10],s=[1,20],c=[1,21],l=[1,22],u=[1,23],h=[1,30],f=[1,32],d=[1,33],p=[1,34],g=[1,62],y=[1,48],m=[1,52],b=[1,36],v=[1,37],_=[1,38],x=[1,39],k=[1,40],w=[1,56],T=[1,63],E=[1,51],C=[1,53],S=[1,55],A=[1,59],M=[1,60],N=[1,41],D=[1,42],O=[1,43],B=[1,44],L=[1,61],I=[1,50],F=[1,54],R=[1,57],P=[1,58],j=[1,49],z=[1,66],Y=[1,71],U=[1,20,21,22,23,38,42,44,46,48,52,66,67,86,87,88,89,90,91,95,105,106,109,111,112,118,119,120,121,122,123,124,125,126,127],$=[1,75],W=[1,74],q=[1,76],H=[20,21,23,81,82],V=[1,99],G=[1,104],X=[1,107],Z=[1,108],Q=[1,101],K=[1,106],J=[1,109],tt=[1,102],et=[1,114],nt=[1,113],rt=[1,103],it=[1,105],at=[1,110],ot=[1,111],st=[1,112],ct=[1,115],lt=[20,21,22,23,81,82],ut=[20,21,22,23,53,81,82],ht=[20,21,22,23,40,52,53,55,57,59,61,63,65,66,67,69,71,73,74,76,81,82,91,95,105,106,109,111,112,122,123,124,125,126,127],ft=[20,21,23],dt=[20,21,23,52,66,67,81,82,91,95,105,106,109,111,112,122,123,124,125,126,127],pt=[1,12,20,21,22,23,24,38,42,44,46,48,52,66,67,86,87,88,89,90,91,95,105,106,109,111,112,118,119,120,121,122,123,124,125,126,127],gt=[52,66,67,91,95,105,106,109,111,112,122,123,124,125,126,127],yt=[1,149],mt=[1,157],bt=[1,158],vt=[1,159],_t=[1,160],xt=[1,144],kt=[1,145],wt=[1,141],Tt=[1,152],Et=[1,153],Ct=[1,154],St=[1,155],At=[1,156],Mt=[1,161],Nt=[1,162],Dt=[1,147],Ot=[1,150],Bt=[1,146],Lt=[1,143],It=[20,21,22,23,38,42,44,46,48,52,66,67,86,87,88,89,90,91,95,105,106,109,111,112,118,119,120,121,122,123,124,125,126,127],Ft=[1,165],Rt=[20,21,22,23,26,52,66,67,91,105,106,109,111,112,122,123,124,125,126,127],Pt=[20,21,22,23,24,26,38,40,41,42,52,56,58,60,62,64,66,67,68,70,72,73,75,77,81,82,86,87,88,89,90,91,92,95,105,106,109,111,112,113,114,122,123,124,125,126,127],jt=[12,21,22,24],zt=[22,106],Yt=[1,250],Ut=[1,245],$t=[1,246],Wt=[1,254],qt=[1,251],Ht=[1,248],Vt=[1,247],Gt=[1,249],Xt=[1,252],Zt=[1,253],Qt=[1,255],Kt=[1,273],Jt=[20,21,23,106],te=[20,21,22,23,66,67,86,102,105,106,109,110,111,112,113],ee={trace:function(){},yy:{},symbols_:{error:2,start:3,mermaidDoc:4,directive:5,openDirective:6,typeDirective:7,closeDirective:8,separator:9,":":10,argDirective:11,open_directive:12,type_directive:13,arg_directive:14,close_directive:15,graphConfig:16,document:17,line:18,statement:19,SEMI:20,NEWLINE:21,SPACE:22,EOF:23,GRAPH:24,NODIR:25,DIR:26,FirstStmtSeperator:27,ending:28,endToken:29,spaceList:30,spaceListNewline:31,verticeStatement:32,styleStatement:33,linkStyleStatement:34,classDefStatement:35,classStatement:36,clickStatement:37,subgraph:38,text:39,SQS:40,SQE:41,end:42,direction:43,acc_title:44,acc_title_value:45,acc_descr:46,acc_descr_value:47,acc_descr_multiline_value:48,link:49,node:50,vertex:51,AMP:52,STYLE_SEPARATOR:53,idString:54,DOUBLECIRCLESTART:55,DOUBLECIRCLEEND:56,PS:57,PE:58,"(-":59,"-)":60,STADIUMSTART:61,STADIUMEND:62,SUBROUTINESTART:63,SUBROUTINEEND:64,VERTEX_WITH_PROPS_START:65,ALPHA:66,COLON:67,PIPE:68,CYLINDERSTART:69,CYLINDEREND:70,DIAMOND_START:71,DIAMOND_STOP:72,TAGEND:73,TRAPSTART:74,TRAPEND:75,INVTRAPSTART:76,INVTRAPEND:77,linkStatement:78,arrowText:79,TESTSTR:80,START_LINK:81,LINK:82,textToken:83,STR:84,keywords:85,STYLE:86,LINKSTYLE:87,CLASSDEF:88,CLASS:89,CLICK:90,DOWN:91,UP:92,textNoTags:93,textNoTagsToken:94,DEFAULT:95,stylesOpt:96,alphaNum:97,CALLBACKNAME:98,CALLBACKARGS:99,HREF:100,LINK_TARGET:101,HEX:102,numList:103,INTERPOLATE:104,NUM:105,COMMA:106,style:107,styleComponent:108,MINUS:109,UNIT:110,BRKT:111,DOT:112,PCT:113,TAGSTART:114,alphaNumToken:115,idStringToken:116,alphaNumStatement:117,direction_tb:118,direction_bt:119,direction_rl:120,direction_lr:121,PUNCTUATION:122,UNICODE_TEXT:123,PLUS:124,EQUALS:125,MULT:126,UNDERSCORE:127,graphCodeTokens:128,ARROW_CROSS:129,ARROW_POINT:130,ARROW_CIRCLE:131,ARROW_OPEN:132,QUOTE:133,$accept:0,$end:1},terminals_:{2:"error",10:":",12:"open_directive",13:"type_directive",14:"arg_directive",15:"close_directive",20:"SEMI",21:"NEWLINE",22:"SPACE",23:"EOF",24:"GRAPH",25:"NODIR",26:"DIR",38:"subgraph",40:"SQS",41:"SQE",42:"end",44:"acc_title",45:"acc_title_value",46:"acc_descr",47:"acc_descr_value",48:"acc_descr_multiline_value",52:"AMP",53:"STYLE_SEPARATOR",55:"DOUBLECIRCLESTART",56:"DOUBLECIRCLEEND",57:"PS",58:"PE",59:"(-",60:"-)",61:"STADIUMSTART",62:"STADIUMEND",63:"SUBROUTINESTART",64:"SUBROUTINEEND",65:"VERTEX_WITH_PROPS_START",66:"ALPHA",67:"COLON",68:"PIPE",69:"CYLINDERSTART",70:"CYLINDEREND",71:"DIAMOND_START",72:"DIAMOND_STOP",73:"TAGEND",74:"TRAPSTART",75:"TRAPEND",76:"INVTRAPSTART",77:"INVTRAPEND",80:"TESTSTR",81:"START_LINK",82:"LINK",84:"STR",86:"STYLE",87:"LINKSTYLE",88:"CLASSDEF",89:"CLASS",90:"CLICK",91:"DOWN",92:"UP",95:"DEFAULT",98:"CALLBACKNAME",99:"CALLBACKARGS",100:"HREF",101:"LINK_TARGET",102:"HEX",104:"INTERPOLATE",105:"NUM",106:"COMMA",109:"MINUS",110:"UNIT",111:"BRKT",112:"DOT",113:"PCT",114:"TAGSTART",118:"direction_tb",119:"direction_bt",120:"direction_rl",121:"direction_lr",122:"PUNCTUATION",123:"UNICODE_TEXT",124:"PLUS",125:"EQUALS",126:"MULT",127:"UNDERSCORE",129:"ARROW_CROSS",130:"ARROW_POINT",131:"ARROW_CIRCLE",132:"ARROW_OPEN",133:"QUOTE"},productions_:[0,[3,1],[3,2],[5,4],[5,6],[6,1],[7,1],[11,1],[8,1],[4,2],[17,0],[17,2],[18,1],[18,1],[18,1],[18,1],[18,1],[16,2],[16,2],[16,2],[16,3],[28,2],[28,1],[29,1],[29,1],[29,1],[27,1],[27,1],[27,2],[31,2],[31,2],[31,1],[31,1],[30,2],[30,1],[19,2],[19,2],[19,2],[19,2],[19,2],[19,2],[19,9],[19,6],[19,4],[19,1],[19,2],[19,2],[19,1],[9,1],[9,1],[9,1],[32,3],[32,4],[32,2],[32,1],[50,1],[50,5],[50,3],[51,4],[51,4],[51,6],[51,4],[51,4],[51,4],[51,8],[51,4],[51,4],[51,4],[51,6],[51,4],[51,4],[51,4],[51,4],[51,4],[51,1],[49,2],[49,3],[49,3],[49,1],[49,3],[78,1],[79,3],[39,1],[39,2],[39,1],[85,1],[85,1],[85,1],[85,1],[85,1],[85,1],[85,1],[85,1],[85,1],[85,1],[85,1],[93,1],[93,2],[35,5],[35,5],[36,5],[37,2],[37,4],[37,3],[37,5],[37,2],[37,4],[37,4],[37,6],[37,2],[37,4],[37,2],[37,4],[37,4],[37,6],[33,5],[33,5],[34,5],[34,5],[34,9],[34,9],[34,7],[34,7],[103,1],[103,3],[96,1],[96,3],[107,1],[107,2],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[108,1],[83,1],[83,1],[83,1],[83,1],[83,1],[83,1],[94,1],[94,1],[94,1],[94,1],[54,1],[54,2],[97,1],[97,2],[117,1],[117,1],[117,1],[117,1],[43,1],[43,1],[43,1],[43,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[115,1],[116,1],[116,1],[116,1],[116,1],[116,1],[116,1],[116,1],[116,1],[116,1],[116,1],[116,1],[116,1],[116,1],[116,1],[116,1],[116,1],[128,1],[128,1],[128,1],[128,1],[128,1],[128,1],[128,1],[128,1],[128,1],[128,1],[128,1],[128,1],[128,1],[128,1],[128,1],[128,1],[128,1],[128,1],[128,1],[128,1],[128,1],[128,1],[128,1],[128,1],[128,1],[128,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 5:r.parseDirective("%%{","open_directive");break;case 6:r.parseDirective(a[s],"type_directive");break;case 7:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 8:r.parseDirective("}%%","close_directive","flowchart");break;case 10:case 36:case 37:case 38:case 39:case 40:this.$=[];break;case 11:a[s]!==[]&&a[s-1].push(a[s]),this.$=a[s-1];break;case 12:case 82:case 84:case 96:case 152:case 154:case 155:case 78:case 150:this.$=a[s];break;case 19:r.setDirection("TB"),this.$="TB";break;case 20:r.setDirection(a[s-1]),this.$=a[s-1];break;case 35:this.$=a[s-1].nodes;break;case 41:this.$=r.addSubGraph(a[s-6],a[s-1],a[s-4]);break;case 42:this.$=r.addSubGraph(a[s-3],a[s-1],a[s-3]);break;case 43:this.$=r.addSubGraph(void 0,a[s-1],void 0);break;case 45:this.$=a[s].trim(),r.setAccTitle(this.$);break;case 46:case 47:this.$=a[s].trim(),r.setAccDescription(this.$);break;case 51:r.addLink(a[s-2].stmt,a[s],a[s-1]),this.$={stmt:a[s],nodes:a[s].concat(a[s-2].nodes)};break;case 52:r.addLink(a[s-3].stmt,a[s-1],a[s-2]),this.$={stmt:a[s-1],nodes:a[s-1].concat(a[s-3].nodes)};break;case 53:this.$={stmt:a[s-1],nodes:a[s-1]};break;case 54:this.$={stmt:a[s],nodes:a[s]};break;case 55:case 123:case 125:this.$=[a[s]];break;case 56:this.$=a[s-4].concat(a[s]);break;case 57:this.$=[a[s-2]],r.setClass(a[s-2],a[s]);break;case 58:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"square");break;case 59:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"doublecircle");break;case 60:this.$=a[s-5],r.addVertex(a[s-5],a[s-2],"circle");break;case 61:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"ellipse");break;case 62:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"stadium");break;case 63:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"subroutine");break;case 64:this.$=a[s-7],r.addVertex(a[s-7],a[s-1],"rect",void 0,void 0,void 0,Object.fromEntries([[a[s-5],a[s-3]]]));break;case 65:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"cylinder");break;case 66:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"round");break;case 67:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"diamond");break;case 68:this.$=a[s-5],r.addVertex(a[s-5],a[s-2],"hexagon");break;case 69:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"odd");break;case 70:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"trapezoid");break;case 71:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"inv_trapezoid");break;case 72:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"lean_right");break;case 73:this.$=a[s-3],r.addVertex(a[s-3],a[s-1],"lean_left");break;case 74:this.$=a[s],r.addVertex(a[s]);break;case 75:a[s-1].text=a[s],this.$=a[s-1];break;case 76:case 77:a[s-2].text=a[s-1],this.$=a[s-2];break;case 79:var c=r.destructLink(a[s],a[s-2]);this.$={type:c.type,stroke:c.stroke,length:c.length,text:a[s-1]};break;case 80:c=r.destructLink(a[s]),this.$={type:c.type,stroke:c.stroke,length:c.length};break;case 81:this.$=a[s-1];break;case 83:case 97:case 153:case 151:this.$=a[s-1]+""+a[s];break;case 98:case 99:this.$=a[s-4],r.addClass(a[s-2],a[s]);break;case 100:this.$=a[s-4],r.setClass(a[s-2],a[s]);break;case 101:case 109:this.$=a[s-1],r.setClickEvent(a[s-1],a[s]);break;case 102:case 110:this.$=a[s-3],r.setClickEvent(a[s-3],a[s-2]),r.setTooltip(a[s-3],a[s]);break;case 103:this.$=a[s-2],r.setClickEvent(a[s-2],a[s-1],a[s]);break;case 104:this.$=a[s-4],r.setClickEvent(a[s-4],a[s-3],a[s-2]),r.setTooltip(a[s-4],a[s]);break;case 105:case 111:this.$=a[s-1],r.setLink(a[s-1],a[s]);break;case 106:case 112:this.$=a[s-3],r.setLink(a[s-3],a[s-2]),r.setTooltip(a[s-3],a[s]);break;case 107:case 113:this.$=a[s-3],r.setLink(a[s-3],a[s-2],a[s]);break;case 108:case 114:this.$=a[s-5],r.setLink(a[s-5],a[s-4],a[s]),r.setTooltip(a[s-5],a[s-2]);break;case 115:this.$=a[s-4],r.addVertex(a[s-2],void 0,void 0,a[s]);break;case 116:case 118:this.$=a[s-4],r.updateLink(a[s-2],a[s]);break;case 117:this.$=a[s-4],r.updateLink([a[s-2]],a[s]);break;case 119:this.$=a[s-8],r.updateLinkInterpolate([a[s-6]],a[s-2]),r.updateLink([a[s-6]],a[s]);break;case 120:this.$=a[s-8],r.updateLinkInterpolate(a[s-6],a[s-2]),r.updateLink(a[s-6],a[s]);break;case 121:this.$=a[s-6],r.updateLinkInterpolate([a[s-4]],a[s]);break;case 122:this.$=a[s-6],r.updateLinkInterpolate(a[s-4],a[s]);break;case 124:case 126:a[s-2].push(a[s]),this.$=a[s-2];break;case 128:this.$=a[s-1]+a[s];break;case 156:this.$="v";break;case 157:this.$="-";break;case 158:this.$={stmt:"dir",value:"TB"};break;case 159:this.$={stmt:"dir",value:"BT"};break;case 160:this.$={stmt:"dir",value:"RL"};break;case 161:this.$={stmt:"dir",value:"LR"}}},table:[{3:1,4:2,5:3,6:5,12:e,16:4,21:n,22:r,24:i},{1:[3]},{1:[2,1]},{3:10,4:2,5:3,6:5,12:e,16:4,21:n,22:r,24:i},t(a,o,{17:11}),{7:12,13:[1,13]},{16:14,21:n,22:r,24:i},{16:15,21:n,22:r,24:i},{25:[1,16],26:[1,17]},{13:[2,5]},{1:[2,2]},{1:[2,9],18:18,19:19,20:s,21:c,22:l,23:u,32:24,33:25,34:26,35:27,36:28,37:29,38:h,43:31,44:f,46:d,48:p,50:35,51:45,52:g,54:46,66:y,67:m,86:b,87:v,88:_,89:x,90:k,91:w,95:T,105:E,106:C,109:S,111:A,112:M,116:47,118:N,119:D,120:O,121:B,122:L,123:I,124:F,125:R,126:P,127:j},{8:64,10:[1,65],15:z},t([10,15],[2,6]),t(a,[2,17]),t(a,[2,18]),t(a,[2,19]),{20:[1,68],21:[1,69],22:Y,27:67,30:70},t(U,[2,11]),t(U,[2,12]),t(U,[2,13]),t(U,[2,14]),t(U,[2,15]),t(U,[2,16]),{9:72,20:$,21:W,23:q,49:73,78:77,81:[1,78],82:[1,79]},{9:80,20:$,21:W,23:q},{9:81,20:$,21:W,23:q},{9:82,20:$,21:W,23:q},{9:83,20:$,21:W,23:q},{9:84,20:$,21:W,23:q},{9:86,20:$,21:W,22:[1,85],23:q},t(U,[2,44]),{45:[1,87]},{47:[1,88]},t(U,[2,47]),t(H,[2,54],{30:89,22:Y}),{22:[1,90]},{22:[1,91]},{22:[1,92]},{22:[1,93]},{26:V,52:G,66:X,67:Z,84:[1,97],91:Q,97:96,98:[1,94],100:[1,95],105:K,106:J,109:tt,111:et,112:nt,115:100,117:98,122:rt,123:it,124:at,125:ot,126:st,127:ct},t(U,[2,158]),t(U,[2,159]),t(U,[2,160]),t(U,[2,161]),t(lt,[2,55],{53:[1,116]}),t(ut,[2,74],{116:129,40:[1,117],52:g,55:[1,118],57:[1,119],59:[1,120],61:[1,121],63:[1,122],65:[1,123],66:y,67:m,69:[1,124],71:[1,125],73:[1,126],74:[1,127],76:[1,128],91:w,95:T,105:E,106:C,109:S,111:A,112:M,122:L,123:I,124:F,125:R,126:P,127:j}),t(ht,[2,150]),t(ht,[2,175]),t(ht,[2,176]),t(ht,[2,177]),t(ht,[2,178]),t(ht,[2,179]),t(ht,[2,180]),t(ht,[2,181]),t(ht,[2,182]),t(ht,[2,183]),t(ht,[2,184]),t(ht,[2,185]),t(ht,[2,186]),t(ht,[2,187]),t(ht,[2,188]),t(ht,[2,189]),t(ht,[2,190]),{9:130,20:$,21:W,23:q},{11:131,14:[1,132]},t(ft,[2,8]),t(a,[2,20]),t(a,[2,26]),t(a,[2,27]),{21:[1,133]},t(dt,[2,34],{30:134,22:Y}),t(U,[2,35]),{50:135,51:45,52:g,54:46,66:y,67:m,91:w,95:T,105:E,106:C,109:S,111:A,112:M,116:47,122:L,123:I,124:F,125:R,126:P,127:j},t(pt,[2,48]),t(pt,[2,49]),t(pt,[2,50]),t(gt,[2,78],{79:136,68:[1,138],80:[1,137]}),{22:yt,24:mt,26:bt,38:vt,39:139,42:_t,52:G,66:X,67:Z,73:xt,81:kt,83:140,84:wt,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},t([52,66,67,68,80,91,95,105,106,109,111,112,122,123,124,125,126,127],[2,80]),t(U,[2,36]),t(U,[2,37]),t(U,[2,38]),t(U,[2,39]),t(U,[2,40]),{22:yt,24:mt,26:bt,38:vt,39:163,42:_t,52:G,66:X,67:Z,73:xt,81:kt,83:140,84:wt,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},t(It,o,{17:164}),t(U,[2,45]),t(U,[2,46]),t(H,[2,53],{52:Ft}),{26:V,52:G,66:X,67:Z,91:Q,97:166,102:[1,167],105:K,106:J,109:tt,111:et,112:nt,115:100,117:98,122:rt,123:it,124:at,125:ot,126:st,127:ct},{95:[1,168],103:169,105:[1,170]},{26:V,52:G,66:X,67:Z,91:Q,95:[1,171],97:172,105:K,106:J,109:tt,111:et,112:nt,115:100,117:98,122:rt,123:it,124:at,125:ot,126:st,127:ct},{26:V,52:G,66:X,67:Z,91:Q,97:173,105:K,106:J,109:tt,111:et,112:nt,115:100,117:98,122:rt,123:it,124:at,125:ot,126:st,127:ct},t(ft,[2,101],{22:[1,174],99:[1,175]}),t(ft,[2,105],{22:[1,176]}),t(ft,[2,109],{115:100,117:178,22:[1,177],26:V,52:G,66:X,67:Z,91:Q,105:K,106:J,109:tt,111:et,112:nt,122:rt,123:it,124:at,125:ot,126:st,127:ct}),t(ft,[2,111],{22:[1,179]}),t(Rt,[2,152]),t(Rt,[2,154]),t(Rt,[2,155]),t(Rt,[2,156]),t(Rt,[2,157]),t(Pt,[2,162]),t(Pt,[2,163]),t(Pt,[2,164]),t(Pt,[2,165]),t(Pt,[2,166]),t(Pt,[2,167]),t(Pt,[2,168]),t(Pt,[2,169]),t(Pt,[2,170]),t(Pt,[2,171]),t(Pt,[2,172]),t(Pt,[2,173]),t(Pt,[2,174]),{52:g,54:180,66:y,67:m,91:w,95:T,105:E,106:C,109:S,111:A,112:M,116:47,122:L,123:I,124:F,125:R,126:P,127:j},{22:yt,24:mt,26:bt,38:vt,39:181,42:_t,52:G,66:X,67:Z,73:xt,81:kt,83:140,84:wt,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:yt,24:mt,26:bt,38:vt,39:182,42:_t,52:G,66:X,67:Z,73:xt,81:kt,83:140,84:wt,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:yt,24:mt,26:bt,38:vt,39:184,42:_t,52:G,57:[1,183],66:X,67:Z,73:xt,81:kt,83:140,84:wt,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:yt,24:mt,26:bt,38:vt,39:185,42:_t,52:G,66:X,67:Z,73:xt,81:kt,83:140,84:wt,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:yt,24:mt,26:bt,38:vt,39:186,42:_t,52:G,66:X,67:Z,73:xt,81:kt,83:140,84:wt,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:yt,24:mt,26:bt,38:vt,39:187,42:_t,52:G,66:X,67:Z,73:xt,81:kt,83:140,84:wt,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{66:[1,188]},{22:yt,24:mt,26:bt,38:vt,39:189,42:_t,52:G,66:X,67:Z,73:xt,81:kt,83:140,84:wt,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:yt,24:mt,26:bt,38:vt,39:190,42:_t,52:G,66:X,67:Z,71:[1,191],73:xt,81:kt,83:140,84:wt,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:yt,24:mt,26:bt,38:vt,39:192,42:_t,52:G,66:X,67:Z,73:xt,81:kt,83:140,84:wt,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:yt,24:mt,26:bt,38:vt,39:193,42:_t,52:G,66:X,67:Z,73:xt,81:kt,83:140,84:wt,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:yt,24:mt,26:bt,38:vt,39:194,42:_t,52:G,66:X,67:Z,73:xt,81:kt,83:140,84:wt,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},t(ht,[2,151]),t(jt,[2,3]),{8:195,15:z},{15:[2,7]},t(a,[2,28]),t(dt,[2,33]),t(H,[2,51],{30:196,22:Y}),t(gt,[2,75],{22:[1,197]}),{22:[1,198]},{22:yt,24:mt,26:bt,38:vt,39:199,42:_t,52:G,66:X,67:Z,73:xt,81:kt,83:140,84:wt,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:yt,24:mt,26:bt,38:vt,42:_t,52:G,66:X,67:Z,73:xt,81:kt,82:[1,200],83:201,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},t(Pt,[2,82]),t(Pt,[2,84]),t(Pt,[2,140]),t(Pt,[2,141]),t(Pt,[2,142]),t(Pt,[2,143]),t(Pt,[2,144]),t(Pt,[2,145]),t(Pt,[2,146]),t(Pt,[2,147]),t(Pt,[2,148]),t(Pt,[2,149]),t(Pt,[2,85]),t(Pt,[2,86]),t(Pt,[2,87]),t(Pt,[2,88]),t(Pt,[2,89]),t(Pt,[2,90]),t(Pt,[2,91]),t(Pt,[2,92]),t(Pt,[2,93]),t(Pt,[2,94]),t(Pt,[2,95]),{9:203,20:$,21:W,22:yt,23:q,24:mt,26:bt,38:vt,40:[1,202],42:_t,52:G,66:X,67:Z,73:xt,81:kt,83:201,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{18:18,19:19,20:s,21:c,22:l,23:u,32:24,33:25,34:26,35:27,36:28,37:29,38:h,42:[1,204],43:31,44:f,46:d,48:p,50:35,51:45,52:g,54:46,66:y,67:m,86:b,87:v,88:_,89:x,90:k,91:w,95:T,105:E,106:C,109:S,111:A,112:M,116:47,118:N,119:D,120:O,121:B,122:L,123:I,124:F,125:R,126:P,127:j},{22:Y,30:205},{22:[1,206],26:V,52:G,66:X,67:Z,91:Q,105:K,106:J,109:tt,111:et,112:nt,115:100,117:178,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:[1,207]},{22:[1,208]},{22:[1,209],106:[1,210]},t(zt,[2,123]),{22:[1,211]},{22:[1,212],26:V,52:G,66:X,67:Z,91:Q,105:K,106:J,109:tt,111:et,112:nt,115:100,117:178,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:[1,213],26:V,52:G,66:X,67:Z,91:Q,105:K,106:J,109:tt,111:et,112:nt,115:100,117:178,122:rt,123:it,124:at,125:ot,126:st,127:ct},{84:[1,214]},t(ft,[2,103],{22:[1,215]}),{84:[1,216],101:[1,217]},{84:[1,218]},t(Rt,[2,153]),{84:[1,219],101:[1,220]},t(lt,[2,57],{116:129,52:g,66:y,67:m,91:w,95:T,105:E,106:C,109:S,111:A,112:M,122:L,123:I,124:F,125:R,126:P,127:j}),{22:yt,24:mt,26:bt,38:vt,41:[1,221],42:_t,52:G,66:X,67:Z,73:xt,81:kt,83:201,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:yt,24:mt,26:bt,38:vt,42:_t,52:G,56:[1,222],66:X,67:Z,73:xt,81:kt,83:201,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:yt,24:mt,26:bt,38:vt,39:223,42:_t,52:G,66:X,67:Z,73:xt,81:kt,83:140,84:wt,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:yt,24:mt,26:bt,38:vt,42:_t,52:G,58:[1,224],66:X,67:Z,73:xt,81:kt,83:201,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:yt,24:mt,26:bt,38:vt,42:_t,52:G,60:[1,225],66:X,67:Z,73:xt,81:kt,83:201,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:yt,24:mt,26:bt,38:vt,42:_t,52:G,62:[1,226],66:X,67:Z,73:xt,81:kt,83:201,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:yt,24:mt,26:bt,38:vt,42:_t,52:G,64:[1,227],66:X,67:Z,73:xt,81:kt,83:201,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{67:[1,228]},{22:yt,24:mt,26:bt,38:vt,42:_t,52:G,66:X,67:Z,70:[1,229],73:xt,81:kt,83:201,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:yt,24:mt,26:bt,38:vt,42:_t,52:G,66:X,67:Z,72:[1,230],73:xt,81:kt,83:201,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:yt,24:mt,26:bt,38:vt,39:231,42:_t,52:G,66:X,67:Z,73:xt,81:kt,83:140,84:wt,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:yt,24:mt,26:bt,38:vt,41:[1,232],42:_t,52:G,66:X,67:Z,73:xt,81:kt,83:201,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:yt,24:mt,26:bt,38:vt,42:_t,52:G,66:X,67:Z,73:xt,75:[1,233],77:[1,234],81:kt,83:201,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{22:yt,24:mt,26:bt,38:vt,42:_t,52:G,66:X,67:Z,73:xt,75:[1,236],77:[1,235],81:kt,83:201,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{9:237,20:$,21:W,23:q},t(H,[2,52],{52:Ft}),t(gt,[2,77]),t(gt,[2,76]),{22:yt,24:mt,26:bt,38:vt,42:_t,52:G,66:X,67:Z,68:[1,238],73:xt,81:kt,83:201,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},t(gt,[2,79]),t(Pt,[2,83]),{22:yt,24:mt,26:bt,38:vt,39:239,42:_t,52:G,66:X,67:Z,73:xt,81:kt,83:140,84:wt,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},t(It,o,{17:240}),t(U,[2,43]),{51:241,52:g,54:46,66:y,67:m,91:w,95:T,105:E,106:C,109:S,111:A,112:M,116:47,122:L,123:I,124:F,125:R,126:P,127:j},{22:Yt,66:Ut,67:$t,86:Wt,96:242,102:qt,105:Ht,107:243,108:244,109:Vt,110:Gt,111:Xt,112:Zt,113:Qt},{22:Yt,66:Ut,67:$t,86:Wt,96:256,102:qt,105:Ht,107:243,108:244,109:Vt,110:Gt,111:Xt,112:Zt,113:Qt},{22:Yt,66:Ut,67:$t,86:Wt,96:257,102:qt,104:[1,258],105:Ht,107:243,108:244,109:Vt,110:Gt,111:Xt,112:Zt,113:Qt},{22:Yt,66:Ut,67:$t,86:Wt,96:259,102:qt,104:[1,260],105:Ht,107:243,108:244,109:Vt,110:Gt,111:Xt,112:Zt,113:Qt},{105:[1,261]},{22:Yt,66:Ut,67:$t,86:Wt,96:262,102:qt,105:Ht,107:243,108:244,109:Vt,110:Gt,111:Xt,112:Zt,113:Qt},{22:Yt,66:Ut,67:$t,86:Wt,96:263,102:qt,105:Ht,107:243,108:244,109:Vt,110:Gt,111:Xt,112:Zt,113:Qt},{26:V,52:G,66:X,67:Z,91:Q,97:264,105:K,106:J,109:tt,111:et,112:nt,115:100,117:98,122:rt,123:it,124:at,125:ot,126:st,127:ct},t(ft,[2,102]),{84:[1,265]},t(ft,[2,106],{22:[1,266]}),t(ft,[2,107]),t(ft,[2,110]),t(ft,[2,112],{22:[1,267]}),t(ft,[2,113]),t(ut,[2,58]),t(ut,[2,59]),{22:yt,24:mt,26:bt,38:vt,42:_t,52:G,58:[1,268],66:X,67:Z,73:xt,81:kt,83:201,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},t(ut,[2,66]),t(ut,[2,61]),t(ut,[2,62]),t(ut,[2,63]),{66:[1,269]},t(ut,[2,65]),t(ut,[2,67]),{22:yt,24:mt,26:bt,38:vt,42:_t,52:G,66:X,67:Z,72:[1,270],73:xt,81:kt,83:201,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},t(ut,[2,69]),t(ut,[2,70]),t(ut,[2,72]),t(ut,[2,71]),t(ut,[2,73]),t(jt,[2,4]),t([22,52,66,67,91,95,105,106,109,111,112,122,123,124,125,126,127],[2,81]),{22:yt,24:mt,26:bt,38:vt,41:[1,271],42:_t,52:G,66:X,67:Z,73:xt,81:kt,83:201,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{18:18,19:19,20:s,21:c,22:l,23:u,32:24,33:25,34:26,35:27,36:28,37:29,38:h,42:[1,272],43:31,44:f,46:d,48:p,50:35,51:45,52:g,54:46,66:y,67:m,86:b,87:v,88:_,89:x,90:k,91:w,95:T,105:E,106:C,109:S,111:A,112:M,116:47,118:N,119:D,120:O,121:B,122:L,123:I,124:F,125:R,126:P,127:j},t(lt,[2,56]),t(ft,[2,115],{106:Kt}),t(Jt,[2,125],{108:274,22:Yt,66:Ut,67:$t,86:Wt,102:qt,105:Ht,109:Vt,110:Gt,111:Xt,112:Zt,113:Qt}),t(te,[2,127]),t(te,[2,129]),t(te,[2,130]),t(te,[2,131]),t(te,[2,132]),t(te,[2,133]),t(te,[2,134]),t(te,[2,135]),t(te,[2,136]),t(te,[2,137]),t(te,[2,138]),t(te,[2,139]),t(ft,[2,116],{106:Kt}),t(ft,[2,117],{106:Kt}),{22:[1,275]},t(ft,[2,118],{106:Kt}),{22:[1,276]},t(zt,[2,124]),t(ft,[2,98],{106:Kt}),t(ft,[2,99],{106:Kt}),t(ft,[2,100],{115:100,117:178,26:V,52:G,66:X,67:Z,91:Q,105:K,106:J,109:tt,111:et,112:nt,122:rt,123:it,124:at,125:ot,126:st,127:ct}),t(ft,[2,104]),{101:[1,277]},{101:[1,278]},{58:[1,279]},{68:[1,280]},{72:[1,281]},{9:282,20:$,21:W,23:q},t(U,[2,42]),{22:Yt,66:Ut,67:$t,86:Wt,102:qt,105:Ht,107:283,108:244,109:Vt,110:Gt,111:Xt,112:Zt,113:Qt},t(te,[2,128]),{26:V,52:G,66:X,67:Z,91:Q,97:284,105:K,106:J,109:tt,111:et,112:nt,115:100,117:98,122:rt,123:it,124:at,125:ot,126:st,127:ct},{26:V,52:G,66:X,67:Z,91:Q,97:285,105:K,106:J,109:tt,111:et,112:nt,115:100,117:98,122:rt,123:it,124:at,125:ot,126:st,127:ct},t(ft,[2,108]),t(ft,[2,114]),t(ut,[2,60]),{22:yt,24:mt,26:bt,38:vt,39:286,42:_t,52:G,66:X,67:Z,73:xt,81:kt,83:140,84:wt,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},t(ut,[2,68]),t(It,o,{17:287}),t(Jt,[2,126],{108:274,22:Yt,66:Ut,67:$t,86:Wt,102:qt,105:Ht,109:Vt,110:Gt,111:Xt,112:Zt,113:Qt}),t(ft,[2,121],{115:100,117:178,22:[1,288],26:V,52:G,66:X,67:Z,91:Q,105:K,106:J,109:tt,111:et,112:nt,122:rt,123:it,124:at,125:ot,126:st,127:ct}),t(ft,[2,122],{115:100,117:178,22:[1,289],26:V,52:G,66:X,67:Z,91:Q,105:K,106:J,109:tt,111:et,112:nt,122:rt,123:it,124:at,125:ot,126:st,127:ct}),{22:yt,24:mt,26:bt,38:vt,41:[1,290],42:_t,52:G,66:X,67:Z,73:xt,81:kt,83:201,85:151,86:Tt,87:Et,88:Ct,89:St,90:At,91:Mt,92:Nt,94:142,95:Dt,105:K,106:J,109:Ot,111:et,112:nt,113:Bt,114:Lt,115:148,122:rt,123:it,124:at,125:ot,126:st,127:ct},{18:18,19:19,20:s,21:c,22:l,23:u,32:24,33:25,34:26,35:27,36:28,37:29,38:h,42:[1,291],43:31,44:f,46:d,48:p,50:35,51:45,52:g,54:46,66:y,67:m,86:b,87:v,88:_,89:x,90:k,91:w,95:T,105:E,106:C,109:S,111:A,112:M,116:47,118:N,119:D,120:O,121:B,122:L,123:I,124:F,125:R,126:P,127:j},{22:Yt,66:Ut,67:$t,86:Wt,96:292,102:qt,105:Ht,107:243,108:244,109:Vt,110:Gt,111:Xt,112:Zt,113:Qt},{22:Yt,66:Ut,67:$t,86:Wt,96:293,102:qt,105:Ht,107:243,108:244,109:Vt,110:Gt,111:Xt,112:Zt,113:Qt},t(ut,[2,64]),t(U,[2,41]),t(ft,[2,119],{106:Kt}),t(ft,[2,120],{106:Kt})],defaultActions:{2:[2,1],9:[2,5],10:[2,2],132:[2,7]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,l=0,u=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var m=p.yylloc;a.push(m);var b=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,x,k,w,T,E,C,S,A,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==_&&(_=v()),w=o[k]&&o[k][_]),void 0===w||!w.length||!w[0]){var N="";for(E in A=[],o[k])this.terminals_[E]&&E>h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(c+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:m,expected:A})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+_);switch(w[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),_=null,x?(_=x,x=null):(l=p.yyleng,s=p.yytext,c=p.yylineno,m=p.yylloc,u>0&&u--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},b&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,l,c,g.yy,w[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},ne={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),12;case 1:return this.begin("type_directive"),13;case 2:return this.popState(),this.begin("arg_directive"),10;case 3:return this.popState(),this.popState(),15;case 4:return 14;case 5:case 6:break;case 7:return this.begin("acc_title"),44;case 8:return this.popState(),"acc_title_value";case 9:return this.begin("acc_descr"),46;case 10:return this.popState(),"acc_descr_value";case 11:this.begin("acc_descr_multiline");break;case 12:case 15:case 24:case 27:case 30:case 33:this.popState();break;case 13:return"acc_descr_multiline_value";case 14:this.begin("string");break;case 16:return"STR";case 17:return 86;case 18:return 95;case 19:return 87;case 20:return 104;case 21:return 88;case 22:return 89;case 23:this.begin("href");break;case 25:return 100;case 26:this.begin("callbackname");break;case 28:this.popState(),this.begin("callbackargs");break;case 29:return 98;case 31:return 99;case 32:this.begin("click");break;case 34:return 90;case 35:case 36:return t.lex.firstGraph()&&this.begin("dir"),24;case 37:return 38;case 38:return 42;case 39:case 40:case 41:case 42:return 101;case 43:return this.popState(),25;case 44:case 45:case 46:case 47:case 48:case 49:case 50:case 51:case 52:case 53:return this.popState(),26;case 54:return 118;case 55:return 119;case 56:return 120;case 57:return 121;case 58:return 105;case 59:return 111;case 60:return 53;case 61:return 67;case 62:return 52;case 63:return 20;case 64:return 106;case 65:return 126;case 66:case 67:case 68:return 82;case 69:case 70:case 71:return 81;case 72:return 59;case 73:return 60;case 74:return 61;case 75:return 62;case 76:return 63;case 77:return 64;case 78:return 65;case 79:return 69;case 80:return 70;case 81:return 55;case 82:return 56;case 83:return 109;case 84:return 112;case 85:return 127;case 86:return 124;case 87:return 113;case 88:case 89:return 125;case 90:return 114;case 91:return 73;case 92:return 92;case 93:return"SEP";case 94:return 91;case 95:return 66;case 96:return 75;case 97:return 74;case 98:return 77;case 99:return 76;case 100:return 122;case 101:return 123;case 102:return 68;case 103:return 57;case 104:return 58;case 105:return 40;case 106:return 41;case 107:return 71;case 108:return 72;case 109:return 133;case 110:return 21;case 111:return 22;case 112:return 23}},rules:[/^(?:%%\{)/,/^(?:((?:(?!\}%%)[^:.])*))/,/^(?::)/,/^(?:\}%%)/,/^(?:((?:(?!\}%%).|\n)*))/,/^(?:%%(?!\{)[^\n]*)/,/^(?:[^\}]%%[^\n]*)/,/^(?:accTitle\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*:\s*)/,/^(?:(?!\n||)*[^\n]*)/,/^(?:accDescr\s*\{\s*)/,/^(?:[\}])/,/^(?:[^\}]*)/,/^(?:["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:style\b)/,/^(?:default\b)/,/^(?:linkStyle\b)/,/^(?:interpolate\b)/,/^(?:classDef\b)/,/^(?:class\b)/,/^(?:href[\s]+["])/,/^(?:["])/,/^(?:[^"]*)/,/^(?:call[\s]+)/,/^(?:\([\s]*\))/,/^(?:\()/,/^(?:[^(]*)/,/^(?:\))/,/^(?:[^)]*)/,/^(?:click[\s]+)/,/^(?:[\s\n])/,/^(?:[^\s\n]*)/,/^(?:graph\b)/,/^(?:flowchart\b)/,/^(?:subgraph\b)/,/^(?:end\b\s*)/,/^(?:_self\b)/,/^(?:_blank\b)/,/^(?:_parent\b)/,/^(?:_top\b)/,/^(?:(\r?\n)*\s*\n)/,/^(?:\s*LR\b)/,/^(?:\s*RL\b)/,/^(?:\s*TB\b)/,/^(?:\s*BT\b)/,/^(?:\s*TD\b)/,/^(?:\s*BR\b)/,/^(?:\s*<)/,/^(?:\s*>)/,/^(?:\s*\^)/,/^(?:\s*v\b)/,/^(?:.*direction\s+TB[^\n]*)/,/^(?:.*direction\s+BT[^\n]*)/,/^(?:.*direction\s+RL[^\n]*)/,/^(?:.*direction\s+LR[^\n]*)/,/^(?:[0-9]+)/,/^(?:#)/,/^(?::::)/,/^(?::)/,/^(?:&)/,/^(?:;)/,/^(?:,)/,/^(?:\*)/,/^(?:\s*[xo<]?--+[-xo>]\s*)/,/^(?:\s*[xo<]?==+[=xo>]\s*)/,/^(?:\s*[xo<]?-?\.+-[xo>]?\s*)/,/^(?:\s*[xo<]?--\s*)/,/^(?:\s*[xo<]?==\s*)/,/^(?:\s*[xo<]?-\.\s*)/,/^(?:\(-)/,/^(?:-\))/,/^(?:\(\[)/,/^(?:\]\))/,/^(?:\[\[)/,/^(?:\]\])/,/^(?:\[\|)/,/^(?:\[\()/,/^(?:\)\])/,/^(?:\(\(\()/,/^(?:\)\)\))/,/^(?:-)/,/^(?:\.)/,/^(?:[\_])/,/^(?:\+)/,/^(?:%)/,/^(?:=)/,/^(?:=)/,/^(?:<)/,/^(?:>)/,/^(?:\^)/,/^(?:\\\|)/,/^(?:v\b)/,/^(?:[A-Za-z]+)/,/^(?:\\\])/,/^(?:\[\/)/,/^(?:\/\])/,/^(?:\[\\)/,/^(?:[!"#$%&'*+,-.`?\\_/])/,/^(?:[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|[\uFFD2-\uFFD7\uFFDA-\uFFDC])/,/^(?:\|)/,/^(?:\()/,/^(?:\))/,/^(?:\[)/,/^(?:\])/,/^(?:\{)/,/^(?:\})/,/^(?:")/,/^(?:(\r?\n)+)/,/^(?:\s)/,/^(?:$)/],conditions:{close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},callbackargs:{rules:[30,31],inclusive:!1},callbackname:{rules:[27,28,29],inclusive:!1},href:{rules:[24,25],inclusive:!1},click:{rules:[33,34],inclusive:!1},vertex:{rules:[],inclusive:!1},dir:{rules:[43,44,45,46,47,48,49,50,51,52,53],inclusive:!1},acc_descr_multiline:{rules:[12,13],inclusive:!1},acc_descr:{rules:[10],inclusive:!1},acc_title:{rules:[8],inclusive:!1},string:{rules:[15,16],inclusive:!1},INITIAL:{rules:[0,5,6,7,9,11,14,17,18,19,20,21,22,23,26,32,35,36,37,38,39,40,41,42,54,55,56,57,58,59,60,61,62,63,64,65,66,67,68,69,70,71,72,73,74,75,76,77,78,79,80,81,82,83,84,85,86,87,88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112],inclusive:!0}}};function re(){this.yy={}}return ee.lexer=ne,re.prototype=ee,ee.Parser=re,new re}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(5354).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},9959:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,3],n=[1,5],r=[7,9,11,12,13,14,15,16,17,18,19,20,22,24,25,27,34,39],i=[1,15],a=[1,16],o=[1,17],s=[1,18],c=[1,19],l=[1,20],u=[1,21],h=[1,22],f=[1,23],d=[1,24],p=[1,25],g=[1,26],y=[1,28],m=[1,30],b=[1,33],v=[5,7,9,11,12,13,14,15,16,17,18,19,20,22,24,25,27,34,39],_={trace:function(){},yy:{},symbols_:{error:2,start:3,directive:4,gantt:5,document:6,EOF:7,line:8,SPACE:9,statement:10,NL:11,dateFormat:12,inclusiveEndDates:13,topAxis:14,axisFormat:15,excludes:16,includes:17,todayMarker:18,title:19,acc_title:20,acc_title_value:21,acc_descr:22,acc_descr_value:23,acc_descr_multiline_value:24,section:25,clickStatement:26,taskTxt:27,taskData:28,openDirective:29,typeDirective:30,closeDirective:31,":":32,argDirective:33,click:34,callbackname:35,callbackargs:36,href:37,clickStatementDebug:38,open_directive:39,type_directive:40,arg_directive:41,close_directive:42,$accept:0,$end:1},terminals_:{2:"error",5:"gantt",7:"EOF",9:"SPACE",11:"NL",12:"dateFormat",13:"inclusiveEndDates",14:"topAxis",15:"axisFormat",16:"excludes",17:"includes",18:"todayMarker",19:"title",20:"acc_title",21:"acc_title_value",22:"acc_descr",23:"acc_descr_value",24:"acc_descr_multiline_value",25:"section",27:"taskTxt",28:"taskData",32:":",34:"click",35:"callbackname",36:"callbackargs",37:"href",39:"open_directive",40:"type_directive",41:"arg_directive",42:"close_directive"},productions_:[0,[3,2],[3,3],[6,0],[6,2],[8,2],[8,1],[8,1],[8,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,1],[10,2],[10,2],[10,1],[10,1],[10,1],[10,2],[10,1],[4,4],[4,6],[26,2],[26,3],[26,3],[26,4],[26,3],[26,4],[26,2],[38,2],[38,3],[38,3],[38,4],[38,3],[38,4],[38,2],[29,1],[30,1],[33,1],[31,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 2:return a[s-1];case 3:case 7:case 8:this.$=[];break;case 4:a[s-1].push(a[s]),this.$=a[s-1];break;case 5:case 6:this.$=a[s];break;case 9:r.setDateFormat(a[s].substr(11)),this.$=a[s].substr(11);break;case 10:r.enableInclusiveEndDates(),this.$=a[s].substr(18);break;case 11:r.TopAxis(),this.$=a[s].substr(8);break;case 12:r.setAxisFormat(a[s].substr(11)),this.$=a[s].substr(11);break;case 13:r.setExcludes(a[s].substr(9)),this.$=a[s].substr(9);break;case 14:r.setIncludes(a[s].substr(9)),this.$=a[s].substr(9);break;case 15:r.setTodayMarker(a[s].substr(12)),this.$=a[s].substr(12);break;case 16:r.setDiagramTitle(a[s].substr(6)),this.$=a[s].substr(6);break;case 17:this.$=a[s].trim(),r.setAccTitle(this.$);break;case 18:case 19:this.$=a[s].trim(),r.setAccDescription(this.$);break;case 20:r.addSection(a[s].substr(8)),this.$=a[s].substr(8);break;case 22:r.addTask(a[s-1],a[s]),this.$="task";break;case 26:this.$=a[s-1],r.setClickEvent(a[s-1],a[s],null);break;case 27:this.$=a[s-2],r.setClickEvent(a[s-2],a[s-1],a[s]);break;case 28:this.$=a[s-2],r.setClickEvent(a[s-2],a[s-1],null),r.setLink(a[s-2],a[s]);break;case 29:this.$=a[s-3],r.setClickEvent(a[s-3],a[s-2],a[s-1]),r.setLink(a[s-3],a[s]);break;case 30:this.$=a[s-2],r.setClickEvent(a[s-2],a[s],null),r.setLink(a[s-2],a[s-1]);break;case 31:this.$=a[s-3],r.setClickEvent(a[s-3],a[s-1],a[s]),r.setLink(a[s-3],a[s-2]);break;case 32:this.$=a[s-1],r.setLink(a[s-1],a[s]);break;case 33:case 39:this.$=a[s-1]+" "+a[s];break;case 34:case 35:case 37:this.$=a[s-2]+" "+a[s-1]+" "+a[s];break;case 36:case 38:this.$=a[s-3]+" "+a[s-2]+" "+a[s-1]+" "+a[s];break;case 40:r.parseDirective("%%{","open_directive");break;case 41:r.parseDirective(a[s],"type_directive");break;case 42:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 43:r.parseDirective("}%%","close_directive","gantt")}},table:[{3:1,4:2,5:e,29:4,39:n},{1:[3]},{3:6,4:2,5:e,29:4,39:n},t(r,[2,3],{6:7}),{30:8,40:[1,9]},{40:[2,40]},{1:[2,1]},{4:29,7:[1,10],8:11,9:[1,12],10:13,11:[1,14],12:i,13:a,14:o,15:s,16:c,17:l,18:u,19:h,20:f,22:d,24:p,25:g,26:27,27:y,29:4,34:m,39:n},{31:31,32:[1,32],42:b},t([32,42],[2,41]),t(r,[2,8],{1:[2,2]}),t(r,[2,4]),{4:29,10:34,12:i,13:a,14:o,15:s,16:c,17:l,18:u,19:h,20:f,22:d,24:p,25:g,26:27,27:y,29:4,34:m,39:n},t(r,[2,6]),t(r,[2,7]),t(r,[2,9]),t(r,[2,10]),t(r,[2,11]),t(r,[2,12]),t(r,[2,13]),t(r,[2,14]),t(r,[2,15]),t(r,[2,16]),{21:[1,35]},{23:[1,36]},t(r,[2,19]),t(r,[2,20]),t(r,[2,21]),{28:[1,37]},t(r,[2,23]),{35:[1,38],37:[1,39]},{11:[1,40]},{33:41,41:[1,42]},{11:[2,43]},t(r,[2,5]),t(r,[2,17]),t(r,[2,18]),t(r,[2,22]),t(r,[2,26],{36:[1,43],37:[1,44]}),t(r,[2,32],{35:[1,45]}),t(v,[2,24]),{31:46,42:b},{42:[2,42]},t(r,[2,27],{37:[1,47]}),t(r,[2,28]),t(r,[2,30],{36:[1,48]}),{11:[1,49]},t(r,[2,29]),t(r,[2,31]),t(v,[2,25])],defaultActions:{5:[2,40],6:[2,1],33:[2,43],42:[2,42]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,l=0,u=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var m=p.yylloc;a.push(m);var b=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,x,k,w,T,E,C,S,A,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==_&&(_=v()),w=o[k]&&o[k][_]),void 0===w||!w.length||!w[0]){var N="";for(E in A=[],o[k])this.terminals_[E]&&E>h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(c+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:m,expected:A})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+_);switch(w[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),_=null,x?(_=x,x=null):(l=p.yyleng,s=p.yytext,c=p.yylineno,m=p.yylloc,u>0&&u--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},b&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,l,c,g.yy,w[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},x={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),39;case 1:return this.begin("type_directive"),40;case 2:return this.popState(),this.begin("arg_directive"),32;case 3:return this.popState(),this.popState(),42;case 4:return 41;case 5:return this.begin("acc_title"),20;case 6:return this.popState(),"acc_title_value";case 7:return this.begin("acc_descr"),22;case 8:return this.popState(),"acc_descr_value";case 9:this.begin("acc_descr_multiline");break;case 10:case 20:case 23:case 26:case 29:this.popState();break;case 11:return"acc_descr_multiline_value";case 12:case 13:case 14:case 16:case 17:case 18:break;case 15:return 11;case 19:this.begin("href");break;case 21:return 37;case 22:this.begin("callbackname");break;case 24:this.popState(),this.begin("callbackargs");break;case 25:return 35;case 27:return 36;case 28:this.begin("click");break;case 30:return 34;case 31:return 5;case 32:return 12;case 33:return 13;case 34:return 14;case 35:return 15;case 36:return 17;case 37:return 16;case 38:return 18;case 39:return"date";case 40:return 19;case 41:return"accDescription";case 42:return 25;case 43:return 27;case 44:return 28;case 45:return 32;case 46:return 7;case 47:return"INVALID"}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:%%(?!\{)*[^\n]*)/i,/^(?:[^\}]%%*[^\n]*)/i,/^(?:%%*[^\n]*[\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:href[\s]+["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:call[\s]+)/i,/^(?:\([\s]*\))/i,/^(?:\()/i,/^(?:[^(]*)/i,/^(?:\))/i,/^(?:[^)]*)/i,/^(?:click[\s]+)/i,/^(?:[\s\n])/i,/^(?:[^\s\n]*)/i,/^(?:gantt\b)/i,/^(?:dateFormat\s[^#\n;]+)/i,/^(?:inclusiveEndDates\b)/i,/^(?:topAxis\b)/i,/^(?:axisFormat\s[^#\n;]+)/i,/^(?:includes\s[^#\n;]+)/i,/^(?:excludes\s[^#\n;]+)/i,/^(?:todayMarker\s[^\n;]+)/i,/^(?:\d\d\d\d-\d\d-\d\d\b)/i,/^(?:title\s[^#\n;]+)/i,/^(?:accDescription\s[^#\n;]+)/i,/^(?:section\s[^#:\n;]+)/i,/^(?:[^#:\n;]+)/i,/^(?::[^#\n;]+)/i,/^(?::)/i,/^(?:$)/i,/^(?:.)/i],conditions:{acc_descr_multiline:{rules:[10,11],inclusive:!1},acc_descr:{rules:[8],inclusive:!1},acc_title:{rules:[6],inclusive:!1},close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},callbackargs:{rules:[26,27],inclusive:!1},callbackname:{rules:[23,24,25],inclusive:!1},href:{rules:[20,21],inclusive:!1},click:{rules:[29,30],inclusive:!1},INITIAL:{rules:[0,5,7,9,12,13,14,15,16,17,18,19,22,28,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47],inclusive:!0}}};function k(){this.yy={}}return _.lexer=x,k.prototype=_,_.Parser=k,new k}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(6878).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},2553:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,4],n=[1,7],r=[1,5],i=[1,9],a=[1,6],o=[2,6],s=[1,16],c=[6,8,14,20,22,24,25,27,29,32,35,37,49,53],l=[8,14,20,22,24,25,27,29,32,35,37],u=[8,13,14,20,22,24,25,27,29,32,35,37],h=[1,26],f=[6,8,14,49,53],d=[8,14,53],p=[1,64],g=[1,65],y=[1,66],m=[8,14,33,36,41,53],b={trace:function(){},yy:{},symbols_:{error:2,start:3,eol:4,directive:5,GG:6,document:7,EOF:8,":":9,DIR:10,options:11,body:12,OPT:13,NL:14,line:15,statement:16,commitStatement:17,mergeStatement:18,cherryPickStatement:19,acc_title:20,acc_title_value:21,acc_descr:22,acc_descr_value:23,acc_descr_multiline_value:24,section:25,branchStatement:26,CHECKOUT:27,ID:28,BRANCH:29,ORDER:30,NUM:31,CHERRY_PICK:32,COMMIT_ID:33,STR:34,MERGE:35,COMMIT_TAG:36,COMMIT:37,commit_arg:38,COMMIT_TYPE:39,commitType:40,COMMIT_MSG:41,NORMAL:42,REVERSE:43,HIGHLIGHT:44,openDirective:45,typeDirective:46,closeDirective:47,argDirective:48,open_directive:49,type_directive:50,arg_directive:51,close_directive:52,";":53,$accept:0,$end:1},terminals_:{2:"error",6:"GG",8:"EOF",9:":",10:"DIR",13:"OPT",14:"NL",20:"acc_title",21:"acc_title_value",22:"acc_descr",23:"acc_descr_value",24:"acc_descr_multiline_value",25:"section",27:"CHECKOUT",28:"ID",29:"BRANCH",30:"ORDER",31:"NUM",32:"CHERRY_PICK",33:"COMMIT_ID",34:"STR",35:"MERGE",36:"COMMIT_TAG",37:"COMMIT",39:"COMMIT_TYPE",41:"COMMIT_MSG",42:"NORMAL",43:"REVERSE",44:"HIGHLIGHT",49:"open_directive",50:"type_directive",51:"arg_directive",52:"close_directive",53:";"},productions_:[0,[3,2],[3,2],[3,3],[3,4],[3,5],[7,0],[7,2],[11,2],[11,1],[12,0],[12,2],[15,2],[15,1],[16,1],[16,1],[16,1],[16,2],[16,2],[16,1],[16,1],[16,1],[16,2],[26,2],[26,4],[19,3],[18,2],[18,4],[17,2],[17,3],[17,3],[17,5],[17,5],[17,3],[17,5],[17,5],[17,5],[17,5],[17,7],[17,7],[17,7],[17,7],[17,7],[17,7],[17,3],[17,5],[17,5],[17,5],[17,5],[17,5],[17,5],[17,7],[17,7],[17,7],[17,7],[17,7],[17,7],[17,7],[17,7],[17,7],[17,7],[17,7],[17,7],[17,7],[17,7],[17,7],[17,7],[17,7],[17,7],[17,9],[17,9],[17,9],[17,9],[17,9],[17,9],[17,9],[17,9],[17,9],[17,9],[17,9],[17,9],[17,9],[17,9],[17,9],[17,9],[17,9],[17,9],[17,9],[17,9],[17,9],[17,9],[17,9],[17,9],[38,0],[38,1],[40,1],[40,1],[40,1],[5,3],[5,5],[45,1],[46,1],[48,1],[47,1],[4,1],[4,1],[4,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 3:return a[s];case 4:return a[s-1];case 5:return r.setDirection(a[s-3]),a[s-1];case 7:r.setOptions(a[s-1]),this.$=a[s];break;case 8:a[s-1]+=a[s],this.$=a[s-1];break;case 10:this.$=[];break;case 11:a[s-1].push(a[s]),this.$=a[s-1];break;case 12:this.$=a[s-1];break;case 17:this.$=a[s].trim(),r.setAccTitle(this.$);break;case 18:case 19:this.$=a[s].trim(),r.setAccDescription(this.$);break;case 20:r.addSection(a[s].substr(8)),this.$=a[s].substr(8);break;case 22:r.checkout(a[s]);break;case 23:r.branch(a[s]);break;case 24:r.branch(a[s-2],a[s]);break;case 25:r.cherryPick(a[s]);break;case 26:r.merge(a[s]);break;case 27:r.merge(a[s-2],a[s]);break;case 28:r.commit(a[s]);break;case 29:r.commit("","",r.commitType.NORMAL,a[s]);break;case 30:r.commit("","",a[s],"");break;case 31:r.commit("","",a[s],a[s-2]);break;case 32:r.commit("","",a[s-2],a[s]);break;case 33:r.commit("",a[s],r.commitType.NORMAL,"");break;case 34:r.commit("",a[s-2],r.commitType.NORMAL,a[s]);break;case 35:r.commit("",a[s],r.commitType.NORMAL,a[s-2]);break;case 36:r.commit("",a[s-2],a[s],"");break;case 37:r.commit("",a[s],a[s-2],"");break;case 38:r.commit("",a[s-4],a[s-2],a[s]);break;case 39:r.commit("",a[s-4],a[s],a[s-2]);break;case 40:r.commit("",a[s-2],a[s-4],a[s]);break;case 41:r.commit("",a[s],a[s-4],a[s-2]);break;case 42:r.commit("",a[s],a[s-2],a[s-4]);break;case 43:r.commit("",a[s-2],a[s],a[s-4]);break;case 44:r.commit(a[s],"",r.commitType.NORMAL,"");break;case 45:r.commit(a[s],"",r.commitType.NORMAL,a[s-2]);break;case 46:r.commit(a[s-2],"",r.commitType.NORMAL,a[s]);break;case 47:r.commit(a[s-2],"",a[s],"");break;case 48:r.commit(a[s],"",a[s-2],"");break;case 49:r.commit(a[s],a[s-2],r.commitType.NORMAL,"");break;case 50:r.commit(a[s-2],a[s],r.commitType.NORMAL,"");break;case 51:r.commit(a[s-4],"",a[s-2],a[s]);break;case 52:r.commit(a[s-4],"",a[s],a[s-2]);break;case 53:r.commit(a[s-2],"",a[s-4],a[s]);break;case 54:r.commit(a[s],"",a[s-4],a[s-2]);break;case 55:r.commit(a[s],"",a[s-2],a[s-4]);break;case 56:r.commit(a[s-2],"",a[s],a[s-4]);break;case 57:r.commit(a[s-4],a[s],a[s-2],"");break;case 58:r.commit(a[s-4],a[s-2],a[s],"");break;case 59:r.commit(a[s-2],a[s],a[s-4],"");break;case 60:r.commit(a[s],a[s-2],a[s-4],"");break;case 61:r.commit(a[s],a[s-4],a[s-2],"");break;case 62:r.commit(a[s-2],a[s-4],a[s],"");break;case 63:r.commit(a[s-4],a[s],r.commitType.NORMAL,a[s-2]);break;case 64:r.commit(a[s-4],a[s-2],r.commitType.NORMAL,a[s]);break;case 65:r.commit(a[s-2],a[s],r.commitType.NORMAL,a[s-4]);break;case 66:r.commit(a[s],a[s-2],r.commitType.NORMAL,a[s-4]);break;case 67:r.commit(a[s],a[s-4],r.commitType.NORMAL,a[s-2]);break;case 68:r.commit(a[s-2],a[s-4],r.commitType.NORMAL,a[s]);break;case 69:r.commit(a[s-6],a[s-4],a[s-2],a[s]);break;case 70:r.commit(a[s-6],a[s-4],a[s],a[s-2]);break;case 71:r.commit(a[s-6],a[s-2],a[s-4],a[s]);break;case 72:r.commit(a[s-6],a[s],a[s-4],a[s-2]);break;case 73:r.commit(a[s-6],a[s-2],a[s],a[s-4]);break;case 74:r.commit(a[s-6],a[s],a[s-2],a[s-4]);break;case 75:r.commit(a[s-4],a[s-6],a[s-2],a[s]);break;case 76:r.commit(a[s-4],a[s-6],a[s],a[s-2]);break;case 77:r.commit(a[s-2],a[s-6],a[s-4],a[s]);break;case 78:r.commit(a[s],a[s-6],a[s-4],a[s-2]);break;case 79:r.commit(a[s-2],a[s-6],a[s],a[s-4]);break;case 80:r.commit(a[s],a[s-6],a[s-2],a[s-4]);break;case 81:r.commit(a[s],a[s-4],a[s-2],a[s-6]);break;case 82:r.commit(a[s-2],a[s-4],a[s],a[s-6]);break;case 83:r.commit(a[s],a[s-2],a[s-4],a[s-6]);break;case 84:r.commit(a[s-2],a[s],a[s-4],a[s-6]);break;case 85:r.commit(a[s-4],a[s-2],a[s],a[s-6]);break;case 86:r.commit(a[s-4],a[s],a[s-2],a[s-6]);break;case 87:r.commit(a[s-2],a[s-4],a[s-6],a[s]);break;case 88:r.commit(a[s],a[s-4],a[s-6],a[s-2]);break;case 89:r.commit(a[s-2],a[s],a[s-6],a[s-4]);break;case 90:r.commit(a[s],a[s-2],a[s-6],a[s-4]);break;case 91:r.commit(a[s-4],a[s-2],a[s-6],a[s]);break;case 92:r.commit(a[s-4],a[s],a[s-6],a[s-2]);break;case 93:this.$="";break;case 94:this.$=a[s];break;case 95:this.$=r.commitType.NORMAL;break;case 96:this.$=r.commitType.REVERSE;break;case 97:this.$=r.commitType.HIGHLIGHT;break;case 100:r.parseDirective("%%{","open_directive");break;case 101:r.parseDirective(a[s],"type_directive");break;case 102:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 103:r.parseDirective("}%%","close_directive","gitGraph")}},table:[{3:1,4:2,5:3,6:e,8:n,14:r,45:8,49:i,53:a},{1:[3]},{3:10,4:2,5:3,6:e,8:n,14:r,45:8,49:i,53:a},{3:11,4:2,5:3,6:e,8:n,14:r,45:8,49:i,53:a},{7:12,8:o,9:[1,13],10:[1,14],11:15,14:s},t(c,[2,104]),t(c,[2,105]),t(c,[2,106]),{46:17,50:[1,18]},{50:[2,100]},{1:[2,1]},{1:[2,2]},{8:[1,19]},{7:20,8:o,11:15,14:s},{9:[1,21]},t(l,[2,10],{12:22,13:[1,23]}),t(u,[2,9]),{9:[1,25],47:24,52:h},t([9,52],[2,101]),{1:[2,3]},{8:[1,27]},{7:28,8:o,11:15,14:s},{8:[2,7],14:[1,31],15:29,16:30,17:32,18:33,19:34,20:[1,35],22:[1,36],24:[1,37],25:[1,38],26:39,27:[1,40],29:[1,44],32:[1,43],35:[1,42],37:[1,41]},t(u,[2,8]),t(f,[2,98]),{48:45,51:[1,46]},t(f,[2,103]),{1:[2,4]},{8:[1,47]},t(l,[2,11]),{4:48,8:n,14:r,53:a},t(l,[2,13]),t(d,[2,14]),t(d,[2,15]),t(d,[2,16]),{21:[1,49]},{23:[1,50]},t(d,[2,19]),t(d,[2,20]),t(d,[2,21]),{28:[1,51]},t(d,[2,93],{38:52,33:[1,55],34:[1,57],36:[1,53],39:[1,54],41:[1,56]}),{28:[1,58]},{33:[1,59]},{28:[1,60]},{47:61,52:h},{52:[2,102]},{1:[2,5]},t(l,[2,12]),t(d,[2,17]),t(d,[2,18]),t(d,[2,22]),t(d,[2,28]),{34:[1,62]},{40:63,42:p,43:g,44:y},{34:[1,67]},{34:[1,68]},t(d,[2,94]),t(d,[2,26],{36:[1,69]}),{34:[1,70]},t(d,[2,23],{30:[1,71]}),t(f,[2,99]),t(d,[2,29],{33:[1,73],39:[1,72],41:[1,74]}),t(d,[2,30],{33:[1,76],36:[1,75],41:[1,77]}),t(m,[2,95]),t(m,[2,96]),t(m,[2,97]),t(d,[2,33],{36:[1,78],39:[1,79],41:[1,80]}),t(d,[2,44],{33:[1,83],36:[1,81],39:[1,82]}),{34:[1,84]},t(d,[2,25]),{31:[1,85]},{40:86,42:p,43:g,44:y},{34:[1,87]},{34:[1,88]},{34:[1,89]},{34:[1,90]},{34:[1,91]},{34:[1,92]},{40:93,42:p,43:g,44:y},{34:[1,94]},{34:[1,95]},{40:96,42:p,43:g,44:y},{34:[1,97]},t(d,[2,27]),t(d,[2,24]),t(d,[2,31],{33:[1,98],41:[1,99]}),t(d,[2,35],{39:[1,100],41:[1,101]}),t(d,[2,45],{33:[1,103],39:[1,102]}),t(d,[2,32],{33:[1,104],41:[1,105]}),t(d,[2,37],{36:[1,106],41:[1,107]}),t(d,[2,48],{33:[1,109],36:[1,108]}),t(d,[2,34],{39:[1,110],41:[1,111]}),t(d,[2,36],{36:[1,112],41:[1,113]}),t(d,[2,49],{36:[1,115],39:[1,114]}),t(d,[2,46],{33:[1,117],39:[1,116]}),t(d,[2,47],{33:[1,119],36:[1,118]}),t(d,[2,50],{36:[1,121],39:[1,120]}),{34:[1,122]},{34:[1,123]},{40:124,42:p,43:g,44:y},{34:[1,125]},{40:126,42:p,43:g,44:y},{34:[1,127]},{34:[1,128]},{34:[1,129]},{34:[1,130]},{34:[1,131]},{34:[1,132]},{34:[1,133]},{40:134,42:p,43:g,44:y},{34:[1,135]},{34:[1,136]},{34:[1,137]},{40:138,42:p,43:g,44:y},{34:[1,139]},{40:140,42:p,43:g,44:y},{34:[1,141]},{34:[1,142]},{34:[1,143]},{40:144,42:p,43:g,44:y},{34:[1,145]},t(d,[2,42],{41:[1,146]}),t(d,[2,55],{33:[1,147]}),t(d,[2,43],{41:[1,148]}),t(d,[2,66],{39:[1,149]}),t(d,[2,56],{33:[1,150]}),t(d,[2,65],{39:[1,151]}),t(d,[2,41],{41:[1,152]}),t(d,[2,54],{33:[1,153]}),t(d,[2,40],{41:[1,154]}),t(d,[2,60],{36:[1,155]}),t(d,[2,53],{33:[1,156]}),t(d,[2,59],{36:[1,157]}),t(d,[2,39],{41:[1,158]}),t(d,[2,67],{39:[1,159]}),t(d,[2,38],{41:[1,160]}),t(d,[2,61],{36:[1,161]}),t(d,[2,62],{36:[1,162]}),t(d,[2,68],{39:[1,163]}),t(d,[2,52],{33:[1,164]}),t(d,[2,63],{39:[1,165]}),t(d,[2,51],{33:[1,166]}),t(d,[2,57],{36:[1,167]}),t(d,[2,58],{36:[1,168]}),t(d,[2,64],{39:[1,169]}),{34:[1,170]},{34:[1,171]},{34:[1,172]},{40:173,42:p,43:g,44:y},{34:[1,174]},{40:175,42:p,43:g,44:y},{34:[1,176]},{34:[1,177]},{34:[1,178]},{34:[1,179]},{34:[1,180]},{34:[1,181]},{34:[1,182]},{40:183,42:p,43:g,44:y},{34:[1,184]},{34:[1,185]},{34:[1,186]},{40:187,42:p,43:g,44:y},{34:[1,188]},{40:189,42:p,43:g,44:y},{34:[1,190]},{34:[1,191]},{34:[1,192]},{40:193,42:p,43:g,44:y},t(d,[2,83]),t(d,[2,84]),t(d,[2,81]),t(d,[2,82]),t(d,[2,86]),t(d,[2,85]),t(d,[2,90]),t(d,[2,89]),t(d,[2,88]),t(d,[2,87]),t(d,[2,92]),t(d,[2,91]),t(d,[2,80]),t(d,[2,79]),t(d,[2,78]),t(d,[2,77]),t(d,[2,75]),t(d,[2,76]),t(d,[2,74]),t(d,[2,73]),t(d,[2,72]),t(d,[2,71]),t(d,[2,69]),t(d,[2,70])],defaultActions:{9:[2,100],10:[2,1],11:[2,2],19:[2,3],27:[2,4],46:[2,102],47:[2,5]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,l=0,u=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var m=p.yylloc;a.push(m);var b=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,x,k,w,T,E,C,S,A,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==_&&(_=v()),w=o[k]&&o[k][_]),void 0===w||!w.length||!w[0]){var N="";for(E in A=[],o[k])this.terminals_[E]&&E>h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(c+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:m,expected:A})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+_);switch(w[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),_=null,x?(_=x,x=null):(l=p.yyleng,s=p.yytext,c=p.yylineno,m=p.yylloc,u>0&&u--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},b&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,l,c,g.yy,w[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},v={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),49;case 1:return this.begin("type_directive"),50;case 2:return this.popState(),this.begin("arg_directive"),9;case 3:return this.popState(),this.popState(),52;case 4:return 51;case 5:return this.begin("acc_title"),20;case 6:return this.popState(),"acc_title_value";case 7:return this.begin("acc_descr"),22;case 8:return this.popState(),"acc_descr_value";case 9:this.begin("acc_descr_multiline");break;case 10:case 35:case 38:this.popState();break;case 11:return"acc_descr_multiline_value";case 12:return 14;case 13:case 14:case 15:break;case 16:return 6;case 17:return 37;case 18:return 33;case 19:return 39;case 20:return 41;case 21:return 42;case 22:return 43;case 23:return 44;case 24:return 36;case 25:return 29;case 26:return 30;case 27:return 35;case 28:return 32;case 29:return 27;case 30:case 31:return 10;case 32:return 9;case 33:return"CARET";case 34:this.begin("options");break;case 36:return 13;case 37:this.begin("string");break;case 39:return 34;case 40:return 31;case 41:return 28;case 42:return 8}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:(\r?\n)+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:gitGraph\b)/i,/^(?:commit\b)/i,/^(?:id:)/i,/^(?:type:)/i,/^(?:msg:)/i,/^(?:NORMAL\b)/i,/^(?:REVERSE\b)/i,/^(?:HIGHLIGHT\b)/i,/^(?:tag:)/i,/^(?:branch\b)/i,/^(?:order:)/i,/^(?:merge\b)/i,/^(?:cherry-pick\b)/i,/^(?:checkout\b)/i,/^(?:LR\b)/i,/^(?:BT\b)/i,/^(?::)/i,/^(?:\^)/i,/^(?:options\r?\n)/i,/^(?:[ \r\n\t]+end\b)/i,/^(?:[\s\S]+(?=[ \r\n\t]+end))/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[0-9]+)/i,/^(?:[a-zA-Z][-_\./a-zA-Z0-9]*[-_a-zA-Z0-9])/i,/^(?:$)/i],conditions:{acc_descr_multiline:{rules:[10,11],inclusive:!1},acc_descr:{rules:[8],inclusive:!1},acc_title:{rules:[6],inclusive:!1},close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},options:{rules:[35,36],inclusive:!1},string:{rules:[38,39],inclusive:!1},INITIAL:{rules:[0,5,7,9,12,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,37,40,41,42],inclusive:!0}}};function _(){this.yy={}}return b.lexer=v,_.prototype=b,b.Parser=_,new _}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(8183).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},6765:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[6,9,10],n={trace:function(){},yy:{},symbols_:{error:2,start:3,info:4,document:5,EOF:6,line:7,statement:8,NL:9,showInfo:10,$accept:0,$end:1},terminals_:{2:"error",4:"info",6:"EOF",9:"NL",10:"showInfo"},productions_:[0,[3,3],[5,0],[5,2],[7,1],[7,1],[8,1]],performAction:function(t,e,n,r,i,a,o){switch(a.length,i){case 1:return r;case 4:break;case 6:r.setInfo(!0)}},table:[{3:1,4:[1,2]},{1:[3]},t(e,[2,2],{5:3}),{6:[1,4],7:5,8:6,9:[1,7],10:[1,8]},{1:[2,1]},t(e,[2,3]),t(e,[2,4]),t(e,[2,5]),t(e,[2,6])],defaultActions:{4:[2,1]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,l=0,u=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var m=p.yylloc;a.push(m);var b=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,x,k,w,T,E,C,S,A,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==_&&(_=v()),w=o[k]&&o[k][_]),void 0===w||!w.length||!w[0]){var N="";for(E in A=[],o[k])this.terminals_[E]&&E>h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(c+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:m,expected:A})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+_);switch(w[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),_=null,x?(_=x,x=null):(l=p.yyleng,s=p.yytext,c=p.yylineno,m=p.yylloc,u>0&&u--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},b&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,l,c,g.yy,w[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},r={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return 4;case 1:return 9;case 2:return"space";case 3:return 10;case 4:return 6;case 5:return"TXT"}},rules:[/^(?:info\b)/i,/^(?:[\s\n\r]+)/i,/^(?:[\s]+)/i,/^(?:showInfo\b)/i,/^(?:$)/i,/^(?:.)/i],conditions:{INITIAL:{rules:[0,1,2,3,4,5],inclusive:!0}}};function i(){this.yy={}}return n.lexer=r,i.prototype=n,n.Parser=i,new i}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(1428).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},7062:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,4],n=[1,5],r=[1,6],i=[1,7],a=[1,9],o=[1,11,13,15,17,19,20,26,27,28,29],s=[2,5],c=[1,6,11,13,15,17,19,20,26,27,28,29],l=[26,27,28],u=[2,8],h=[1,18],f=[1,19],d=[1,20],p=[1,21],g=[1,22],y=[1,23],m=[1,28],b=[6,26,27,28,29],v={trace:function(){},yy:{},symbols_:{error:2,start:3,eol:4,directive:5,PIE:6,document:7,showData:8,line:9,statement:10,txt:11,value:12,title:13,title_value:14,acc_title:15,acc_title_value:16,acc_descr:17,acc_descr_value:18,acc_descr_multiline_value:19,section:20,openDirective:21,typeDirective:22,closeDirective:23,":":24,argDirective:25,NEWLINE:26,";":27,EOF:28,open_directive:29,type_directive:30,arg_directive:31,close_directive:32,$accept:0,$end:1},terminals_:{2:"error",6:"PIE",8:"showData",11:"txt",12:"value",13:"title",14:"title_value",15:"acc_title",16:"acc_title_value",17:"acc_descr",18:"acc_descr_value",19:"acc_descr_multiline_value",20:"section",24:":",26:"NEWLINE",27:";",28:"EOF",29:"open_directive",30:"type_directive",31:"arg_directive",32:"close_directive"},productions_:[0,[3,2],[3,2],[3,2],[3,3],[7,0],[7,2],[9,2],[10,0],[10,2],[10,2],[10,2],[10,2],[10,1],[10,1],[10,1],[5,3],[5,5],[4,1],[4,1],[4,1],[21,1],[22,1],[25,1],[23,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 4:r.setShowData(!0);break;case 7:this.$=a[s-1];break;case 9:r.addSection(a[s-1],r.cleanupValue(a[s]));break;case 10:this.$=a[s].trim(),r.setDiagramTitle(this.$);break;case 11:this.$=a[s].trim(),r.setAccTitle(this.$);break;case 12:case 13:this.$=a[s].trim(),r.setAccDescription(this.$);break;case 14:r.addSection(a[s].substr(8)),this.$=a[s].substr(8);break;case 21:r.parseDirective("%%{","open_directive");break;case 22:r.parseDirective(a[s],"type_directive");break;case 23:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 24:r.parseDirective("}%%","close_directive","pie")}},table:[{3:1,4:2,5:3,6:e,21:8,26:n,27:r,28:i,29:a},{1:[3]},{3:10,4:2,5:3,6:e,21:8,26:n,27:r,28:i,29:a},{3:11,4:2,5:3,6:e,21:8,26:n,27:r,28:i,29:a},t(o,s,{7:12,8:[1,13]}),t(c,[2,18]),t(c,[2,19]),t(c,[2,20]),{22:14,30:[1,15]},{30:[2,21]},{1:[2,1]},{1:[2,2]},t(l,u,{21:8,9:16,10:17,5:24,1:[2,3],11:h,13:f,15:d,17:p,19:g,20:y,29:a}),t(o,s,{7:25}),{23:26,24:[1,27],32:m},t([24,32],[2,22]),t(o,[2,6]),{4:29,26:n,27:r,28:i},{12:[1,30]},{14:[1,31]},{16:[1,32]},{18:[1,33]},t(l,[2,13]),t(l,[2,14]),t(l,[2,15]),t(l,u,{21:8,9:16,10:17,5:24,1:[2,4],11:h,13:f,15:d,17:p,19:g,20:y,29:a}),t(b,[2,16]),{25:34,31:[1,35]},t(b,[2,24]),t(o,[2,7]),t(l,[2,9]),t(l,[2,10]),t(l,[2,11]),t(l,[2,12]),{23:36,32:m},{32:[2,23]},t(b,[2,17])],defaultActions:{9:[2,21],10:[2,1],11:[2,2],35:[2,23]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,l=0,u=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var m=p.yylloc;a.push(m);var b=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,x,k,w,T,E,C,S,A,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==_&&(_=v()),w=o[k]&&o[k][_]),void 0===w||!w.length||!w[0]){var N="";for(E in A=[],o[k])this.terminals_[E]&&E>h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(c+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:m,expected:A})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+_);switch(w[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),_=null,x?(_=x,x=null):(l=p.yyleng,s=p.yytext,c=p.yylineno,m=p.yylloc,u>0&&u--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},b&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,l,c,g.yy,w[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},_={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),29;case 1:return this.begin("type_directive"),30;case 2:return this.popState(),this.begin("arg_directive"),24;case 3:return this.popState(),this.popState(),32;case 4:return 31;case 5:case 6:case 8:case 9:break;case 7:return 26;case 10:return this.begin("title"),13;case 11:return this.popState(),"title_value";case 12:return this.begin("acc_title"),15;case 13:return this.popState(),"acc_title_value";case 14:return this.begin("acc_descr"),17;case 15:return this.popState(),"acc_descr_value";case 16:this.begin("acc_descr_multiline");break;case 17:case 20:this.popState();break;case 18:return"acc_descr_multiline_value";case 19:this.begin("string");break;case 21:return"txt";case 22:return 6;case 23:return 8;case 24:return"value";case 25:return 28}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n\r]+)/i,/^(?:%%[^\n]*)/i,/^(?:[\s]+)/i,/^(?:title\b)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:pie\b)/i,/^(?:showData\b)/i,/^(?::[\s]*[\d]+(?:\.[\d]+)?)/i,/^(?:$)/i],conditions:{acc_descr_multiline:{rules:[17,18],inclusive:!1},acc_descr:{rules:[15],inclusive:!1},acc_title:{rules:[13],inclusive:!1},close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},title:{rules:[11],inclusive:!1},string:{rules:[20,21],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,12,14,16,19,22,23,24,25],inclusive:!0}}};function x(){this.yy={}}return v.lexer=_,x.prototype=v,v.Parser=x,new x}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(4551).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},3176:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,3],n=[1,5],r=[1,6],i=[1,7],a=[1,8],o=[5,6,8,14,16,18,19,40,41,42,43,44,45,53,71,72],s=[1,22],c=[2,13],l=[1,26],u=[1,27],h=[1,28],f=[1,29],d=[1,30],p=[1,31],g=[1,24],y=[1,32],m=[1,33],b=[1,36],v=[71,72],_=[5,8,14,16,18,19,40,41,42,43,44,45,53,60,62,71,72],x=[1,56],k=[1,57],w=[1,58],T=[1,59],E=[1,60],C=[1,61],S=[1,62],A=[62,63],M=[1,74],N=[1,70],D=[1,71],O=[1,72],B=[1,73],L=[1,75],I=[1,79],F=[1,80],R=[1,77],P=[1,78],j=[5,8,14,16,18,19,40,41,42,43,44,45,53,71,72],z={trace:function(){},yy:{},symbols_:{error:2,start:3,directive:4,NEWLINE:5,RD:6,diagram:7,EOF:8,openDirective:9,typeDirective:10,closeDirective:11,":":12,argDirective:13,acc_title:14,acc_title_value:15,acc_descr:16,acc_descr_value:17,acc_descr_multiline_value:18,open_directive:19,type_directive:20,arg_directive:21,close_directive:22,requirementDef:23,elementDef:24,relationshipDef:25,requirementType:26,requirementName:27,STRUCT_START:28,requirementBody:29,ID:30,COLONSEP:31,id:32,TEXT:33,text:34,RISK:35,riskLevel:36,VERIFYMTHD:37,verifyType:38,STRUCT_STOP:39,REQUIREMENT:40,FUNCTIONAL_REQUIREMENT:41,INTERFACE_REQUIREMENT:42,PERFORMANCE_REQUIREMENT:43,PHYSICAL_REQUIREMENT:44,DESIGN_CONSTRAINT:45,LOW_RISK:46,MED_RISK:47,HIGH_RISK:48,VERIFY_ANALYSIS:49,VERIFY_DEMONSTRATION:50,VERIFY_INSPECTION:51,VERIFY_TEST:52,ELEMENT:53,elementName:54,elementBody:55,TYPE:56,type:57,DOCREF:58,ref:59,END_ARROW_L:60,relationship:61,LINE:62,END_ARROW_R:63,CONTAINS:64,COPIES:65,DERIVES:66,SATISFIES:67,VERIFIES:68,REFINES:69,TRACES:70,unqString:71,qString:72,$accept:0,$end:1},terminals_:{2:"error",5:"NEWLINE",6:"RD",8:"EOF",12:":",14:"acc_title",15:"acc_title_value",16:"acc_descr",17:"acc_descr_value",18:"acc_descr_multiline_value",19:"open_directive",20:"type_directive",21:"arg_directive",22:"close_directive",28:"STRUCT_START",30:"ID",31:"COLONSEP",33:"TEXT",35:"RISK",37:"VERIFYMTHD",39:"STRUCT_STOP",40:"REQUIREMENT",41:"FUNCTIONAL_REQUIREMENT",42:"INTERFACE_REQUIREMENT",43:"PERFORMANCE_REQUIREMENT",44:"PHYSICAL_REQUIREMENT",45:"DESIGN_CONSTRAINT",46:"LOW_RISK",47:"MED_RISK",48:"HIGH_RISK",49:"VERIFY_ANALYSIS",50:"VERIFY_DEMONSTRATION",51:"VERIFY_INSPECTION",52:"VERIFY_TEST",53:"ELEMENT",56:"TYPE",58:"DOCREF",60:"END_ARROW_L",62:"LINE",63:"END_ARROW_R",64:"CONTAINS",65:"COPIES",66:"DERIVES",67:"SATISFIES",68:"VERIFIES",69:"REFINES",70:"TRACES",71:"unqString",72:"qString"},productions_:[0,[3,3],[3,2],[3,4],[4,3],[4,5],[4,2],[4,2],[4,1],[9,1],[10,1],[13,1],[11,1],[7,0],[7,2],[7,2],[7,2],[7,2],[7,2],[23,5],[29,5],[29,5],[29,5],[29,5],[29,2],[29,1],[26,1],[26,1],[26,1],[26,1],[26,1],[26,1],[36,1],[36,1],[36,1],[38,1],[38,1],[38,1],[38,1],[24,5],[55,5],[55,5],[55,2],[55,1],[25,5],[25,5],[61,1],[61,1],[61,1],[61,1],[61,1],[61,1],[61,1],[27,1],[27,1],[32,1],[32,1],[34,1],[34,1],[54,1],[54,1],[57,1],[57,1],[59,1],[59,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 6:this.$=a[s].trim(),r.setAccTitle(this.$);break;case 7:case 8:this.$=a[s].trim(),r.setAccDescription(this.$);break;case 9:r.parseDirective("%%{","open_directive");break;case 10:r.parseDirective(a[s],"type_directive");break;case 11:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 12:r.parseDirective("}%%","close_directive","pie");break;case 13:this.$=[];break;case 19:r.addRequirement(a[s-3],a[s-4]);break;case 20:r.setNewReqId(a[s-2]);break;case 21:r.setNewReqText(a[s-2]);break;case 22:r.setNewReqRisk(a[s-2]);break;case 23:r.setNewReqVerifyMethod(a[s-2]);break;case 26:this.$=r.RequirementType.REQUIREMENT;break;case 27:this.$=r.RequirementType.FUNCTIONAL_REQUIREMENT;break;case 28:this.$=r.RequirementType.INTERFACE_REQUIREMENT;break;case 29:this.$=r.RequirementType.PERFORMANCE_REQUIREMENT;break;case 30:this.$=r.RequirementType.PHYSICAL_REQUIREMENT;break;case 31:this.$=r.RequirementType.DESIGN_CONSTRAINT;break;case 32:this.$=r.RiskLevel.LOW_RISK;break;case 33:this.$=r.RiskLevel.MED_RISK;break;case 34:this.$=r.RiskLevel.HIGH_RISK;break;case 35:this.$=r.VerifyType.VERIFY_ANALYSIS;break;case 36:this.$=r.VerifyType.VERIFY_DEMONSTRATION;break;case 37:this.$=r.VerifyType.VERIFY_INSPECTION;break;case 38:this.$=r.VerifyType.VERIFY_TEST;break;case 39:r.addElement(a[s-3]);break;case 40:r.setNewElementType(a[s-2]);break;case 41:r.setNewElementDocRef(a[s-2]);break;case 44:r.addRelationship(a[s-2],a[s],a[s-4]);break;case 45:r.addRelationship(a[s-2],a[s-4],a[s]);break;case 46:this.$=r.Relationships.CONTAINS;break;case 47:this.$=r.Relationships.COPIES;break;case 48:this.$=r.Relationships.DERIVES;break;case 49:this.$=r.Relationships.SATISFIES;break;case 50:this.$=r.Relationships.VERIFIES;break;case 51:this.$=r.Relationships.REFINES;break;case 52:this.$=r.Relationships.TRACES}},table:[{3:1,4:2,6:e,9:4,14:n,16:r,18:i,19:a},{1:[3]},{3:10,4:2,5:[1,9],6:e,9:4,14:n,16:r,18:i,19:a},{5:[1,11]},{10:12,20:[1,13]},{15:[1,14]},{17:[1,15]},t(o,[2,8]),{20:[2,9]},{3:16,4:2,6:e,9:4,14:n,16:r,18:i,19:a},{1:[2,2]},{4:21,5:s,7:17,8:c,9:4,14:n,16:r,18:i,19:a,23:18,24:19,25:20,26:23,32:25,40:l,41:u,42:h,43:f,44:d,45:p,53:g,71:y,72:m},{11:34,12:[1,35],22:b},t([12,22],[2,10]),t(o,[2,6]),t(o,[2,7]),{1:[2,1]},{8:[1,37]},{4:21,5:s,7:38,8:c,9:4,14:n,16:r,18:i,19:a,23:18,24:19,25:20,26:23,32:25,40:l,41:u,42:h,43:f,44:d,45:p,53:g,71:y,72:m},{4:21,5:s,7:39,8:c,9:4,14:n,16:r,18:i,19:a,23:18,24:19,25:20,26:23,32:25,40:l,41:u,42:h,43:f,44:d,45:p,53:g,71:y,72:m},{4:21,5:s,7:40,8:c,9:4,14:n,16:r,18:i,19:a,23:18,24:19,25:20,26:23,32:25,40:l,41:u,42:h,43:f,44:d,45:p,53:g,71:y,72:m},{4:21,5:s,7:41,8:c,9:4,14:n,16:r,18:i,19:a,23:18,24:19,25:20,26:23,32:25,40:l,41:u,42:h,43:f,44:d,45:p,53:g,71:y,72:m},{4:21,5:s,7:42,8:c,9:4,14:n,16:r,18:i,19:a,23:18,24:19,25:20,26:23,32:25,40:l,41:u,42:h,43:f,44:d,45:p,53:g,71:y,72:m},{27:43,71:[1,44],72:[1,45]},{54:46,71:[1,47],72:[1,48]},{60:[1,49],62:[1,50]},t(v,[2,26]),t(v,[2,27]),t(v,[2,28]),t(v,[2,29]),t(v,[2,30]),t(v,[2,31]),t(_,[2,55]),t(_,[2,56]),t(o,[2,4]),{13:51,21:[1,52]},t(o,[2,12]),{1:[2,3]},{8:[2,14]},{8:[2,15]},{8:[2,16]},{8:[2,17]},{8:[2,18]},{28:[1,53]},{28:[2,53]},{28:[2,54]},{28:[1,54]},{28:[2,59]},{28:[2,60]},{61:55,64:x,65:k,66:w,67:T,68:E,69:C,70:S},{61:63,64:x,65:k,66:w,67:T,68:E,69:C,70:S},{11:64,22:b},{22:[2,11]},{5:[1,65]},{5:[1,66]},{62:[1,67]},t(A,[2,46]),t(A,[2,47]),t(A,[2,48]),t(A,[2,49]),t(A,[2,50]),t(A,[2,51]),t(A,[2,52]),{63:[1,68]},t(o,[2,5]),{5:M,29:69,30:N,33:D,35:O,37:B,39:L},{5:I,39:F,55:76,56:R,58:P},{32:81,71:y,72:m},{32:82,71:y,72:m},t(j,[2,19]),{31:[1,83]},{31:[1,84]},{31:[1,85]},{31:[1,86]},{5:M,29:87,30:N,33:D,35:O,37:B,39:L},t(j,[2,25]),t(j,[2,39]),{31:[1,88]},{31:[1,89]},{5:I,39:F,55:90,56:R,58:P},t(j,[2,43]),t(j,[2,44]),t(j,[2,45]),{32:91,71:y,72:m},{34:92,71:[1,93],72:[1,94]},{36:95,46:[1,96],47:[1,97],48:[1,98]},{38:99,49:[1,100],50:[1,101],51:[1,102],52:[1,103]},t(j,[2,24]),{57:104,71:[1,105],72:[1,106]},{59:107,71:[1,108],72:[1,109]},t(j,[2,42]),{5:[1,110]},{5:[1,111]},{5:[2,57]},{5:[2,58]},{5:[1,112]},{5:[2,32]},{5:[2,33]},{5:[2,34]},{5:[1,113]},{5:[2,35]},{5:[2,36]},{5:[2,37]},{5:[2,38]},{5:[1,114]},{5:[2,61]},{5:[2,62]},{5:[1,115]},{5:[2,63]},{5:[2,64]},{5:M,29:116,30:N,33:D,35:O,37:B,39:L},{5:M,29:117,30:N,33:D,35:O,37:B,39:L},{5:M,29:118,30:N,33:D,35:O,37:B,39:L},{5:M,29:119,30:N,33:D,35:O,37:B,39:L},{5:I,39:F,55:120,56:R,58:P},{5:I,39:F,55:121,56:R,58:P},t(j,[2,20]),t(j,[2,21]),t(j,[2,22]),t(j,[2,23]),t(j,[2,40]),t(j,[2,41])],defaultActions:{8:[2,9],10:[2,2],16:[2,1],37:[2,3],38:[2,14],39:[2,15],40:[2,16],41:[2,17],42:[2,18],44:[2,53],45:[2,54],47:[2,59],48:[2,60],52:[2,11],93:[2,57],94:[2,58],96:[2,32],97:[2,33],98:[2,34],100:[2,35],101:[2,36],102:[2,37],103:[2,38],105:[2,61],106:[2,62],108:[2,63],109:[2,64]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,l=0,u=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var m=p.yylloc;a.push(m);var b=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,x,k,w,T,E,C,S,A,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==_&&(_=v()),w=o[k]&&o[k][_]),void 0===w||!w.length||!w[0]){var N="";for(E in A=[],o[k])this.terminals_[E]&&E>h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(c+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:m,expected:A})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+_);switch(w[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),_=null,x?(_=x,x=null):(l=p.yyleng,s=p.yytext,c=p.yylineno,m=p.yylloc,u>0&&u--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},b&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,l,c,g.yy,w[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},Y={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),19;case 1:return this.begin("type_directive"),20;case 2:return this.popState(),this.begin("arg_directive"),12;case 3:return this.popState(),this.popState(),22;case 4:return 21;case 5:return"title";case 6:return this.begin("acc_title"),14;case 7:return this.popState(),"acc_title_value";case 8:return this.begin("acc_descr"),16;case 9:return this.popState(),"acc_descr_value";case 10:this.begin("acc_descr_multiline");break;case 11:case 53:this.popState();break;case 12:return"acc_descr_multiline_value";case 13:return 5;case 14:case 15:case 16:break;case 17:return 8;case 18:return 6;case 19:return 28;case 20:return 39;case 21:return 31;case 22:return 30;case 23:return 33;case 24:return 35;case 25:return 37;case 26:return 40;case 27:return 41;case 28:return 42;case 29:return 43;case 30:return 44;case 31:return 45;case 32:return 46;case 33:return 47;case 34:return 48;case 35:return 49;case 36:return 50;case 37:return 51;case 38:return 52;case 39:return 53;case 40:return 64;case 41:return 65;case 42:return 66;case 43:return 67;case 44:return 68;case 45:return 69;case 46:return 70;case 47:return 56;case 48:return 58;case 49:return 60;case 50:return 63;case 51:return 62;case 52:this.begin("string");break;case 54:return"qString";case 55:return e.yytext=e.yytext.trim(),71}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:title\s[^#\n;]+)/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:(\r?\n)+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:$)/i,/^(?:requirementDiagram\b)/i,/^(?:\{)/i,/^(?:\})/i,/^(?::)/i,/^(?:id\b)/i,/^(?:text\b)/i,/^(?:risk\b)/i,/^(?:verifyMethod\b)/i,/^(?:requirement\b)/i,/^(?:functionalRequirement\b)/i,/^(?:interfaceRequirement\b)/i,/^(?:performanceRequirement\b)/i,/^(?:physicalRequirement\b)/i,/^(?:designConstraint\b)/i,/^(?:low\b)/i,/^(?:medium\b)/i,/^(?:high\b)/i,/^(?:analysis\b)/i,/^(?:demonstration\b)/i,/^(?:inspection\b)/i,/^(?:test\b)/i,/^(?:element\b)/i,/^(?:contains\b)/i,/^(?:copies\b)/i,/^(?:derives\b)/i,/^(?:satisfies\b)/i,/^(?:verifies\b)/i,/^(?:refines\b)/i,/^(?:traces\b)/i,/^(?:type\b)/i,/^(?:docref\b)/i,/^(?:<-)/i,/^(?:->)/i,/^(?:-)/i,/^(?:["])/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[\w][^\r\n\{\<\>\-\=]*)/i],conditions:{acc_descr_multiline:{rules:[11,12],inclusive:!1},acc_descr:{rules:[9],inclusive:!1},acc_title:{rules:[7],inclusive:!1},close_directive:{rules:[],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},open_directive:{rules:[1],inclusive:!1},unqString:{rules:[],inclusive:!1},token:{rules:[],inclusive:!1},string:{rules:[53,54],inclusive:!1},INITIAL:{rules:[0,5,6,8,10,13,14,15,16,17,18,19,20,21,22,23,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50,51,52,55],inclusive:!0}}};function U(){this.yy={}}return z.lexer=Y,U.prototype=z,z.Parser=U,new U}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(8800).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},6876:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,3],r=[1,5],i=[1,7],a=[2,5],o=[1,15],s=[1,17],c=[1,18],l=[1,19],u=[1,21],h=[1,22],f=[1,23],d=[1,29],p=[1,30],g=[1,31],y=[1,32],m=[1,33],b=[1,34],v=[1,35],_=[1,36],x=[1,37],k=[1,38],w=[1,39],T=[1,40],E=[1,43],C=[1,44],S=[1,45],A=[1,46],M=[1,47],N=[1,48],D=[1,51],O=[1,4,5,16,20,22,25,26,32,33,34,36,38,39,40,41,42,43,45,47,49,50,51,52,53,58,59,60,61,69,79],B=[4,5,16,20,22,25,26,32,33,34,36,38,39,40,41,42,43,45,47,49,53,58,59,60,61,69,79],L=[4,5,16,20,22,25,26,32,33,34,36,38,39,40,41,42,43,45,47,49,52,53,58,59,60,61,69,79],I=[4,5,16,20,22,25,26,32,33,34,36,38,39,40,41,42,43,45,47,49,51,53,58,59,60,61,69,79],F=[4,5,16,20,22,25,26,32,33,34,36,38,39,40,41,42,43,45,47,49,50,53,58,59,60,61,69,79],R=[67,68,69],P=[1,121],j=[1,4,5,7,16,20,22,25,26,32,33,34,36,38,39,40,41,42,43,45,47,49,50,51,52,53,58,59,60,61,69,79],z={trace:function(){},yy:{},symbols_:{error:2,start:3,SPACE:4,NEWLINE:5,directive:6,SD:7,document:8,line:9,statement:10,openDirective:11,typeDirective:12,closeDirective:13,":":14,argDirective:15,participant:16,actor:17,AS:18,restOfLine:19,participant_actor:20,signal:21,autonumber:22,NUM:23,off:24,activate:25,deactivate:26,note_statement:27,links_statement:28,link_statement:29,properties_statement:30,details_statement:31,title:32,legacy_title:33,acc_title:34,acc_title_value:35,acc_descr:36,acc_descr_value:37,acc_descr_multiline_value:38,loop:39,end:40,rect:41,opt:42,alt:43,else_sections:44,par:45,par_sections:46,critical:47,option_sections:48,break:49,option:50,and:51,else:52,note:53,placement:54,text2:55,over:56,actor_pair:57,links:58,link:59,properties:60,details:61,spaceList:62,",":63,left_of:64,right_of:65,signaltype:66,"+":67,"-":68,ACTOR:69,SOLID_OPEN_ARROW:70,DOTTED_OPEN_ARROW:71,SOLID_ARROW:72,DOTTED_ARROW:73,SOLID_CROSS:74,DOTTED_CROSS:75,SOLID_POINT:76,DOTTED_POINT:77,TXT:78,open_directive:79,type_directive:80,arg_directive:81,close_directive:82,$accept:0,$end:1},terminals_:{2:"error",4:"SPACE",5:"NEWLINE",7:"SD",14:":",16:"participant",18:"AS",19:"restOfLine",20:"participant_actor",22:"autonumber",23:"NUM",24:"off",25:"activate",26:"deactivate",32:"title",33:"legacy_title",34:"acc_title",35:"acc_title_value",36:"acc_descr",37:"acc_descr_value",38:"acc_descr_multiline_value",39:"loop",40:"end",41:"rect",42:"opt",43:"alt",45:"par",47:"critical",49:"break",50:"option",51:"and",52:"else",53:"note",56:"over",58:"links",59:"link",60:"properties",61:"details",63:",",64:"left_of",65:"right_of",67:"+",68:"-",69:"ACTOR",70:"SOLID_OPEN_ARROW",71:"DOTTED_OPEN_ARROW",72:"SOLID_ARROW",73:"DOTTED_ARROW",74:"SOLID_CROSS",75:"DOTTED_CROSS",76:"SOLID_POINT",77:"DOTTED_POINT",78:"TXT",79:"open_directive",80:"type_directive",81:"arg_directive",82:"close_directive"},productions_:[0,[3,2],[3,2],[3,2],[3,2],[8,0],[8,2],[9,2],[9,1],[9,1],[6,4],[6,6],[10,5],[10,3],[10,5],[10,3],[10,2],[10,4],[10,3],[10,3],[10,2],[10,3],[10,3],[10,2],[10,2],[10,2],[10,2],[10,2],[10,1],[10,1],[10,2],[10,2],[10,1],[10,4],[10,4],[10,4],[10,4],[10,4],[10,4],[10,4],[10,1],[48,1],[48,4],[46,1],[46,4],[44,1],[44,4],[27,4],[27,4],[28,3],[29,3],[30,3],[31,3],[62,2],[62,1],[57,3],[57,1],[54,1],[54,1],[21,5],[21,5],[21,4],[17,1],[66,1],[66,1],[66,1],[66,1],[66,1],[66,1],[66,1],[66,1],[55,1],[11,1],[12,1],[15,1],[13,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 4:return r.apply(a[s]),a[s];case 5:case 9:this.$=[];break;case 6:a[s-1].push(a[s]),this.$=a[s-1];break;case 7:case 8:case 56:this.$=a[s];break;case 12:a[s-3].type="addParticipant",a[s-3].description=r.parseMessage(a[s-1]),this.$=a[s-3];break;case 13:a[s-1].type="addParticipant",this.$=a[s-1];break;case 14:a[s-3].type="addActor",a[s-3].description=r.parseMessage(a[s-1]),this.$=a[s-3];break;case 15:a[s-1].type="addActor",this.$=a[s-1];break;case 17:this.$={type:"sequenceIndex",sequenceIndex:Number(a[s-2]),sequenceIndexStep:Number(a[s-1]),sequenceVisible:!0,signalType:r.LINETYPE.AUTONUMBER};break;case 18:this.$={type:"sequenceIndex",sequenceIndex:Number(a[s-1]),sequenceIndexStep:1,sequenceVisible:!0,signalType:r.LINETYPE.AUTONUMBER};break;case 19:this.$={type:"sequenceIndex",sequenceVisible:!1,signalType:r.LINETYPE.AUTONUMBER};break;case 20:this.$={type:"sequenceIndex",sequenceVisible:!0,signalType:r.LINETYPE.AUTONUMBER};break;case 21:this.$={type:"activeStart",signalType:r.LINETYPE.ACTIVE_START,actor:a[s-1]};break;case 22:this.$={type:"activeEnd",signalType:r.LINETYPE.ACTIVE_END,actor:a[s-1]};break;case 28:r.setDiagramTitle(a[s].substring(6)),this.$=a[s].substring(6);break;case 29:r.setDiagramTitle(a[s].substring(7)),this.$=a[s].substring(7);break;case 30:this.$=a[s].trim(),r.setAccTitle(this.$);break;case 31:case 32:this.$=a[s].trim(),r.setAccDescription(this.$);break;case 33:a[s-1].unshift({type:"loopStart",loopText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.LOOP_START}),a[s-1].push({type:"loopEnd",loopText:a[s-2],signalType:r.LINETYPE.LOOP_END}),this.$=a[s-1];break;case 34:a[s-1].unshift({type:"rectStart",color:r.parseMessage(a[s-2]),signalType:r.LINETYPE.RECT_START}),a[s-1].push({type:"rectEnd",color:r.parseMessage(a[s-2]),signalType:r.LINETYPE.RECT_END}),this.$=a[s-1];break;case 35:a[s-1].unshift({type:"optStart",optText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.OPT_START}),a[s-1].push({type:"optEnd",optText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.OPT_END}),this.$=a[s-1];break;case 36:a[s-1].unshift({type:"altStart",altText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.ALT_START}),a[s-1].push({type:"altEnd",signalType:r.LINETYPE.ALT_END}),this.$=a[s-1];break;case 37:a[s-1].unshift({type:"parStart",parText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.PAR_START}),a[s-1].push({type:"parEnd",signalType:r.LINETYPE.PAR_END}),this.$=a[s-1];break;case 38:a[s-1].unshift({type:"criticalStart",criticalText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.CRITICAL_START}),a[s-1].push({type:"criticalEnd",signalType:r.LINETYPE.CRITICAL_END}),this.$=a[s-1];break;case 39:a[s-1].unshift({type:"breakStart",breakText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.BREAK_START}),a[s-1].push({type:"breakEnd",optText:r.parseMessage(a[s-2]),signalType:r.LINETYPE.BREAK_END}),this.$=a[s-1];break;case 42:this.$=a[s-3].concat([{type:"option",optionText:r.parseMessage(a[s-1]),signalType:r.LINETYPE.CRITICAL_OPTION},a[s]]);break;case 44:this.$=a[s-3].concat([{type:"and",parText:r.parseMessage(a[s-1]),signalType:r.LINETYPE.PAR_AND},a[s]]);break;case 46:this.$=a[s-3].concat([{type:"else",altText:r.parseMessage(a[s-1]),signalType:r.LINETYPE.ALT_ELSE},a[s]]);break;case 47:this.$=[a[s-1],{type:"addNote",placement:a[s-2],actor:a[s-1].actor,text:a[s]}];break;case 48:a[s-2]=[].concat(a[s-1],a[s-1]).slice(0,2),a[s-2][0]=a[s-2][0].actor,a[s-2][1]=a[s-2][1].actor,this.$=[a[s-1],{type:"addNote",placement:r.PLACEMENT.OVER,actor:a[s-2].slice(0,2),text:a[s]}];break;case 49:this.$=[a[s-1],{type:"addLinks",actor:a[s-1].actor,text:a[s]}];break;case 50:this.$=[a[s-1],{type:"addALink",actor:a[s-1].actor,text:a[s]}];break;case 51:this.$=[a[s-1],{type:"addProperties",actor:a[s-1].actor,text:a[s]}];break;case 52:this.$=[a[s-1],{type:"addDetails",actor:a[s-1].actor,text:a[s]}];break;case 55:this.$=[a[s-2],a[s]];break;case 57:this.$=r.PLACEMENT.LEFTOF;break;case 58:this.$=r.PLACEMENT.RIGHTOF;break;case 59:this.$=[a[s-4],a[s-1],{type:"addMessage",from:a[s-4].actor,to:a[s-1].actor,signalType:a[s-3],msg:a[s]},{type:"activeStart",signalType:r.LINETYPE.ACTIVE_START,actor:a[s-1]}];break;case 60:this.$=[a[s-4],a[s-1],{type:"addMessage",from:a[s-4].actor,to:a[s-1].actor,signalType:a[s-3],msg:a[s]},{type:"activeEnd",signalType:r.LINETYPE.ACTIVE_END,actor:a[s-4]}];break;case 61:this.$=[a[s-3],a[s-1],{type:"addMessage",from:a[s-3].actor,to:a[s-1].actor,signalType:a[s-2],msg:a[s]}];break;case 62:this.$={type:"addParticipant",actor:a[s]};break;case 63:this.$=r.LINETYPE.SOLID_OPEN;break;case 64:this.$=r.LINETYPE.DOTTED_OPEN;break;case 65:this.$=r.LINETYPE.SOLID;break;case 66:this.$=r.LINETYPE.DOTTED;break;case 67:this.$=r.LINETYPE.SOLID_CROSS;break;case 68:this.$=r.LINETYPE.DOTTED_CROSS;break;case 69:this.$=r.LINETYPE.SOLID_POINT;break;case 70:this.$=r.LINETYPE.DOTTED_POINT;break;case 71:this.$=r.parseMessage(a[s].trim().substring(1));break;case 72:r.parseDirective("%%{","open_directive");break;case 73:r.parseDirective(a[s],"type_directive");break;case 74:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 75:r.parseDirective("}%%","close_directive","sequence")}},table:[{3:1,4:e,5:n,6:4,7:r,11:6,79:i},{1:[3]},{3:8,4:e,5:n,6:4,7:r,11:6,79:i},{3:9,4:e,5:n,6:4,7:r,11:6,79:i},{3:10,4:e,5:n,6:4,7:r,11:6,79:i},t([1,4,5,16,20,22,25,26,32,33,34,36,38,39,41,42,43,45,47,49,53,58,59,60,61,69,79],a,{8:11}),{12:12,80:[1,13]},{80:[2,72]},{1:[2,1]},{1:[2,2]},{1:[2,3]},{1:[2,4],4:o,5:s,6:41,9:14,10:16,11:6,16:c,17:42,20:l,21:20,22:u,25:h,26:f,27:24,28:25,29:26,30:27,31:28,32:d,33:p,34:g,36:y,38:m,39:b,41:v,42:_,43:x,45:k,47:w,49:T,53:E,58:C,59:S,60:A,61:M,69:N,79:i},{13:49,14:[1,50],82:D},t([14,82],[2,73]),t(O,[2,6]),{6:41,10:52,11:6,16:c,17:42,20:l,21:20,22:u,25:h,26:f,27:24,28:25,29:26,30:27,31:28,32:d,33:p,34:g,36:y,38:m,39:b,41:v,42:_,43:x,45:k,47:w,49:T,53:E,58:C,59:S,60:A,61:M,69:N,79:i},t(O,[2,8]),t(O,[2,9]),{17:53,69:N},{17:54,69:N},{5:[1,55]},{5:[1,58],23:[1,56],24:[1,57]},{17:59,69:N},{17:60,69:N},{5:[1,61]},{5:[1,62]},{5:[1,63]},{5:[1,64]},{5:[1,65]},t(O,[2,28]),t(O,[2,29]),{35:[1,66]},{37:[1,67]},t(O,[2,32]),{19:[1,68]},{19:[1,69]},{19:[1,70]},{19:[1,71]},{19:[1,72]},{19:[1,73]},{19:[1,74]},t(O,[2,40]),{66:75,70:[1,76],71:[1,77],72:[1,78],73:[1,79],74:[1,80],75:[1,81],76:[1,82],77:[1,83]},{54:84,56:[1,85],64:[1,86],65:[1,87]},{17:88,69:N},{17:89,69:N},{17:90,69:N},{17:91,69:N},t([5,18,63,70,71,72,73,74,75,76,77,78],[2,62]),{5:[1,92]},{15:93,81:[1,94]},{5:[2,75]},t(O,[2,7]),{5:[1,96],18:[1,95]},{5:[1,98],18:[1,97]},t(O,[2,16]),{5:[1,100],23:[1,99]},{5:[1,101]},t(O,[2,20]),{5:[1,102]},{5:[1,103]},t(O,[2,23]),t(O,[2,24]),t(O,[2,25]),t(O,[2,26]),t(O,[2,27]),t(O,[2,30]),t(O,[2,31]),t(B,a,{8:104}),t(B,a,{8:105}),t(B,a,{8:106}),t(L,a,{44:107,8:108}),t(I,a,{46:109,8:110}),t(F,a,{48:111,8:112}),t(B,a,{8:113}),{17:116,67:[1,114],68:[1,115],69:N},t(R,[2,63]),t(R,[2,64]),t(R,[2,65]),t(R,[2,66]),t(R,[2,67]),t(R,[2,68]),t(R,[2,69]),t(R,[2,70]),{17:117,69:N},{17:119,57:118,69:N},{69:[2,57]},{69:[2,58]},{55:120,78:P},{55:122,78:P},{55:123,78:P},{55:124,78:P},t(j,[2,10]),{13:125,82:D},{82:[2,74]},{19:[1,126]},t(O,[2,13]),{19:[1,127]},t(O,[2,15]),{5:[1,128]},t(O,[2,18]),t(O,[2,19]),t(O,[2,21]),t(O,[2,22]),{4:o,5:s,6:41,9:14,10:16,11:6,16:c,17:42,20:l,21:20,22:u,25:h,26:f,27:24,28:25,29:26,30:27,31:28,32:d,33:p,34:g,36:y,38:m,39:b,40:[1,129],41:v,42:_,43:x,45:k,47:w,49:T,53:E,58:C,59:S,60:A,61:M,69:N,79:i},{4:o,5:s,6:41,9:14,10:16,11:6,16:c,17:42,20:l,21:20,22:u,25:h,26:f,27:24,28:25,29:26,30:27,31:28,32:d,33:p,34:g,36:y,38:m,39:b,40:[1,130],41:v,42:_,43:x,45:k,47:w,49:T,53:E,58:C,59:S,60:A,61:M,69:N,79:i},{4:o,5:s,6:41,9:14,10:16,11:6,16:c,17:42,20:l,21:20,22:u,25:h,26:f,27:24,28:25,29:26,30:27,31:28,32:d,33:p,34:g,36:y,38:m,39:b,40:[1,131],41:v,42:_,43:x,45:k,47:w,49:T,53:E,58:C,59:S,60:A,61:M,69:N,79:i},{40:[1,132]},{4:o,5:s,6:41,9:14,10:16,11:6,16:c,17:42,20:l,21:20,22:u,25:h,26:f,27:24,28:25,29:26,30:27,31:28,32:d,33:p,34:g,36:y,38:m,39:b,40:[2,45],41:v,42:_,43:x,45:k,47:w,49:T,52:[1,133],53:E,58:C,59:S,60:A,61:M,69:N,79:i},{40:[1,134]},{4:o,5:s,6:41,9:14,10:16,11:6,16:c,17:42,20:l,21:20,22:u,25:h,26:f,27:24,28:25,29:26,30:27,31:28,32:d,33:p,34:g,36:y,38:m,39:b,40:[2,43],41:v,42:_,43:x,45:k,47:w,49:T,51:[1,135],53:E,58:C,59:S,60:A,61:M,69:N,79:i},{40:[1,136]},{4:o,5:s,6:41,9:14,10:16,11:6,16:c,17:42,20:l,21:20,22:u,25:h,26:f,27:24,28:25,29:26,30:27,31:28,32:d,33:p,34:g,36:y,38:m,39:b,40:[2,41],41:v,42:_,43:x,45:k,47:w,49:T,50:[1,137],53:E,58:C,59:S,60:A,61:M,69:N,79:i},{4:o,5:s,6:41,9:14,10:16,11:6,16:c,17:42,20:l,21:20,22:u,25:h,26:f,27:24,28:25,29:26,30:27,31:28,32:d,33:p,34:g,36:y,38:m,39:b,40:[1,138],41:v,42:_,43:x,45:k,47:w,49:T,53:E,58:C,59:S,60:A,61:M,69:N,79:i},{17:139,69:N},{17:140,69:N},{55:141,78:P},{55:142,78:P},{55:143,78:P},{63:[1,144],78:[2,56]},{5:[2,49]},{5:[2,71]},{5:[2,50]},{5:[2,51]},{5:[2,52]},{5:[1,145]},{5:[1,146]},{5:[1,147]},t(O,[2,17]),t(O,[2,33]),t(O,[2,34]),t(O,[2,35]),t(O,[2,36]),{19:[1,148]},t(O,[2,37]),{19:[1,149]},t(O,[2,38]),{19:[1,150]},t(O,[2,39]),{55:151,78:P},{55:152,78:P},{5:[2,61]},{5:[2,47]},{5:[2,48]},{17:153,69:N},t(j,[2,11]),t(O,[2,12]),t(O,[2,14]),t(L,a,{8:108,44:154}),t(I,a,{8:110,46:155}),t(F,a,{8:112,48:156}),{5:[2,59]},{5:[2,60]},{78:[2,55]},{40:[2,46]},{40:[2,44]},{40:[2,42]}],defaultActions:{7:[2,72],8:[2,1],9:[2,2],10:[2,3],51:[2,75],86:[2,57],87:[2,58],94:[2,74],120:[2,49],121:[2,71],122:[2,50],123:[2,51],124:[2,52],141:[2,61],142:[2,47],143:[2,48],151:[2,59],152:[2,60],153:[2,55],154:[2,46],155:[2,44],156:[2,42]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,l=0,u=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var m=p.yylloc;a.push(m);var b=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,x,k,w,T,E,C,S,A,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==_&&(_=v()),w=o[k]&&o[k][_]),void 0===w||!w.length||!w[0]){var N="";for(E in A=[],o[k])this.terminals_[E]&&E>h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(c+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:m,expected:A})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+_);switch(w[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),_=null,x?(_=x,x=null):(l=p.yyleng,s=p.yytext,c=p.yylineno,m=p.yylloc,u>0&&u--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},b&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,l,c,g.yy,w[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},Y={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),79;case 1:return this.begin("type_directive"),80;case 2:return this.popState(),this.begin("arg_directive"),14;case 3:return this.popState(),this.popState(),82;case 4:return 81;case 5:case 52:case 65:return 5;case 6:case 7:case 8:case 9:case 10:break;case 11:return 23;case 12:return this.begin("ID"),16;case 13:return this.begin("ID"),20;case 14:return e.yytext=e.yytext.trim(),this.begin("ALIAS"),69;case 15:return this.popState(),this.popState(),this.begin("LINE"),18;case 16:return this.popState(),this.popState(),5;case 17:return this.begin("LINE"),39;case 18:return this.begin("LINE"),41;case 19:return this.begin("LINE"),42;case 20:return this.begin("LINE"),43;case 21:return this.begin("LINE"),52;case 22:return this.begin("LINE"),45;case 23:return this.begin("LINE"),51;case 24:return this.begin("LINE"),47;case 25:return this.begin("LINE"),50;case 26:return this.begin("LINE"),49;case 27:return this.popState(),19;case 28:return 40;case 29:return 64;case 30:return 65;case 31:return 58;case 32:return 59;case 33:return 60;case 34:return 61;case 35:return 56;case 36:return 53;case 37:return this.begin("ID"),25;case 38:return this.begin("ID"),26;case 39:return 32;case 40:return 33;case 41:return this.begin("acc_title"),34;case 42:return this.popState(),"acc_title_value";case 43:return this.begin("acc_descr"),36;case 44:return this.popState(),"acc_descr_value";case 45:this.begin("acc_descr_multiline");break;case 46:this.popState();break;case 47:return"acc_descr_multiline_value";case 48:return 7;case 49:return 22;case 50:return 24;case 51:return 63;case 53:return e.yytext=e.yytext.trim(),69;case 54:return 72;case 55:return 73;case 56:return 70;case 57:return 71;case 58:return 74;case 59:return 75;case 60:return 76;case 61:return 77;case 62:return 78;case 63:return 67;case 64:return 68;case 66:return"INVALID"}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:((?!\n)\s)+)/i,/^(?:#[^\n]*)/i,/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[0-9]+(?=[ \n]+))/i,/^(?:participant\b)/i,/^(?:actor\b)/i,/^(?:[^\->:\n,;]+?(?=((?!\n)\s)+as(?!\n)\s|[#\n;]|$))/i,/^(?:as\b)/i,/^(?:(?:))/i,/^(?:loop\b)/i,/^(?:rect\b)/i,/^(?:opt\b)/i,/^(?:alt\b)/i,/^(?:else\b)/i,/^(?:par\b)/i,/^(?:and\b)/i,/^(?:critical\b)/i,/^(?:option\b)/i,/^(?:break\b)/i,/^(?:(?:[:]?(?:no)?wrap)?[^#\n;]*)/i,/^(?:end\b)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:links\b)/i,/^(?:link\b)/i,/^(?:properties\b)/i,/^(?:details\b)/i,/^(?:over\b)/i,/^(?:note\b)/i,/^(?:activate\b)/i,/^(?:deactivate\b)/i,/^(?:title\s[^#\n;]+)/i,/^(?:title:\s[^#\n;]+)/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:sequenceDiagram\b)/i,/^(?:autonumber\b)/i,/^(?:off\b)/i,/^(?:,)/i,/^(?:;)/i,/^(?:[^\+\->:\n,;]+((?!(-x|--x|-\)|--\)))[\-]*[^\+\->:\n,;]+)*)/i,/^(?:->>)/i,/^(?:-->>)/i,/^(?:->)/i,/^(?:-->)/i,/^(?:-[x])/i,/^(?:--[x])/i,/^(?:-[\)])/i,/^(?:--[\)])/i,/^(?::(?:(?:no)?wrap)?[^#\n;]+)/i,/^(?:\+)/i,/^(?:-)/i,/^(?:$)/i,/^(?:.)/i],conditions:{acc_descr_multiline:{rules:[46,47],inclusive:!1},acc_descr:{rules:[44],inclusive:!1},acc_title:{rules:[42],inclusive:!1},open_directive:{rules:[1,8],inclusive:!1},type_directive:{rules:[2,3,8],inclusive:!1},arg_directive:{rules:[3,4,8],inclusive:!1},ID:{rules:[7,8,14],inclusive:!1},ALIAS:{rules:[7,8,15,16],inclusive:!1},LINE:{rules:[7,8,27],inclusive:!1},INITIAL:{rules:[0,5,6,8,9,10,11,12,13,17,18,19,20,21,22,23,24,25,26,28,29,30,31,32,33,34,35,36,37,38,39,40,41,43,45,48,49,50,51,52,53,54,55,56,57,58,59,60,61,62,63,64,65,66],inclusive:!0}}};function U(){this.yy={}}return z.lexer=Y,U.prototype=z,z.Parser=U,new U}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(1993).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},3584:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,3],r=[1,5],i=[1,7],a=[2,5],o=[1,15],s=[1,17],c=[1,19],l=[1,20],u=[1,21],h=[1,22],f=[1,33],d=[1,23],p=[1,24],g=[1,25],y=[1,26],m=[1,27],b=[1,30],v=[1,31],_=[1,32],x=[1,35],k=[1,36],w=[1,37],T=[1,38],E=[1,34],C=[1,41],S=[1,4,5,14,15,17,19,20,22,23,24,25,26,27,31,33,35,41,42,43,44,47,50],A=[1,4,5,12,13,14,15,17,19,20,22,23,24,25,26,27,31,33,35,41,42,43,44,47,50],M=[1,4,5,7,14,15,17,19,20,22,23,24,25,26,27,31,33,35,41,42,43,44,47,50],N=[4,5,14,15,17,19,20,22,23,24,25,26,27,31,33,35,41,42,43,44,47,50],D={trace:function(){},yy:{},symbols_:{error:2,start:3,SPACE:4,NL:5,directive:6,SD:7,document:8,line:9,statement:10,idStatement:11,DESCR:12,"--\x3e":13,HIDE_EMPTY:14,scale:15,WIDTH:16,COMPOSIT_STATE:17,STRUCT_START:18,STRUCT_STOP:19,STATE_DESCR:20,AS:21,ID:22,FORK:23,JOIN:24,CHOICE:25,CONCURRENT:26,note:27,notePosition:28,NOTE_TEXT:29,direction:30,acc_title:31,acc_title_value:32,acc_descr:33,acc_descr_value:34,acc_descr_multiline_value:35,openDirective:36,typeDirective:37,closeDirective:38,":":39,argDirective:40,direction_tb:41,direction_bt:42,direction_rl:43,direction_lr:44,eol:45,";":46,EDGE_STATE:47,left_of:48,right_of:49,open_directive:50,type_directive:51,arg_directive:52,close_directive:53,$accept:0,$end:1},terminals_:{2:"error",4:"SPACE",5:"NL",7:"SD",12:"DESCR",13:"--\x3e",14:"HIDE_EMPTY",15:"scale",16:"WIDTH",17:"COMPOSIT_STATE",18:"STRUCT_START",19:"STRUCT_STOP",20:"STATE_DESCR",21:"AS",22:"ID",23:"FORK",24:"JOIN",25:"CHOICE",26:"CONCURRENT",27:"note",29:"NOTE_TEXT",31:"acc_title",32:"acc_title_value",33:"acc_descr",34:"acc_descr_value",35:"acc_descr_multiline_value",39:":",41:"direction_tb",42:"direction_bt",43:"direction_rl",44:"direction_lr",46:";",47:"EDGE_STATE",48:"left_of",49:"right_of",50:"open_directive",51:"type_directive",52:"arg_directive",53:"close_directive"},productions_:[0,[3,2],[3,2],[3,2],[3,2],[8,0],[8,2],[9,2],[9,1],[9,1],[10,1],[10,2],[10,3],[10,4],[10,1],[10,2],[10,1],[10,4],[10,3],[10,6],[10,1],[10,1],[10,1],[10,1],[10,4],[10,4],[10,1],[10,1],[10,2],[10,2],[10,1],[6,3],[6,5],[30,1],[30,1],[30,1],[30,1],[45,1],[45,1],[11,1],[11,1],[28,1],[28,1],[36,1],[37,1],[40,1],[38,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 4:return r.setRootDoc(a[s]),a[s];case 5:this.$=[];break;case 6:"nl"!=a[s]&&(a[s-1].push(a[s]),this.$=a[s-1]);break;case 7:case 8:case 39:case 40:this.$=a[s];break;case 9:this.$="nl";break;case 10:this.$={stmt:"state",id:a[s],type:"default",description:""};break;case 11:this.$={stmt:"state",id:a[s-1],type:"default",description:r.trimColon(a[s])};break;case 12:this.$={stmt:"relation",state1:{stmt:"state",id:a[s-2],type:"default",description:""},state2:{stmt:"state",id:a[s],type:"default",description:""}};break;case 13:this.$={stmt:"relation",state1:{stmt:"state",id:a[s-3],type:"default",description:""},state2:{stmt:"state",id:a[s-1],type:"default",description:""},description:a[s].substr(1).trim()};break;case 17:this.$={stmt:"state",id:a[s-3],type:"default",description:"",doc:a[s-1]};break;case 18:var c=a[s],l=a[s-2].trim();if(a[s].match(":")){var u=a[s].split(":");c=u[0],l=[l,u[1]]}this.$={stmt:"state",id:c,type:"default",description:l};break;case 19:this.$={stmt:"state",id:a[s-3],type:"default",description:a[s-5],doc:a[s-1]};break;case 20:this.$={stmt:"state",id:a[s],type:"fork"};break;case 21:this.$={stmt:"state",id:a[s],type:"join"};break;case 22:this.$={stmt:"state",id:a[s],type:"choice"};break;case 23:this.$={stmt:"state",id:r.getDividerId(),type:"divider"};break;case 24:this.$={stmt:"state",id:a[s-1].trim(),note:{position:a[s-2].trim(),text:a[s].trim()}};break;case 28:this.$=a[s].trim(),r.setAccTitle(this.$);break;case 29:case 30:this.$=a[s].trim(),r.setAccDescription(this.$);break;case 33:r.setDirection("TB"),this.$={stmt:"dir",value:"TB"};break;case 34:r.setDirection("BT"),this.$={stmt:"dir",value:"BT"};break;case 35:r.setDirection("RL"),this.$={stmt:"dir",value:"RL"};break;case 36:r.setDirection("LR"),this.$={stmt:"dir",value:"LR"};break;case 43:r.parseDirective("%%{","open_directive");break;case 44:r.parseDirective(a[s],"type_directive");break;case 45:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 46:r.parseDirective("}%%","close_directive","state")}},table:[{3:1,4:e,5:n,6:4,7:r,36:6,50:i},{1:[3]},{3:8,4:e,5:n,6:4,7:r,36:6,50:i},{3:9,4:e,5:n,6:4,7:r,36:6,50:i},{3:10,4:e,5:n,6:4,7:r,36:6,50:i},t([1,4,5,14,15,17,20,22,23,24,25,26,27,31,33,35,41,42,43,44,47,50],a,{8:11}),{37:12,51:[1,13]},{51:[2,43]},{1:[2,1]},{1:[2,2]},{1:[2,3]},{1:[2,4],4:o,5:s,6:28,9:14,10:16,11:18,14:c,15:l,17:u,20:h,22:f,23:d,24:p,25:g,26:y,27:m,30:29,31:b,33:v,35:_,36:6,41:x,42:k,43:w,44:T,47:E,50:i},{38:39,39:[1,40],53:C},t([39,53],[2,44]),t(S,[2,6]),{6:28,10:42,11:18,14:c,15:l,17:u,20:h,22:f,23:d,24:p,25:g,26:y,27:m,30:29,31:b,33:v,35:_,36:6,41:x,42:k,43:w,44:T,47:E,50:i},t(S,[2,8]),t(S,[2,9]),t(S,[2,10],{12:[1,43],13:[1,44]}),t(S,[2,14]),{16:[1,45]},t(S,[2,16],{18:[1,46]}),{21:[1,47]},t(S,[2,20]),t(S,[2,21]),t(S,[2,22]),t(S,[2,23]),{28:48,29:[1,49],48:[1,50],49:[1,51]},t(S,[2,26]),t(S,[2,27]),{32:[1,52]},{34:[1,53]},t(S,[2,30]),t(A,[2,39]),t(A,[2,40]),t(S,[2,33]),t(S,[2,34]),t(S,[2,35]),t(S,[2,36]),t(M,[2,31]),{40:54,52:[1,55]},t(M,[2,46]),t(S,[2,7]),t(S,[2,11]),{11:56,22:f,47:E},t(S,[2,15]),t(N,a,{8:57}),{22:[1,58]},{22:[1,59]},{21:[1,60]},{22:[2,41]},{22:[2,42]},t(S,[2,28]),t(S,[2,29]),{38:61,53:C},{53:[2,45]},t(S,[2,12],{12:[1,62]}),{4:o,5:s,6:28,9:14,10:16,11:18,14:c,15:l,17:u,19:[1,63],20:h,22:f,23:d,24:p,25:g,26:y,27:m,30:29,31:b,33:v,35:_,36:6,41:x,42:k,43:w,44:T,47:E,50:i},t(S,[2,18],{18:[1,64]}),{29:[1,65]},{22:[1,66]},t(M,[2,32]),t(S,[2,13]),t(S,[2,17]),t(N,a,{8:67}),t(S,[2,24]),t(S,[2,25]),{4:o,5:s,6:28,9:14,10:16,11:18,14:c,15:l,17:u,19:[1,68],20:h,22:f,23:d,24:p,25:g,26:y,27:m,30:29,31:b,33:v,35:_,36:6,41:x,42:k,43:w,44:T,47:E,50:i},t(S,[2,19])],defaultActions:{7:[2,43],8:[2,1],9:[2,2],10:[2,3],50:[2,41],51:[2,42],55:[2,45]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,l=0,u=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var m=p.yylloc;a.push(m);var b=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,x,k,w,T,E,C,S,A,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==_&&(_=v()),w=o[k]&&o[k][_]),void 0===w||!w.length||!w[0]){var N="";for(E in A=[],o[k])this.terminals_[E]&&E>h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(c+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:m,expected:A})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+_);switch(w[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),_=null,x?(_=x,x=null):(l=p.yyleng,s=p.yytext,c=p.yylineno,m=p.yylloc,u>0&&u--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},b&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,l,c,g.yy,w[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},O={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:case 33:return 41;case 1:case 34:return 42;case 2:case 35:return 43;case 3:case 36:return 44;case 4:return this.begin("open_directive"),50;case 5:return this.begin("type_directive"),51;case 6:return this.popState(),this.begin("arg_directive"),39;case 7:return this.popState(),this.popState(),53;case 8:return 52;case 9:case 10:case 12:case 13:case 14:case 15:case 46:case 52:break;case 11:case 66:return 5;case 16:return this.pushState("SCALE"),15;case 17:return 16;case 18:case 24:case 40:case 43:this.popState();break;case 19:return this.begin("acc_title"),31;case 20:return this.popState(),"acc_title_value";case 21:return this.begin("acc_descr"),33;case 22:return this.popState(),"acc_descr_value";case 23:this.begin("acc_descr_multiline");break;case 25:return"acc_descr_multiline_value";case 26:this.pushState("STATE");break;case 27:case 30:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),23;case 28:case 31:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),24;case 29:case 32:return this.popState(),e.yytext=e.yytext.slice(0,-10).trim(),25;case 37:this.begin("STATE_STRING");break;case 38:return this.popState(),this.pushState("STATE_ID"),"AS";case 39:case 54:return this.popState(),"ID";case 41:return"STATE_DESCR";case 42:return 17;case 44:return this.popState(),this.pushState("struct"),18;case 45:return this.popState(),19;case 47:return this.begin("NOTE"),27;case 48:return this.popState(),this.pushState("NOTE_ID"),48;case 49:return this.popState(),this.pushState("NOTE_ID"),49;case 50:this.popState(),this.pushState("FLOATING_NOTE");break;case 51:return this.popState(),this.pushState("FLOATING_NOTE_ID"),"AS";case 53:return"NOTE_TEXT";case 55:return this.popState(),this.pushState("NOTE_TEXT"),22;case 56:return this.popState(),e.yytext=e.yytext.substr(2).trim(),29;case 57:return this.popState(),e.yytext=e.yytext.slice(0,-8).trim(),29;case 58:case 59:return 7;case 60:return 14;case 61:return 47;case 62:return 22;case 63:return e.yytext=e.yytext.trim(),12;case 64:return 13;case 65:return 26;case 67:return"INVALID"}},rules:[/^(?:.*direction\s+TB[^\n]*)/i,/^(?:.*direction\s+BT[^\n]*)/i,/^(?:.*direction\s+RL[^\n]*)/i,/^(?:.*direction\s+LR[^\n]*)/i,/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:[\s]+)/i,/^(?:((?!\n)\s)+)/i,/^(?:#[^\n]*)/i,/^(?:%[^\n]*)/i,/^(?:scale\s+)/i,/^(?:\d+)/i,/^(?:\s+width\b)/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:state\s+)/i,/^(?:.*<>)/i,/^(?:.*<>)/i,/^(?:.*<>)/i,/^(?:.*\[\[fork\]\])/i,/^(?:.*\[\[join\]\])/i,/^(?:.*\[\[choice\]\])/i,/^(?:.*direction\s+TB[^\n]*)/i,/^(?:.*direction\s+BT[^\n]*)/i,/^(?:.*direction\s+RL[^\n]*)/i,/^(?:.*direction\s+LR[^\n]*)/i,/^(?:["])/i,/^(?:\s*as\s+)/i,/^(?:[^\n\{]*)/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[^\n\s\{]+)/i,/^(?:\n)/i,/^(?:\{)/i,/^(?:\})/i,/^(?:[\n])/i,/^(?:note\s+)/i,/^(?:left of\b)/i,/^(?:right of\b)/i,/^(?:")/i,/^(?:\s*as\s*)/i,/^(?:["])/i,/^(?:[^"]*)/i,/^(?:[^\n]*)/i,/^(?:\s*[^:\n\s\-]+)/i,/^(?:\s*:[^:\n;]+)/i,/^(?:[\s\S]*?end note\b)/i,/^(?:stateDiagram\s+)/i,/^(?:stateDiagram-v2\s+)/i,/^(?:hide empty description\b)/i,/^(?:\[\*\])/i,/^(?:[^:\n\s\-\{]+)/i,/^(?:\s*:[^:\n;]+)/i,/^(?:-->)/i,/^(?:--)/i,/^(?:$)/i,/^(?:.)/i],conditions:{LINE:{rules:[13,14],inclusive:!1},close_directive:{rules:[13,14],inclusive:!1},arg_directive:{rules:[7,8,13,14],inclusive:!1},type_directive:{rules:[6,7,13,14],inclusive:!1},open_directive:{rules:[5,13,14],inclusive:!1},struct:{rules:[13,14,26,33,34,35,36,45,46,47,61,62,63,64,65],inclusive:!1},FLOATING_NOTE_ID:{rules:[54],inclusive:!1},FLOATING_NOTE:{rules:[51,52,53],inclusive:!1},NOTE_TEXT:{rules:[56,57],inclusive:!1},NOTE_ID:{rules:[55],inclusive:!1},NOTE:{rules:[48,49,50],inclusive:!1},acc_descr_multiline:{rules:[24,25],inclusive:!1},acc_descr:{rules:[22],inclusive:!1},acc_title:{rules:[20],inclusive:!1},SCALE:{rules:[17,18],inclusive:!1},ALIAS:{rules:[],inclusive:!1},STATE_ID:{rules:[39],inclusive:!1},STATE_STRING:{rules:[40,41],inclusive:!1},FORK_STATE:{rules:[],inclusive:!1},STATE:{rules:[13,14,27,28,29,30,31,32,37,38,42,43,44],inclusive:!1},ID:{rules:[13,14],inclusive:!1},INITIAL:{rules:[0,1,2,3,4,9,10,11,12,14,15,16,19,21,23,26,44,47,58,59,60,61,62,63,64,66,67],inclusive:!0}}};function B(){this.yy={}}return D.lexer=O,B.prototype=D,D.Parser=B,new B}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(3069).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},9763:(t,e,n)=>{t=n.nmd(t);var r=function(){var t=function(t,e,n,r){for(n=n||{},r=t.length;r--;n[t[r]]=e);return n},e=[1,2],n=[1,5],r=[6,9,11,17,18,20,22,23,24,26],i=[1,15],a=[1,16],o=[1,17],s=[1,18],c=[1,19],l=[1,20],u=[1,24],h=[4,6,9,11,17,18,20,22,23,24,26],f={trace:function(){},yy:{},symbols_:{error:2,start:3,journey:4,document:5,EOF:6,directive:7,line:8,SPACE:9,statement:10,NEWLINE:11,openDirective:12,typeDirective:13,closeDirective:14,":":15,argDirective:16,title:17,acc_title:18,acc_title_value:19,acc_descr:20,acc_descr_value:21,acc_descr_multiline_value:22,section:23,taskName:24,taskData:25,open_directive:26,type_directive:27,arg_directive:28,close_directive:29,$accept:0,$end:1},terminals_:{2:"error",4:"journey",6:"EOF",9:"SPACE",11:"NEWLINE",15:":",17:"title",18:"acc_title",19:"acc_title_value",20:"acc_descr",21:"acc_descr_value",22:"acc_descr_multiline_value",23:"section",24:"taskName",25:"taskData",26:"open_directive",27:"type_directive",28:"arg_directive",29:"close_directive"},productions_:[0,[3,3],[3,2],[5,0],[5,2],[8,2],[8,1],[8,1],[8,1],[7,4],[7,6],[10,1],[10,2],[10,2],[10,1],[10,1],[10,2],[10,1],[12,1],[13,1],[16,1],[14,1]],performAction:function(t,e,n,r,i,a,o){var s=a.length-1;switch(i){case 1:return a[s-1];case 3:case 7:case 8:this.$=[];break;case 4:a[s-1].push(a[s]),this.$=a[s-1];break;case 5:case 6:this.$=a[s];break;case 11:r.setDiagramTitle(a[s].substr(6)),this.$=a[s].substr(6);break;case 12:this.$=a[s].trim(),r.setAccTitle(this.$);break;case 13:case 14:this.$=a[s].trim(),r.setAccDescription(this.$);break;case 15:r.addSection(a[s].substr(8)),this.$=a[s].substr(8);break;case 16:r.addTask(a[s-1],a[s]),this.$="task";break;case 18:r.parseDirective("%%{","open_directive");break;case 19:r.parseDirective(a[s],"type_directive");break;case 20:a[s]=a[s].trim().replace(/'/g,'"'),r.parseDirective(a[s],"arg_directive");break;case 21:r.parseDirective("}%%","close_directive","journey")}},table:[{3:1,4:e,7:3,12:4,26:n},{1:[3]},t(r,[2,3],{5:6}),{3:7,4:e,7:3,12:4,26:n},{13:8,27:[1,9]},{27:[2,18]},{6:[1,10],7:21,8:11,9:[1,12],10:13,11:[1,14],12:4,17:i,18:a,20:o,22:s,23:c,24:l,26:n},{1:[2,2]},{14:22,15:[1,23],29:u},t([15,29],[2,19]),t(r,[2,8],{1:[2,1]}),t(r,[2,4]),{7:21,10:25,12:4,17:i,18:a,20:o,22:s,23:c,24:l,26:n},t(r,[2,6]),t(r,[2,7]),t(r,[2,11]),{19:[1,26]},{21:[1,27]},t(r,[2,14]),t(r,[2,15]),{25:[1,28]},t(r,[2,17]),{11:[1,29]},{16:30,28:[1,31]},{11:[2,21]},t(r,[2,5]),t(r,[2,12]),t(r,[2,13]),t(r,[2,16]),t(h,[2,9]),{14:32,29:u},{29:[2,20]},{11:[1,33]},t(h,[2,10])],defaultActions:{5:[2,18],7:[2,2],24:[2,21],31:[2,20]},parseError:function(t,e){if(!e.recoverable){var n=new Error(t);throw n.hash=e,n}this.trace(t)},parse:function(t){var e=this,n=[0],r=[],i=[null],a=[],o=this.table,s="",c=0,l=0,u=0,h=2,f=1,d=a.slice.call(arguments,1),p=Object.create(this.lexer),g={yy:{}};for(var y in this.yy)Object.prototype.hasOwnProperty.call(this.yy,y)&&(g.yy[y]=this.yy[y]);p.setInput(t,g.yy),g.yy.lexer=p,g.yy.parser=this,void 0===p.yylloc&&(p.yylloc={});var m=p.yylloc;a.push(m);var b=p.options&&p.options.ranges;function v(){var t;return"number"!=typeof(t=r.pop()||p.lex()||f)&&(t instanceof Array&&(t=(r=t).pop()),t=e.symbols_[t]||t),t}"function"==typeof g.yy.parseError?this.parseError=g.yy.parseError:this.parseError=Object.getPrototypeOf(this).parseError;for(var _,x,k,w,T,E,C,S,A,M={};;){if(k=n[n.length-1],this.defaultActions[k]?w=this.defaultActions[k]:(null==_&&(_=v()),w=o[k]&&o[k][_]),void 0===w||!w.length||!w[0]){var N="";for(E in A=[],o[k])this.terminals_[E]&&E>h&&A.push("'"+this.terminals_[E]+"'");N=p.showPosition?"Parse error on line "+(c+1)+":\n"+p.showPosition()+"\nExpecting "+A.join(", ")+", got '"+(this.terminals_[_]||_)+"'":"Parse error on line "+(c+1)+": Unexpected "+(_==f?"end of input":"'"+(this.terminals_[_]||_)+"'"),this.parseError(N,{text:p.match,token:this.terminals_[_]||_,line:p.yylineno,loc:m,expected:A})}if(w[0]instanceof Array&&w.length>1)throw new Error("Parse Error: multiple actions possible at state: "+k+", token: "+_);switch(w[0]){case 1:n.push(_),i.push(p.yytext),a.push(p.yylloc),n.push(w[1]),_=null,x?(_=x,x=null):(l=p.yyleng,s=p.yytext,c=p.yylineno,m=p.yylloc,u>0&&u--);break;case 2:if(C=this.productions_[w[1]][1],M.$=i[i.length-C],M._$={first_line:a[a.length-(C||1)].first_line,last_line:a[a.length-1].last_line,first_column:a[a.length-(C||1)].first_column,last_column:a[a.length-1].last_column},b&&(M._$.range=[a[a.length-(C||1)].range[0],a[a.length-1].range[1]]),void 0!==(T=this.performAction.apply(M,[s,l,c,g.yy,w[1],i,a].concat(d))))return T;C&&(n=n.slice(0,-1*C*2),i=i.slice(0,-1*C),a=a.slice(0,-1*C)),n.push(this.productions_[w[1]][0]),i.push(M.$),a.push(M._$),S=o[n[n.length-2]][n[n.length-1]],n.push(S);break;case 3:return!0}}return!0}},d={EOF:1,parseError:function(t,e){if(!this.yy.parser)throw new Error(t);this.yy.parser.parseError(t,e)},setInput:function(t,e){return this.yy=e||this.yy||{},this._input=t,this._more=this._backtrack=this.done=!1,this.yylineno=this.yyleng=0,this.yytext=this.matched=this.match="",this.conditionStack=["INITIAL"],this.yylloc={first_line:1,first_column:0,last_line:1,last_column:0},this.options.ranges&&(this.yylloc.range=[0,0]),this.offset=0,this},input:function(){var t=this._input[0];return this.yytext+=t,this.yyleng++,this.offset++,this.match+=t,this.matched+=t,t.match(/(?:\r\n?|\n).*/g)?(this.yylineno++,this.yylloc.last_line++):this.yylloc.last_column++,this.options.ranges&&this.yylloc.range[1]++,this._input=this._input.slice(1),t},unput:function(t){var e=t.length,n=t.split(/(?:\r\n?|\n)/g);this._input=t+this._input,this.yytext=this.yytext.substr(0,this.yytext.length-e),this.offset-=e;var r=this.match.split(/(?:\r\n?|\n)/g);this.match=this.match.substr(0,this.match.length-1),this.matched=this.matched.substr(0,this.matched.length-1),n.length-1&&(this.yylineno-=n.length-1);var i=this.yylloc.range;return this.yylloc={first_line:this.yylloc.first_line,last_line:this.yylineno+1,first_column:this.yylloc.first_column,last_column:n?(n.length===r.length?this.yylloc.first_column:0)+r[r.length-n.length].length-n[0].length:this.yylloc.first_column-e},this.options.ranges&&(this.yylloc.range=[i[0],i[0]+this.yyleng-e]),this.yyleng=this.yytext.length,this},more:function(){return this._more=!0,this},reject:function(){return this.options.backtrack_lexer?(this._backtrack=!0,this):this.parseError("Lexical error on line "+(this.yylineno+1)+". You can only invoke reject() in the lexer when the lexer is of the backtracking persuasion (options.backtrack_lexer = true).\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},less:function(t){this.unput(this.match.slice(t))},pastInput:function(){var t=this.matched.substr(0,this.matched.length-this.match.length);return(t.length>20?"...":"")+t.substr(-20).replace(/\n/g,"")},upcomingInput:function(){var t=this.match;return t.length<20&&(t+=this._input.substr(0,20-t.length)),(t.substr(0,20)+(t.length>20?"...":"")).replace(/\n/g,"")},showPosition:function(){var t=this.pastInput(),e=new Array(t.length+1).join("-");return t+this.upcomingInput()+"\n"+e+"^"},test_match:function(t,e){var n,r,i;if(this.options.backtrack_lexer&&(i={yylineno:this.yylineno,yylloc:{first_line:this.yylloc.first_line,last_line:this.last_line,first_column:this.yylloc.first_column,last_column:this.yylloc.last_column},yytext:this.yytext,match:this.match,matches:this.matches,matched:this.matched,yyleng:this.yyleng,offset:this.offset,_more:this._more,_input:this._input,yy:this.yy,conditionStack:this.conditionStack.slice(0),done:this.done},this.options.ranges&&(i.yylloc.range=this.yylloc.range.slice(0))),(r=t[0].match(/(?:\r\n?|\n).*/g))&&(this.yylineno+=r.length),this.yylloc={first_line:this.yylloc.last_line,last_line:this.yylineno+1,first_column:this.yylloc.last_column,last_column:r?r[r.length-1].length-r[r.length-1].match(/\r?\n?/)[0].length:this.yylloc.last_column+t[0].length},this.yytext+=t[0],this.match+=t[0],this.matches=t,this.yyleng=this.yytext.length,this.options.ranges&&(this.yylloc.range=[this.offset,this.offset+=this.yyleng]),this._more=!1,this._backtrack=!1,this._input=this._input.slice(t[0].length),this.matched+=t[0],n=this.performAction.call(this,this.yy,this,e,this.conditionStack[this.conditionStack.length-1]),this.done&&this._input&&(this.done=!1),n)return n;if(this._backtrack){for(var a in i)this[a]=i[a];return!1}return!1},next:function(){if(this.done)return this.EOF;var t,e,n,r;this._input||(this.done=!0),this._more||(this.yytext="",this.match="");for(var i=this._currentRules(),a=0;ae[0].length)){if(e=n,r=a,this.options.backtrack_lexer){if(!1!==(t=this.test_match(n,i[a])))return t;if(this._backtrack){e=!1;continue}return!1}if(!this.options.flex)break}return e?!1!==(t=this.test_match(e,i[r]))&&t:""===this._input?this.EOF:this.parseError("Lexical error on line "+(this.yylineno+1)+". Unrecognized text.\n"+this.showPosition(),{text:"",token:null,line:this.yylineno})},lex:function(){return this.next()||this.lex()},begin:function(t){this.conditionStack.push(t)},popState:function(){return this.conditionStack.length-1>0?this.conditionStack.pop():this.conditionStack[0]},_currentRules:function(){return this.conditionStack.length&&this.conditionStack[this.conditionStack.length-1]?this.conditions[this.conditionStack[this.conditionStack.length-1]].rules:this.conditions.INITIAL.rules},topState:function(t){return(t=this.conditionStack.length-1-Math.abs(t||0))>=0?this.conditionStack[t]:"INITIAL"},pushState:function(t){this.begin(t)},stateStackSize:function(){return this.conditionStack.length},options:{"case-insensitive":!0},performAction:function(t,e,n,r){switch(n){case 0:return this.begin("open_directive"),26;case 1:return this.begin("type_directive"),27;case 2:return this.popState(),this.begin("arg_directive"),15;case 3:return this.popState(),this.popState(),29;case 4:return 28;case 5:case 6:case 8:case 9:break;case 7:return 11;case 10:return 4;case 11:return 17;case 12:return this.begin("acc_title"),18;case 13:return this.popState(),"acc_title_value";case 14:return this.begin("acc_descr"),20;case 15:return this.popState(),"acc_descr_value";case 16:this.begin("acc_descr_multiline");break;case 17:this.popState();break;case 18:return"acc_descr_multiline_value";case 19:return 23;case 20:return 24;case 21:return 25;case 22:return 15;case 23:return 6;case 24:return"INVALID"}},rules:[/^(?:%%\{)/i,/^(?:((?:(?!\}%%)[^:.])*))/i,/^(?::)/i,/^(?:\}%%)/i,/^(?:((?:(?!\}%%).|\n)*))/i,/^(?:%(?!\{)[^\n]*)/i,/^(?:[^\}]%%[^\n]*)/i,/^(?:[\n]+)/i,/^(?:\s+)/i,/^(?:#[^\n]*)/i,/^(?:journey\b)/i,/^(?:title\s[^#\n;]+)/i,/^(?:accTitle\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*:\s*)/i,/^(?:(?!\n||)*[^\n]*)/i,/^(?:accDescr\s*\{\s*)/i,/^(?:[\}])/i,/^(?:[^\}]*)/i,/^(?:section\s[^#:\n;]+)/i,/^(?:[^#:\n;]+)/i,/^(?::[^#\n;]+)/i,/^(?::)/i,/^(?:$)/i,/^(?:.)/i],conditions:{open_directive:{rules:[1],inclusive:!1},type_directive:{rules:[2,3],inclusive:!1},arg_directive:{rules:[3,4],inclusive:!1},acc_descr_multiline:{rules:[17,18],inclusive:!1},acc_descr:{rules:[15],inclusive:!1},acc_title:{rules:[13],inclusive:!1},INITIAL:{rules:[0,5,6,7,8,9,10,11,12,14,16,19,20,21,22,23,24],inclusive:!0}}};function p(){this.yy={}}return f.lexer=d,p.prototype=f,f.Parser=p,new p}();e.parser=r,e.Parser=r.Parser,e.parse=function(){return r.parse.apply(r,arguments)},e.main=function(t){t[1]||(console.log("Usage: "+t[0]+" FILE"),process.exit(1));var r=n(9143).readFileSync(n(6470).normalize(t[1]),"utf8");return e.parser.parse(r)},n.c[n.s]===t&&e.main(process.argv.slice(1))},7967:(t,e)=>{"use strict";e.N=void 0;var n=/^([^\w]*)(javascript|data|vbscript)/im,r=/&#(\w+)(^\w|;)?/g,i=/[\u0000-\u001F\u007F-\u009F\u2000-\u200D\uFEFF]/gim,a=/^([^:]+):/gm,o=[".","/"];e.N=function(t){var e,s=(e=t||"",e.replace(r,(function(t,e){return String.fromCharCode(e)}))).replace(i,"").trim();if(!s)return"about:blank";if(function(t){return o.indexOf(t[0])>-1}(s))return s;var c=s.match(a);if(!c)return s;var l=c[0];return n.test(l)?"about:blank":s}},3841:t=>{t.exports=function(t,e){return t.intersect(e)}},6359:(t,e,n)=>{"use strict";n.d(e,{default:()=>hu});var r=n(1941),i=n.n(r),a={debug:1,info:2,warn:3,error:4,fatal:5},o={debug:function(){},info:function(){},warn:function(){},error:function(){},fatal:function(){}},s=function(){var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:"fatal";isNaN(t)&&(t=t.toLowerCase(),void 0!==a[t]&&(t=a[t])),o.trace=function(){},o.debug=function(){},o.info=function(){},o.warn=function(){},o.error=function(){},o.fatal=function(){},t<=a.fatal&&(o.fatal=console.error?console.error.bind(console,c("FATAL"),"color: orange"):console.log.bind(console,"",c("FATAL"))),t<=a.error&&(o.error=console.error?console.error.bind(console,c("ERROR"),"color: orange"):console.log.bind(console,"",c("ERROR"))),t<=a.warn&&(o.warn=console.warn?console.warn.bind(console,c("WARN"),"color: orange"):console.log.bind(console,"",c("WARN"))),t<=a.info&&(o.info=console.info?console.info.bind(console,c("INFO"),"color: lightblue"):console.log.bind(console,"",c("INFO"))),t<=a.debug&&(o.debug=console.debug?console.debug.bind(console,c("DEBUG"),"color: lightgreen"):console.log.bind(console,"",c("DEBUG")))},c=function(t){var e=i()().format("ss.SSS");return"%c".concat(e," : ").concat(t," : ")},l=n(7543),u="comm",h="rule",f="decl",d=Math.abs,p=String.fromCharCode;function g(t){return t.trim()}function y(t,e,n){return t.replace(e,n)}function m(t,e){return t.indexOf(e)}function b(t,e){return 0|t.charCodeAt(e)}function v(t,e,n){return t.slice(e,n)}function _(t){return t.length}function x(t){return t.length}function k(t,e){return e.push(t),t}function w(t,e){for(var n="",r=x(t),i=0;i0?b(N,--A):0,C--,10===M&&(C=1,E--),M}function B(){return M=A2||R(M)>3?"":" "}function z(t,e){for(;--e&&B()&&!(M<48||M>102||M>57&&M<65||M>70&&M<97););return F(t,I()+(e<6&&32==L()&&32==B()))}function Y(t){for(;B();)switch(M){case t:return A;case 34:case 39:34!==t&&39!==t&&Y(M);break;case 40:41===t&&Y(t);break;case 92:B()}return A}function U(t,e){for(;B()&&t+M!==57&&(t+M!==84||47!==L()););return"/*"+F(e,A-1)+"*"+p(47===t?t:B())}function $(t){for(;!R(L());)B();return F(t,A)}function W(t){return function(t){return N="",t}(q("",null,null,null,[""],t=function(t){return E=C=1,S=_(N=t),A=0,[]}(t),0,[0],t))}function q(t,e,n,r,i,a,o,s,c){for(var l=0,u=0,h=o,f=0,d=0,g=0,b=1,v=1,x=1,w=0,T="",E=i,C=a,S=r,A=T;v;)switch(g=w,w=B()){case 40:if(108!=g&&58==A.charCodeAt(h-1)){-1!=m(A+=y(P(w),"&","&\f"),"&\f")&&(x=-1);break}case 34:case 39:case 91:A+=P(w);break;case 9:case 10:case 13:case 32:A+=j(g);break;case 92:A+=z(I()-1,7);continue;case 47:switch(L()){case 42:case 47:k(V(U(B(),I()),e,n),c);break;default:A+="/"}break;case 123*b:s[l++]=_(A)*x;case 125*b:case 59:case 0:switch(w){case 0:case 125:v=0;case 59+u:d>0&&_(A)-h&&k(d>32?G(A+";",r,n,h-1):G(y(A," ","")+";",r,n,h-2),c);break;case 59:A+=";";default:if(k(S=H(A,e,n,l,u,i,s,T,E=[],C=[],h),a),123===w)if(0===u)q(A,e,S,S,E,a,h,s,C);else switch(f){case 100:case 109:case 115:q(t,S,S,r&&k(H(t,S,S,0,0,i,s,T,i,E=[],h),C),i,C,h,s,r?E:C);break;default:q(A,S,S,S,[""],C,0,s,C)}}l=u=d=0,b=x=1,T=A="",h=o;break;case 58:h=1+_(A),d=g;default:if(b<1)if(123==w)--b;else if(125==w&&0==b++&&125==O())continue;switch(A+=p(w),w*b){case 38:x=u>0?1:(A+="\f",-1);break;case 44:s[l++]=(_(A)-1)*x,x=1;break;case 64:45===L()&&(A+=P(B())),f=L(),u=h=_(T=A+=$(I())),w++;break;case 45:45===g&&2==_(A)&&(b=0)}}return a}function H(t,e,n,r,i,a,o,s,c,l,u){for(var f=i-1,p=0===i?a:[""],m=x(p),b=0,_=0,k=0;b0?p[w]+" "+T:y(T,/&\f/g,p[w])))&&(c[k++]=E);return D(t,e,n,0===i?h:s,c,l,u)}function V(t,e,n){return D(t,e,n,u,p(M),v(t,2,-2),0)}function G(t,e,n,r){return D(t,e,n,f,v(t,0,r),v(t,r+1,-1),r)}const X="9.1.6";function Z(t){return Z="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Z(t)}const Q=function t(e,n,r){var i=Object.assign({depth:2,clobber:!1},r),a=i.depth,o=i.clobber;return Array.isArray(n)&&!Array.isArray(e)?(n.forEach((function(n){return t(e,n,r)})),e):Array.isArray(n)&&Array.isArray(e)?(n.forEach((function(t){-1===e.indexOf(t)&&e.push(t)})),e):void 0===e||a<=0?null!=e&&"object"===Z(e)&&"object"===Z(n)?Object.assign(e,n):n:(void 0!==n&&"object"===Z(e)&&"object"===Z(n)&&Object.keys(n).forEach((function(r){"object"!==Z(n[r])||void 0!==e[r]&&"object"!==Z(e[r])?(o||"object"!==Z(e[r])&&"object"!==Z(n[r]))&&(e[r]=n[r]):(void 0===e[r]&&(e[r]=Array.isArray(n[r])?[]:{}),e[r]=t(e[r],n[r],{depth:a-1,clobber:o}))})),e)},K={min:{r:0,g:0,b:0,s:0,l:0,a:0},max:{r:255,g:255,b:255,h:360,s:100,l:100,a:1},clamp:{r:t=>t>=255?255:t<0?0:t,g:t=>t>=255?255:t<0?0:t,b:t=>t>=255?255:t<0?0:t,h:t=>t%360,s:t=>t>=100?100:t<0?0:t,l:t=>t>=100?100:t<0?0:t,a:t=>t>=1?1:t<0?0:t},toLinear:t=>{const e=t/255;return t>.03928?Math.pow((e+.055)/1.055,2.4):e/12.92},hue2rgb:(t,e,n)=>(n<0&&(n+=1),n>1&&(n-=1),n<1/6?t+6*(e-t)*n:n<.5?e:n<2/3?t+(e-t)*(2/3-n)*6:t),hsl2rgb:({h:t,s:e,l:n},r)=>{if(!e)return 2.55*n;t/=360,e/=100;const i=(n/=100)<.5?n*(1+e):n+e-n*e,a=2*n-i;switch(r){case"r":return 255*K.hue2rgb(a,i,t+1/3);case"g":return 255*K.hue2rgb(a,i,t);case"b":return 255*K.hue2rgb(a,i,t-1/3)}},rgb2hsl:({r:t,g:e,b:n},r)=>{t/=255,e/=255,n/=255;const i=Math.max(t,e,n),a=Math.min(t,e,n),o=(i+a)/2;if("l"===r)return 100*o;if(i===a)return 0;const s=i-a;if("s"===r)return 100*(o>.5?s/(2-i-a):s/(i+a));switch(i){case t:return 60*((e-n)/s+(ee>n?Math.min(e,Math.max(n,t)):Math.min(n,Math.max(e,t)),round:t=>Math.round(1e10*t)/1e10},unit:{dec2hex:t=>{const e=Math.round(t).toString(16);return e.length>1?e:`0${e}`}}},tt={};for(let t=0;t<=255;t++)tt[t]=J.unit.dec2hex(t);const et=new class{constructor(t,e){this.color=e,this.changed=!1,this.data=t,this.type=new class{constructor(){this.type=0}get(){return this.type}set(t){if(this.type&&this.type!==t)throw new Error("Cannot change both RGB and HSL channels at the same time");this.type=t}reset(){this.type=0}is(t){return this.type===t}}}set(t,e){return this.color=e,this.changed=!1,this.data=t,this.type.type=0,this}_ensureHSL(){const t=this.data,{h:e,s:n,l:r}=t;void 0===e&&(t.h=J.channel.rgb2hsl(t,"h")),void 0===n&&(t.s=J.channel.rgb2hsl(t,"s")),void 0===r&&(t.l=J.channel.rgb2hsl(t,"l"))}_ensureRGB(){const t=this.data,{r:e,g:n,b:r}=t;void 0===e&&(t.r=J.channel.hsl2rgb(t,"r")),void 0===n&&(t.g=J.channel.hsl2rgb(t,"g")),void 0===r&&(t.b=J.channel.hsl2rgb(t,"b"))}get r(){const t=this.data,e=t.r;return this.type.is(2)||void 0===e?(this._ensureHSL(),J.channel.hsl2rgb(t,"r")):e}get g(){const t=this.data,e=t.g;return this.type.is(2)||void 0===e?(this._ensureHSL(),J.channel.hsl2rgb(t,"g")):e}get b(){const t=this.data,e=t.b;return this.type.is(2)||void 0===e?(this._ensureHSL(),J.channel.hsl2rgb(t,"b")):e}get h(){const t=this.data,e=t.h;return this.type.is(1)||void 0===e?(this._ensureRGB(),J.channel.rgb2hsl(t,"h")):e}get s(){const t=this.data,e=t.s;return this.type.is(1)||void 0===e?(this._ensureRGB(),J.channel.rgb2hsl(t,"s")):e}get l(){const t=this.data,e=t.l;return this.type.is(1)||void 0===e?(this._ensureRGB(),J.channel.rgb2hsl(t,"l")):e}get a(){return this.data.a}set r(t){this.type.set(1),this.changed=!0,this.data.r=t}set g(t){this.type.set(1),this.changed=!0,this.data.g=t}set b(t){this.type.set(1),this.changed=!0,this.data.b=t}set h(t){this.type.set(2),this.changed=!0,this.data.h=t}set s(t){this.type.set(2),this.changed=!0,this.data.s=t}set l(t){this.type.set(2),this.changed=!0,this.data.l=t}set a(t){this.changed=!0,this.data.a=t}}({r:0,g:0,b:0,a:0},"transparent"),nt={re:/^#((?:[a-f0-9]{2}){2,4}|[a-f0-9]{3})$/i,parse:t=>{if(35!==t.charCodeAt(0))return;const e=t.match(nt.re);if(!e)return;const n=e[1],r=parseInt(n,16),i=n.length,a=i%4==0,o=i>4,s=o?1:17,c=o?8:4,l=a?0:-1,u=o?255:15;return et.set({r:(r>>c*(l+3)&u)*s,g:(r>>c*(l+2)&u)*s,b:(r>>c*(l+1)&u)*s,a:a?(r&u)*s/255:1},t)},stringify:t=>{const{r:e,g:n,b:r,a:i}=t;return i<1?`#${tt[Math.round(e)]}${tt[Math.round(n)]}${tt[Math.round(r)]}${tt[Math.round(255*i)]}`:`#${tt[Math.round(e)]}${tt[Math.round(n)]}${tt[Math.round(r)]}`}},rt=nt,it={re:/^hsla?\(\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?(?:deg|grad|rad|turn)?)\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?%)\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?%)(?:\s*?(?:,|\/)\s*?\+?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e-?\d+)?(%)?))?\s*?\)$/i,hueRe:/^(.+?)(deg|grad|rad|turn)$/i,_hue2deg:t=>{const e=t.match(it.hueRe);if(e){const[,t,n]=e;switch(n){case"grad":return J.channel.clamp.h(.9*parseFloat(t));case"rad":return J.channel.clamp.h(180*parseFloat(t)/Math.PI);case"turn":return J.channel.clamp.h(360*parseFloat(t))}}return J.channel.clamp.h(parseFloat(t))},parse:t=>{const e=t.charCodeAt(0);if(104!==e&&72!==e)return;const n=t.match(it.re);if(!n)return;const[,r,i,a,o,s]=n;return et.set({h:it._hue2deg(r),s:J.channel.clamp.s(parseFloat(i)),l:J.channel.clamp.l(parseFloat(a)),a:o?J.channel.clamp.a(s?parseFloat(o)/100:parseFloat(o)):1},t)},stringify:t=>{const{h:e,s:n,l:r,a:i}=t;return i<1?`hsla(${J.lang.round(e)}, ${J.lang.round(n)}%, ${J.lang.round(r)}%, ${i})`:`hsl(${J.lang.round(e)}, ${J.lang.round(n)}%, ${J.lang.round(r)}%)`}},at=it,ot={colors:{aliceblue:"#f0f8ff",antiquewhite:"#faebd7",aqua:"#00ffff",aquamarine:"#7fffd4",azure:"#f0ffff",beige:"#f5f5dc",bisque:"#ffe4c4",black:"#000000",blanchedalmond:"#ffebcd",blue:"#0000ff",blueviolet:"#8a2be2",brown:"#a52a2a",burlywood:"#deb887",cadetblue:"#5f9ea0",chartreuse:"#7fff00",chocolate:"#d2691e",coral:"#ff7f50",cornflowerblue:"#6495ed",cornsilk:"#fff8dc",crimson:"#dc143c",cyanaqua:"#00ffff",darkblue:"#00008b",darkcyan:"#008b8b",darkgoldenrod:"#b8860b",darkgray:"#a9a9a9",darkgreen:"#006400",darkgrey:"#a9a9a9",darkkhaki:"#bdb76b",darkmagenta:"#8b008b",darkolivegreen:"#556b2f",darkorange:"#ff8c00",darkorchid:"#9932cc",darkred:"#8b0000",darksalmon:"#e9967a",darkseagreen:"#8fbc8f",darkslateblue:"#483d8b",darkslategray:"#2f4f4f",darkslategrey:"#2f4f4f",darkturquoise:"#00ced1",darkviolet:"#9400d3",deeppink:"#ff1493",deepskyblue:"#00bfff",dimgray:"#696969",dimgrey:"#696969",dodgerblue:"#1e90ff",firebrick:"#b22222",floralwhite:"#fffaf0",forestgreen:"#228b22",fuchsia:"#ff00ff",gainsboro:"#dcdcdc",ghostwhite:"#f8f8ff",gold:"#ffd700",goldenrod:"#daa520",gray:"#808080",green:"#008000",greenyellow:"#adff2f",grey:"#808080",honeydew:"#f0fff0",hotpink:"#ff69b4",indianred:"#cd5c5c",indigo:"#4b0082",ivory:"#fffff0",khaki:"#f0e68c",lavender:"#e6e6fa",lavenderblush:"#fff0f5",lawngreen:"#7cfc00",lemonchiffon:"#fffacd",lightblue:"#add8e6",lightcoral:"#f08080",lightcyan:"#e0ffff",lightgoldenrodyellow:"#fafad2",lightgray:"#d3d3d3",lightgreen:"#90ee90",lightgrey:"#d3d3d3",lightpink:"#ffb6c1",lightsalmon:"#ffa07a",lightseagreen:"#20b2aa",lightskyblue:"#87cefa",lightslategray:"#778899",lightslategrey:"#778899",lightsteelblue:"#b0c4de",lightyellow:"#ffffe0",lime:"#00ff00",limegreen:"#32cd32",linen:"#faf0e6",magenta:"#ff00ff",maroon:"#800000",mediumaquamarine:"#66cdaa",mediumblue:"#0000cd",mediumorchid:"#ba55d3",mediumpurple:"#9370db",mediumseagreen:"#3cb371",mediumslateblue:"#7b68ee",mediumspringgreen:"#00fa9a",mediumturquoise:"#48d1cc",mediumvioletred:"#c71585",midnightblue:"#191970",mintcream:"#f5fffa",mistyrose:"#ffe4e1",moccasin:"#ffe4b5",navajowhite:"#ffdead",navy:"#000080",oldlace:"#fdf5e6",olive:"#808000",olivedrab:"#6b8e23",orange:"#ffa500",orangered:"#ff4500",orchid:"#da70d6",palegoldenrod:"#eee8aa",palegreen:"#98fb98",paleturquoise:"#afeeee",palevioletred:"#db7093",papayawhip:"#ffefd5",peachpuff:"#ffdab9",peru:"#cd853f",pink:"#ffc0cb",plum:"#dda0dd",powderblue:"#b0e0e6",purple:"#800080",rebeccapurple:"#663399",red:"#ff0000",rosybrown:"#bc8f8f",royalblue:"#4169e1",saddlebrown:"#8b4513",salmon:"#fa8072",sandybrown:"#f4a460",seagreen:"#2e8b57",seashell:"#fff5ee",sienna:"#a0522d",silver:"#c0c0c0",skyblue:"#87ceeb",slateblue:"#6a5acd",slategray:"#708090",slategrey:"#708090",snow:"#fffafa",springgreen:"#00ff7f",tan:"#d2b48c",teal:"#008080",thistle:"#d8bfd8",transparent:"#00000000",turquoise:"#40e0d0",violet:"#ee82ee",wheat:"#f5deb3",white:"#ffffff",whitesmoke:"#f5f5f5",yellow:"#ffff00",yellowgreen:"#9acd32"},parse:t=>{t=t.toLowerCase();const e=ot.colors[t];if(e)return rt.parse(e)},stringify:t=>{const e=rt.stringify(t);for(const t in ot.colors)if(ot.colors[t]===e)return t}},st=ot,ct={re:/^rgba?\(\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?))\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?))\s*?(?:,|\s)\s*?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?))(?:\s*?(?:,|\/)\s*?\+?(-?(?:\d+(?:\.\d+)?|(?:\.\d+))(?:e\d+)?(%?)))?\s*?\)$/i,parse:t=>{const e=t.charCodeAt(0);if(114!==e&&82!==e)return;const n=t.match(ct.re);if(!n)return;const[,r,i,a,o,s,c,l,u]=n;return et.set({r:J.channel.clamp.r(i?2.55*parseFloat(r):parseFloat(r)),g:J.channel.clamp.g(o?2.55*parseFloat(a):parseFloat(a)),b:J.channel.clamp.b(c?2.55*parseFloat(s):parseFloat(s)),a:l?J.channel.clamp.a(u?parseFloat(l)/100:parseFloat(l)):1},t)},stringify:t=>{const{r:e,g:n,b:r,a:i}=t;return i<1?`rgba(${J.lang.round(e)}, ${J.lang.round(n)}, ${J.lang.round(r)}, ${J.lang.round(i)})`:`rgb(${J.lang.round(e)}, ${J.lang.round(n)}, ${J.lang.round(r)})`}},lt=ct,ut={format:{keyword:st,hex:rt,rgb:lt,rgba:lt,hsl:at,hsla:at},parse:t=>{if("string"!=typeof t)return t;const e=rt.parse(t)||lt.parse(t)||at.parse(t)||st.parse(t);if(e)return e;throw new Error(`Unsupported color format: "${t}"`)},stringify:t=>!t.changed&&t.color?t.color:t.type.is(2)||void 0===t.data.r?at.stringify(t):t.a<1||!Number.isInteger(t.r)||!Number.isInteger(t.g)||!Number.isInteger(t.b)?lt.stringify(t):rt.stringify(t)},ht=(t,e)=>{const n=ut.parse(t);for(const t in e)n[t]=J.channel.clamp[t](e[t]);return ut.stringify(n)},ft=(t,e)=>{const n=ut.parse(t),r={};for(const t in e)e[t]&&(r[t]=n[t]+e[t]);return ht(t,r)},dt=(t,e,n=0,r=1)=>{if("number"!=typeof t)return ht(t,{a:e});const i=et.set({r:J.channel.clamp.r(t),g:J.channel.clamp.g(e),b:J.channel.clamp.b(n),a:J.channel.clamp.a(r)});return ut.stringify(i)},pt=(t,e=100)=>{const n=ut.parse(t);return n.r=255-n.r,n.g=255-n.g,n.b=255-n.b,((t,e,n=50)=>{const{r,g:i,b:a,a:o}=ut.parse(t),{r:s,g:c,b:l,a:u}=ut.parse(e),h=n/100,f=2*h-1,d=o-u,p=((f*d==-1?f:(f+d)/(1+f*d))+1)/2,g=1-p;return dt(r*p+s*g,i*p+c*g,a*p+l*g,o*h+u*(1-h))})(n,t,e)},gt=(t,e,n)=>{const r=ut.parse(t),i=r[e],a=J.channel.clamp[e](i+n);return i!==a&&(r[e]=a),ut.stringify(r)},yt=(t,e)=>gt(t,"l",-e),mt=(t,e)=>gt(t,"l",e);var bt=function(t,e){return ft(t,e?{s:-40,l:10}:{s:-40,l:-10})};function vt(t){return vt="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},vt(t)}function _t(t,e){for(var n=0;nt.length)&&(e=t.length);for(var n=0,r=new Array(e);n1&&void 0!==arguments[1]?arguments[1]:"";return Object.keys(e).reduce((function(r,i){return Array.isArray(e[i])?r:"object"===Rt(e[i])&&null!==e[i]?[].concat(It(r),[n+i],It(t(e[i],""))):[].concat(It(r),[n+i])}),[])}(Pt,"");const zt=Pt;function Yt(t){return Yt="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},Yt(t)}var Ut,$t=Object.freeze(zt),Wt=Q({},$t),qt=[],Ht=Q({},$t),Vt=function(t,e){for(var n=Q({},t),r={},i=0;i-1||e[n].indexOf(">")>-1||e[n].indexOf("url(data:")>-1)&&delete e[n],"object"===Yt(e[n])&&t(e[n])}))},Qt=function(t){t.fontFamily&&(t.themeVariables&&t.themeVariables.fontFamily||(t.themeVariables={fontFamily:t.fontFamily})),qt.push(t),Vt(Wt,qt)},Kt=function(){Vt(Wt,qt=[])},Jt=n(7856),te=n.n(Jt),ee=function(t){var e=t.replace(/\\u[\dA-F]{4}/gi,(function(t){return String.fromCharCode(parseInt(t.replace(/\\u/g,""),16))}));return e=(e=(e=e.replace(/\\x([0-9a-f]{2})/gi,(function(t,e){return String.fromCharCode(parseInt(e,16))}))).replace(/\\[\d\d\d]{3}/gi,(function(t){return String.fromCharCode(parseInt(t.replace(/\\/g,""),8))}))).replace(/\\[\d\d\d]{2}/gi,(function(t){return String.fromCharCode(parseInt(t.replace(/\\/g,""),8))}))},ne=function(t){for(var e="",n=0;n>=0;){if(!((n=t.indexOf("=0)){e+=t,n=-1;break}e+=t.substr(0,n),(n=(t=t.substr(n+1)).indexOf("<\/script>"))>=0&&(n+=9,t=t.substr(n))}var r=ee(e);return(r=(r=(r=(r=r.replaceAll(/script>/gi,"#")).replaceAll(/javascript:/gi,"#")).replaceAll(/javascript&colon/gi,"#")).replaceAll(/onerror=/gi,"onerror:")).replaceAll(/')}else"loose"!==s.securityLevel&&(A=te().sanitize(A,{ADD_TAGS:["foreignobject"],ADD_ATTR:["dominant-baseline"]}));if(void 0!==n)switch(m){case"flowchart":case"flowchart-v2":n(A,sa.bindFunctions);break;case"gantt":n(A,fo.bindFunctions);break;case"class":case"classDiagram":n(A,hr.bindFunctions);break;default:n(A)}else o.debug("CB = undefined!");ms.forEach((function(t){t()})),ms=[];var D="sandbox"===s.securityLevel?"#i"+t:"#d"+t,O=(0,l.select)(D).node();return null!==O&&"function"==typeof O.remove&&(0,l.select)(D).node().remove(),A},parse:function(t,e){nu||(ql(),nu=!0);var n=!1;try{var r=e||new Xl(t);return r.db.clear(),r.parse(t)}catch(t){if(n=!0,!hu.parseError)throw t;null!=t.str?hu.parseError(t.str,t.hash):hu.parseError(t)}return!n},parseDirective:function(t,e,n,r){try{if(void 0!==e)switch(e=e.trim(),n){case"open_directive":iu={};break;case"type_directive":iu.type=e.toLowerCase();break;case"arg_directive":iu.args=JSON.parse(e);break;case"close_directive":(function(t,e,n){switch(o.debug("Directive type=".concat(e.type," with args:"),e.args),e.type){case"init":case"initialize":["config"].forEach((function(t){void 0!==e.args[t]&&("flowchart-v2"===n&&(n="flowchart"),e.args[n]=e.args[t],delete e.args[t])})),o.debug("sanitize in handleDirective",e.args),En(e.args),o.debug("sanitize in handleDirective (done)",e.args),Qt(e.args);break;case"wrap":case"nowrap":t&&t.setWrap&&t.setWrap("wrap"===e.type);break;case"themeCss":o.warn("themeCss encountered");break;default:o.warn("Unhandled directive: source: '%%{".concat(e.type,": ").concat(JSON.stringify(e.args?e.args:{}),"}%%"),e)}})(t,iu,r),iu=null}}catch(t){o.error("Error while rendering sequenceDiagram directive: ".concat(e," jison context: ").concat(n)),o.error(t.message)}},initialize:function(t){var e;null!=t&&t.fontFamily&&(null!==(e=t.themeVariables)&&void 0!==e&&e.fontFamily||(t.themeVariables={fontFamily:t.fontFamily})),function(t){Ut=Q({},t)}(t),null!=t&&t.theme&&Lt[t.theme]?t.themeVariables=Lt[t.theme].getThemeVariables(t.themeVariables):t&&(t.themeVariables=Lt.default.getThemeVariables(t.themeVariables));var n="object"===eu(t)?function(t){return Wt=Q({},$t),Wt=Q(Wt,t),t.theme&&Lt[t.theme]&&(Wt.themeVariables=Lt[t.theme].getThemeVariables(t.themeVariables)),Ht=Vt(Wt,qt),Wt}(t):Gt();au(n),s(n.logLevel),nu||(ql(),nu=!0)},getConfig:Xt,setConfig:function(t){return Q(Ht,t),Xt()},getSiteConfig:Gt,updateSiteConfig:function(t){return Wt=Q(Wt,t),Vt(Wt,qt),Wt},reset:function(){Kt()},globalReset:function(){Kt(),au(Xt())},defaultConfig:$t});s(Xt().logLevel),Kt(Xt());const su=ou;var cu=function(){var t,e,n=su.getConfig();arguments.length>=2?(void 0!==arguments[0]&&(uu.sequenceConfig=arguments[0]),t=arguments[1]):t=arguments[0],"function"==typeof arguments[arguments.length-1]?(e=arguments[arguments.length-1],o.debug("Callback function found")):void 0!==n.mermaid&&("function"==typeof n.mermaid.callback?(e=n.mermaid.callback,o.debug("Callback function found")):o.debug("No Callback function found")),t=void 0===t?document.querySelectorAll(".mermaid"):"string"==typeof t?document.querySelectorAll(t):t instanceof window.Node?[t]:t,o.debug("Start On Load before: "+uu.startOnLoad),void 0!==uu.startOnLoad&&(o.debug("Start On Load inner: "+uu.startOnLoad),su.updateSiteConfig({startOnLoad:uu.startOnLoad})),void 0!==uu.ganttConfig&&su.updateSiteConfig({gantt:uu.ganttConfig});for(var r,i=new Sn.initIdGenerator(n.deterministicIds,n.deterministicIDSeed),a=function(n){var a=t[n];if(a.getAttribute("data-processed"))return"continue";a.setAttribute("data-processed",!0);var s="mermaid-".concat(i.next());r=a.innerHTML,r=Sn.entityDecode(r).trim().replace(//gi,"
");var c=Sn.detectInit(r);c&&o.debug("Detected early reinit: ",c);try{su.render(s,r,(function(t,n){a.innerHTML=t,void 0!==e&&e(s),n&&n(a)}),a)}catch(t){throw o.warn("Catching Error (bootstrap)"),{error:t,message:t.str}}},s=0;s{t.exports={graphlib:n(6614),dagre:n(6478),intersect:n(8114),render:n(5787),util:n(8355),version:n(5689)}},9144:(t,e,n)=>{var r=n(8355);function i(t,e,n,i){var a=t.append("marker").attr("id",e).attr("viewBox","0 0 10 10").attr("refX",9).attr("refY",5).attr("markerUnits","strokeWidth").attr("markerWidth",8).attr("markerHeight",6).attr("orient","auto").append("path").attr("d","M 0 0 L 10 5 L 0 10 z").style("stroke-width",1).style("stroke-dasharray","1,0");r.applyStyle(a,n[i+"Style"]),n[i+"Class"]&&a.attr("class",n[i+"Class"])}t.exports={default:i,normal:i,vee:function(t,e,n,i){var a=t.append("marker").attr("id",e).attr("viewBox","0 0 10 10").attr("refX",9).attr("refY",5).attr("markerUnits","strokeWidth").attr("markerWidth",8).attr("markerHeight",6).attr("orient","auto").append("path").attr("d","M 0 0 L 10 5 L 0 10 L 4 5 z").style("stroke-width",1).style("stroke-dasharray","1,0");r.applyStyle(a,n[i+"Style"]),n[i+"Class"]&&a.attr("class",n[i+"Class"])},undirected:function(t,e,n,i){var a=t.append("marker").attr("id",e).attr("viewBox","0 0 10 10").attr("refX",9).attr("refY",5).attr("markerUnits","strokeWidth").attr("markerWidth",8).attr("markerHeight",6).attr("orient","auto").append("path").attr("d","M 0 5 L 10 5").style("stroke-width",1).style("stroke-dasharray","1,0");r.applyStyle(a,n[i+"Style"]),n[i+"Class"]&&a.attr("class",n[i+"Class"])}}},5632:(t,e,n)=>{var r=n(8355),i=n(4322),a=n(1322);t.exports=function(t,e){var n,o=e.nodes().filter((function(t){return r.isSubgraph(e,t)})),s=t.selectAll("g.cluster").data(o,(function(t){return t}));return s.selectAll("*").remove(),s.enter().append("g").attr("class","cluster").attr("id",(function(t){return e.node(t).id})).style("opacity",0),s=t.selectAll("g.cluster"),r.applyTransition(s,e).style("opacity",1),s.each((function(t){var n=e.node(t),r=i.select(this);i.select(this).append("rect");var o=r.append("g").attr("class","label");a(o,n,n.clusterLabelPos)})),s.selectAll("rect").each((function(t){var n=e.node(t),a=i.select(this);r.applyStyle(a,n.style)})),n=s.exit?s.exit():s.selectAll(null),r.applyTransition(n,e).style("opacity",0).remove(),s}},6315:(t,e,n)=>{"use strict";var r=n(1034),i=n(1322),a=n(8355),o=n(4322);t.exports=function(t,e){var n,s=t.selectAll("g.edgeLabel").data(e.edges(),(function(t){return a.edgeToId(t)})).classed("update",!0);return s.exit().remove(),s.enter().append("g").classed("edgeLabel",!0).style("opacity",0),(s=t.selectAll("g.edgeLabel")).each((function(t){var n=o.select(this);n.select(".label").remove();var a=e.edge(t),s=i(n,e.edge(t),0,0).classed("label",!0),c=s.node().getBBox();a.labelId&&s.attr("id",a.labelId),r.has(a,"width")||(a.width=c.width),r.has(a,"height")||(a.height=c.height)})),n=s.exit?s.exit():s.selectAll(null),a.applyTransition(n,e).style("opacity",0).remove(),s}},940:(t,e,n)=>{"use strict";var r=n(1034),i=n(3042),a=n(8355),o=n(4322);function s(t,e){var n=(o.line||o.svg.line)().x((function(t){return t.x})).y((function(t){return t.y}));return(n.curve||n.interpolate)(t.curve),n(e)}t.exports=function(t,e,n){var c=t.selectAll("g.edgePath").data(e.edges(),(function(t){return a.edgeToId(t)})).classed("update",!0),l=function(t,e){var n=t.enter().append("g").attr("class","edgePath").style("opacity",0);return n.append("path").attr("class","path").attr("d",(function(t){var n=e.edge(t),i=e.node(t.v).elem;return s(n,r.range(n.points.length).map((function(){return e=(t=i).getBBox(),{x:(n=t.ownerSVGElement.getScreenCTM().inverse().multiply(t.getScreenCTM()).translate(e.width/2,e.height/2)).e,y:n.f};var t,e,n})))})),n.append("defs"),n}(c,e);!function(t,e){var n=t.exit();a.applyTransition(n,e).style("opacity",0).remove()}(c,e);var u=void 0!==c.merge?c.merge(l):c;return a.applyTransition(u,e).style("opacity",1),u.each((function(t){var n=o.select(this),r=e.edge(t);r.elem=this,r.id&&n.attr("id",r.id),a.applyClass(n,r.class,(n.classed("update")?"update ":"")+"edgePath")})),u.selectAll("path.path").each((function(t){var n=e.edge(t);n.arrowheadId=r.uniqueId("arrowhead");var c=o.select(this).attr("marker-end",(function(){return"url("+(t=location.href,e=n.arrowheadId,t.split("#")[0]+"#"+e+")");var t,e})).style("fill","none");a.applyTransition(c,e).attr("d",(function(t){return function(t,e){var n=t.edge(e),r=t.node(e.v),a=t.node(e.w),o=n.points.slice(1,n.points.length-1);return o.unshift(i(r,o[0])),o.push(i(a,o[o.length-1])),s(n,o)}(e,t)})),a.applyStyle(c,n.style)})),u.selectAll("defs *").remove(),u.selectAll("defs").each((function(t){var r=e.edge(t);(0,n[r.arrowhead])(o.select(this),r.arrowheadId,r,"arrowhead")})),u}},607:(t,e,n)=>{"use strict";var r=n(1034),i=n(1322),a=n(8355),o=n(4322);t.exports=function(t,e,n){var s,c=e.nodes().filter((function(t){return!a.isSubgraph(e,t)})),l=t.selectAll("g.node").data(c,(function(t){return t})).classed("update",!0);return l.exit().remove(),l.enter().append("g").attr("class","node").style("opacity",0),(l=t.selectAll("g.node")).each((function(t){var s=e.node(t),c=o.select(this);a.applyClass(c,s.class,(c.classed("update")?"update ":"")+"node"),c.select("g.label").remove();var l=c.append("g").attr("class","label"),u=i(l,s),h=n[s.shape],f=r.pick(u.node().getBBox(),"width","height");s.elem=this,s.id&&c.attr("id",s.id),s.labelId&&l.attr("id",s.labelId),r.has(s,"width")&&(f.width=s.width),r.has(s,"height")&&(f.height=s.height),f.width+=s.paddingLeft+s.paddingRight,f.height+=s.paddingTop+s.paddingBottom,l.attr("transform","translate("+(s.paddingLeft-s.paddingRight)/2+","+(s.paddingTop-s.paddingBottom)/2+")");var d=o.select(this);d.select(".label-container").remove();var p=h(d,f,s).classed("label-container",!0);a.applyStyle(p,s.style);var g=p.node().getBBox();s.width=g.width,s.height=g.height})),s=l.exit?l.exit():l.selectAll(null),a.applyTransition(s,e).style("opacity",0).remove(),l}},4322:(t,e,n)=>{var r;if(!r)try{r=n(7543)}catch(t){}r||(r=window.d3),t.exports=r},6478:(t,e,n)=>{var r;try{r=n(681)}catch(t){}r||(r=window.dagre),t.exports=r},6614:(t,e,n)=>{var r;try{r=n(8282)}catch(t){}r||(r=window.graphlib),t.exports=r},8114:(t,e,n)=>{t.exports={node:n(3042),circle:n(6587),ellipse:n(3260),polygon:n(5337),rect:n(8049)}},6587:(t,e,n)=>{var r=n(3260);t.exports=function(t,e,n){return r(t,e,e,n)}},3260:t=>{t.exports=function(t,e,n,r){var i=t.x,a=t.y,o=i-r.x,s=a-r.y,c=Math.sqrt(e*e*s*s+n*n*o*o),l=Math.abs(e*n*o/c);r.x{function e(t,e){return t*e>0}t.exports=function(t,n,r,i){var a,o,s,c,l,u,h,f,d,p,g,y,m;if(!(a=n.y-t.y,s=t.x-n.x,l=n.x*t.y-t.x*n.y,d=a*r.x+s*r.y+l,p=a*i.x+s*i.y+l,0!==d&&0!==p&&e(d,p)||(o=i.y-r.y,c=r.x-i.x,u=i.x*r.y-r.x*i.y,h=o*t.x+c*t.y+u,f=o*n.x+c*n.y+u,0!==h&&0!==f&&e(h,f)||0==(g=a*c-o*s))))return y=Math.abs(g/2),{x:(m=s*u-c*l)<0?(m-y)/g:(m+y)/g,y:(m=o*l-a*u)<0?(m-y)/g:(m+y)/g}}},3042:t=>{t.exports=function(t,e){return t.intersect(e)}},5337:(t,e,n)=>{var r=n(6808);t.exports=function(t,e,n){var i=t.x,a=t.y,o=[],s=Number.POSITIVE_INFINITY,c=Number.POSITIVE_INFINITY;e.forEach((function(t){s=Math.min(s,t.x),c=Math.min(c,t.y)}));for(var l=i-t.width/2-s,u=a-t.height/2-c,h=0;h1&&o.sort((function(t,e){var r=t.x-n.x,i=t.y-n.y,a=Math.sqrt(r*r+i*i),o=e.x-n.x,s=e.y-n.y,c=Math.sqrt(o*o+s*s);return a{t.exports=function(t,e){var n,r,i=t.x,a=t.y,o=e.x-i,s=e.y-a,c=t.width/2,l=t.height/2;return Math.abs(s)*c>Math.abs(o)*l?(s<0&&(l=-l),n=0===s?0:l*o/s,r=l):(o<0&&(c=-c),n=c,r=0===o?0:c*s/o),{x:i+n,y:a+r}}},8284:(t,e,n)=>{var r=n(8355);t.exports=function(t,e){var n=t.append("foreignObject").attr("width","100000"),i=n.append("xhtml:div");i.attr("xmlns","http://www.w3.org/1999/xhtml");var a=e.label;switch(typeof a){case"function":i.insert(a);break;case"object":i.insert((function(){return a}));break;default:i.html(a)}r.applyStyle(i,e.labelStyle),i.style("display","inline-block"),i.style("white-space","nowrap");var o=i.node().getBoundingClientRect();return n.attr("width",o.width).attr("height",o.height),n}},1322:(t,e,n)=>{var r=n(7318),i=n(8284),a=n(8287);t.exports=function(t,e,n){var o=e.label,s=t.append("g");"svg"===e.labelType?a(s,e):"string"!=typeof o||"html"===e.labelType?i(s,e):r(s,e);var c,l=s.node().getBBox();switch(n){case"top":c=-e.height/2;break;case"bottom":c=e.height/2-l.height;break;default:c=-l.height/2}return s.attr("transform","translate("+-l.width/2+","+c+")"),s}},8287:(t,e,n)=>{var r=n(8355);t.exports=function(t,e){var n=t;return n.node().appendChild(e.label),r.applyStyle(n,e.labelStyle),n}},7318:(t,e,n)=>{var r=n(8355);t.exports=function(t,e){for(var n=t.append("text"),i=function(t){for(var e,n="",r=!1,i=0;i{var r;try{r={defaults:n(1747),each:n(6073),isFunction:n(3560),isPlainObject:n(8630),pick:n(9722),has:n(8721),range:n(6026),uniqueId:n(3955)}}catch(t){}r||(r=window._),t.exports=r},6381:(t,e,n)=>{"use strict";var r=n(8355),i=n(4322);t.exports=function(t,e){var n=t.filter((function(){return!i.select(this).classed("update")}));function a(t){var n=e.node(t);return"translate("+n.x+","+n.y+")"}n.attr("transform",a),r.applyTransition(t,e).style("opacity",1).attr("transform",a),r.applyTransition(n.selectAll("rect"),e).attr("width",(function(t){return e.node(t).width})).attr("height",(function(t){return e.node(t).height})).attr("x",(function(t){return-e.node(t).width/2})).attr("y",(function(t){return-e.node(t).height/2}))}},4577:(t,e,n)=>{"use strict";var r=n(8355),i=n(4322),a=n(1034);t.exports=function(t,e){function n(t){var n=e.edge(t);return a.has(n,"x")?"translate("+n.x+","+n.y+")":""}t.filter((function(){return!i.select(this).classed("update")})).attr("transform",n),r.applyTransition(t,e).style("opacity",1).attr("transform",n)}},4849:(t,e,n)=>{"use strict";var r=n(8355),i=n(4322);t.exports=function(t,e){function n(t){var n=e.node(t);return"translate("+n.x+","+n.y+")"}t.filter((function(){return!i.select(this).classed("update")})).attr("transform",n),r.applyTransition(t,e).style("opacity",1).attr("transform",n)}},5787:(t,e,n)=>{var r=n(1034),i=n(4322),a=n(6478).layout;t.exports=function(){var t=n(607),e=n(5632),i=n(6315),l=n(940),u=n(4849),h=n(4577),f=n(6381),d=n(4418),p=n(9144),g=function(n,g){!function(t){t.nodes().forEach((function(e){var n=t.node(e);r.has(n,"label")||t.children(e).length||(n.label=e),r.has(n,"paddingX")&&r.defaults(n,{paddingLeft:n.paddingX,paddingRight:n.paddingX}),r.has(n,"paddingY")&&r.defaults(n,{paddingTop:n.paddingY,paddingBottom:n.paddingY}),r.has(n,"padding")&&r.defaults(n,{paddingLeft:n.padding,paddingRight:n.padding,paddingTop:n.padding,paddingBottom:n.padding}),r.defaults(n,o),r.each(["paddingLeft","paddingRight","paddingTop","paddingBottom"],(function(t){n[t]=Number(n[t])})),r.has(n,"width")&&(n._prevWidth=n.width),r.has(n,"height")&&(n._prevHeight=n.height)})),t.edges().forEach((function(e){var n=t.edge(e);r.has(n,"label")||(n.label=""),r.defaults(n,s)}))}(g);var y=c(n,"output"),m=c(y,"clusters"),b=c(y,"edgePaths"),v=i(c(y,"edgeLabels"),g),_=t(c(y,"nodes"),g,d);a(g),u(_,g),h(v,g),l(b,g,p);var x=e(m,g);f(x,g),function(t){r.each(t.nodes(),(function(e){var n=t.node(e);r.has(n,"_prevWidth")?n.width=n._prevWidth:delete n.width,r.has(n,"_prevHeight")?n.height=n._prevHeight:delete n.height,delete n._prevWidth,delete n._prevHeight}))}(g)};return g.createNodes=function(e){return arguments.length?(t=e,g):t},g.createClusters=function(t){return arguments.length?(e=t,g):e},g.createEdgeLabels=function(t){return arguments.length?(i=t,g):i},g.createEdgePaths=function(t){return arguments.length?(l=t,g):l},g.shapes=function(t){return arguments.length?(d=t,g):d},g.arrows=function(t){return arguments.length?(p=t,g):p},g};var o={paddingLeft:10,paddingRight:10,paddingTop:10,paddingBottom:10,rx:0,ry:0,shape:"rect"},s={arrowhead:"normal",curve:i.curveLinear};function c(t,e){var n=t.select("g."+e);return n.empty()&&(n=t.append("g").attr("class",e)),n}},4418:(t,e,n)=>{"use strict";var r=n(8049),i=n(3260),a=n(6587),o=n(5337);t.exports={rect:function(t,e,n){var i=t.insert("rect",":first-child").attr("rx",n.rx).attr("ry",n.ry).attr("x",-e.width/2).attr("y",-e.height/2).attr("width",e.width).attr("height",e.height);return n.intersect=function(t){return r(n,t)},i},ellipse:function(t,e,n){var r=e.width/2,a=e.height/2,o=t.insert("ellipse",":first-child").attr("x",-e.width/2).attr("y",-e.height/2).attr("rx",r).attr("ry",a);return n.intersect=function(t){return i(n,r,a,t)},o},circle:function(t,e,n){var r=Math.max(e.width,e.height)/2,i=t.insert("circle",":first-child").attr("x",-e.width/2).attr("y",-e.height/2).attr("r",r);return n.intersect=function(t){return a(n,r,t)},i},diamond:function(t,e,n){var r=e.width*Math.SQRT2/2,i=e.height*Math.SQRT2/2,a=[{x:0,y:-i},{x:-r,y:0},{x:0,y:i},{x:r,y:0}],s=t.insert("polygon",":first-child").attr("points",a.map((function(t){return t.x+","+t.y})).join(" "));return n.intersect=function(t){return o(n,a,t)},s}}},8355:(t,e,n)=>{var r=n(1034);t.exports={isSubgraph:function(t,e){return!!t.children(e).length},edgeToId:function(t){return a(t.v)+":"+a(t.w)+":"+a(t.name)},applyStyle:function(t,e){e&&t.attr("style",e)},applyClass:function(t,e,n){e&&t.attr("class",e).attr("class",n+" "+t.attr("class"))},applyTransition:function(t,e){var n=e.graph();if(r.isPlainObject(n)){var i=n.transition;if(r.isFunction(i))return i(t)}return t}};var i=/:/g;function a(t){return t?String(t).replace(i,"\\:"):""}},5689:t=>{t.exports="0.6.4"},681:(t,e,n)=>{t.exports={graphlib:n(574),layout:n(8123),debug:n(7570),util:{time:n(1138).time,notime:n(1138).notime},version:n(8177)}},2188:(t,e,n)=>{"use strict";var r=n(8436),i=n(4079);t.exports={run:function(t){var e="greedy"===t.graph().acyclicer?i(t,function(t){return function(e){return t.edge(e).weight}}(t)):function(t){var e=[],n={},i={};return r.forEach(t.nodes(),(function a(o){r.has(i,o)||(i[o]=!0,n[o]=!0,r.forEach(t.outEdges(o),(function(t){r.has(n,t.w)?e.push(t):a(t.w)})),delete n[o])})),e}(t);r.forEach(e,(function(e){var n=t.edge(e);t.removeEdge(e),n.forwardName=e.name,n.reversed=!0,t.setEdge(e.w,e.v,n,r.uniqueId("rev"))}))},undo:function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);if(n.reversed){t.removeEdge(e);var r=n.forwardName;delete n.reversed,delete n.forwardName,t.setEdge(e.w,e.v,n,r)}}))}}},1133:(t,e,n)=>{var r=n(8436),i=n(1138);function a(t,e,n,r,a,o){var s={width:0,height:0,rank:o,borderType:e},c=a[e][o-1],l=i.addDummyNode(t,"border",s,n);a[e][o]=l,t.setParent(l,r),c&&t.setEdge(c,l,{weight:1})}t.exports=function(t){r.forEach(t.children(),(function e(n){var i=t.children(n),o=t.node(n);if(i.length&&r.forEach(i,e),r.has(o,"minRank")){o.borderLeft=[],o.borderRight=[];for(var s=o.minRank,c=o.maxRank+1;s{"use strict";var r=n(8436);function i(t){r.forEach(t.nodes(),(function(e){a(t.node(e))})),r.forEach(t.edges(),(function(e){a(t.edge(e))}))}function a(t){var e=t.width;t.width=t.height,t.height=e}function o(t){t.y=-t.y}function s(t){var e=t.x;t.x=t.y,t.y=e}t.exports={adjust:function(t){var e=t.graph().rankdir.toLowerCase();"lr"!==e&&"rl"!==e||i(t)},undo:function(t){var e=t.graph().rankdir.toLowerCase();"bt"!==e&&"rl"!==e||function(t){r.forEach(t.nodes(),(function(e){o(t.node(e))})),r.forEach(t.edges(),(function(e){var n=t.edge(e);r.forEach(n.points,o),r.has(n,"y")&&o(n)}))}(t),"lr"!==e&&"rl"!==e||(function(t){r.forEach(t.nodes(),(function(e){s(t.node(e))})),r.forEach(t.edges(),(function(e){var n=t.edge(e);r.forEach(n.points,s),r.has(n,"x")&&s(n)}))}(t),i(t))}}},7822:t=>{function e(){var t={};t._next=t._prev=t,this._sentinel=t}function n(t){t._prev._next=t._next,t._next._prev=t._prev,delete t._next,delete t._prev}function r(t,e){if("_next"!==t&&"_prev"!==t)return e}t.exports=e,e.prototype.dequeue=function(){var t=this._sentinel,e=t._prev;if(e!==t)return n(e),e},e.prototype.enqueue=function(t){var e=this._sentinel;t._prev&&t._next&&n(t),t._next=e._next,e._next._prev=t,e._next=t,t._prev=e},e.prototype.toString=function(){for(var t=[],e=this._sentinel,n=e._prev;n!==e;)t.push(JSON.stringify(n,r)),n=n._prev;return"["+t.join(", ")+"]"}},7570:(t,e,n)=>{var r=n(8436),i=n(1138),a=n(574).Graph;t.exports={debugOrdering:function(t){var e=i.buildLayerMatrix(t),n=new a({compound:!0,multigraph:!0}).setGraph({});return r.forEach(t.nodes(),(function(e){n.setNode(e,{label:e}),n.setParent(e,"layer"+t.node(e).rank)})),r.forEach(t.edges(),(function(t){n.setEdge(t.v,t.w,{},t.name)})),r.forEach(e,(function(t,e){var i="layer"+e;n.setNode(i,{rank:"same"}),r.reduce(t,(function(t,e){return n.setEdge(t,e,{style:"invis"}),e}))})),n}}},574:(t,e,n)=>{var r;try{r=n(8282)}catch(t){}r||(r=window.graphlib),t.exports=r},4079:(t,e,n)=>{var r=n(8436),i=n(574).Graph,a=n(7822);t.exports=function(t,e){if(t.nodeCount()<=1)return[];var n=function(t,e){var n=new i,o=0,s=0;r.forEach(t.nodes(),(function(t){n.setNode(t,{v:t,in:0,out:0})})),r.forEach(t.edges(),(function(t){var r=n.edge(t.v,t.w)||0,i=e(t),a=r+i;n.setEdge(t.v,t.w,a),s=Math.max(s,n.node(t.v).out+=i),o=Math.max(o,n.node(t.w).in+=i)}));var l=r.range(s+o+3).map((function(){return new a})),u=o+1;return r.forEach(n.nodes(),(function(t){c(l,u,n.node(t))})),{graph:n,buckets:l,zeroIdx:u}}(t,e||o),l=function(t,e,n){for(var r,i=[],a=e[e.length-1],o=e[0];t.nodeCount();){for(;r=o.dequeue();)s(t,e,n,r);for(;r=a.dequeue();)s(t,e,n,r);if(t.nodeCount())for(var c=e.length-2;c>0;--c)if(r=e[c].dequeue()){i=i.concat(s(t,e,n,r,!0));break}}return i}(n.graph,n.buckets,n.zeroIdx);return r.flatten(r.map(l,(function(e){return t.outEdges(e.v,e.w)})),!0)};var o=r.constant(1);function s(t,e,n,i,a){var o=a?[]:void 0;return r.forEach(t.inEdges(i.v),(function(r){var i=t.edge(r),s=t.node(r.v);a&&o.push({v:r.v,w:r.w}),s.out-=i,c(e,n,s)})),r.forEach(t.outEdges(i.v),(function(r){var i=t.edge(r),a=r.w,o=t.node(a);o.in-=i,c(e,n,o)})),t.removeNode(i.v),o}function c(t,e,n){n.out?n.in?t[n.out-n.in+e].enqueue(n):t[t.length-1].enqueue(n):t[0].enqueue(n)}},8123:(t,e,n)=>{"use strict";var r=n(8436),i=n(2188),a=n(5995),o=n(8093),s=n(1138).normalizeRanks,c=n(4219),l=n(1138).removeEmptyRanks,u=n(2981),h=n(1133),f=n(3258),d=n(3408),p=n(7873),g=n(1138),y=n(574).Graph;t.exports=function(t,e){var n=e&&e.debugTiming?g.time:g.notime;n("layout",(function(){var e=n(" buildLayoutGraph",(function(){return function(t){var e=new y({multigraph:!0,compound:!0}),n=C(t.graph());return e.setGraph(r.merge({},b,E(n,m),r.pick(n,v))),r.forEach(t.nodes(),(function(n){var i=C(t.node(n));e.setNode(n,r.defaults(E(i,_),x)),e.setParent(n,t.parent(n))})),r.forEach(t.edges(),(function(n){var i=C(t.edge(n));e.setEdge(n,r.merge({},w,E(i,k),r.pick(i,T)))})),e}(t)}));n(" runLayout",(function(){!function(t,e){e(" makeSpaceForEdgeLabels",(function(){!function(t){var e=t.graph();e.ranksep/=2,r.forEach(t.edges(),(function(n){var r=t.edge(n);r.minlen*=2,"c"!==r.labelpos.toLowerCase()&&("TB"===e.rankdir||"BT"===e.rankdir?r.width+=r.labeloffset:r.height+=r.labeloffset)}))}(t)})),e(" removeSelfEdges",(function(){!function(t){r.forEach(t.edges(),(function(e){if(e.v===e.w){var n=t.node(e.v);n.selfEdges||(n.selfEdges=[]),n.selfEdges.push({e,label:t.edge(e)}),t.removeEdge(e)}}))}(t)})),e(" acyclic",(function(){i.run(t)})),e(" nestingGraph.run",(function(){u.run(t)})),e(" rank",(function(){o(g.asNonCompoundGraph(t))})),e(" injectEdgeLabelProxies",(function(){!function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);if(n.width&&n.height){var r=t.node(e.v),i={rank:(t.node(e.w).rank-r.rank)/2+r.rank,e};g.addDummyNode(t,"edge-proxy",i,"_ep")}}))}(t)})),e(" removeEmptyRanks",(function(){l(t)})),e(" nestingGraph.cleanup",(function(){u.cleanup(t)})),e(" normalizeRanks",(function(){s(t)})),e(" assignRankMinMax",(function(){!function(t){var e=0;r.forEach(t.nodes(),(function(n){var i=t.node(n);i.borderTop&&(i.minRank=t.node(i.borderTop).rank,i.maxRank=t.node(i.borderBottom).rank,e=r.max(e,i.maxRank))})),t.graph().maxRank=e}(t)})),e(" removeEdgeLabelProxies",(function(){!function(t){r.forEach(t.nodes(),(function(e){var n=t.node(e);"edge-proxy"===n.dummy&&(t.edge(n.e).labelRank=n.rank,t.removeNode(e))}))}(t)})),e(" normalize.run",(function(){a.run(t)})),e(" parentDummyChains",(function(){c(t)})),e(" addBorderSegments",(function(){h(t)})),e(" order",(function(){d(t)})),e(" insertSelfEdges",(function(){!function(t){var e=g.buildLayerMatrix(t);r.forEach(e,(function(e){var n=0;r.forEach(e,(function(e,i){var a=t.node(e);a.order=i+n,r.forEach(a.selfEdges,(function(e){g.addDummyNode(t,"selfedge",{width:e.label.width,height:e.label.height,rank:a.rank,order:i+ ++n,e:e.e,label:e.label},"_se")})),delete a.selfEdges}))}))}(t)})),e(" adjustCoordinateSystem",(function(){f.adjust(t)})),e(" position",(function(){p(t)})),e(" positionSelfEdges",(function(){!function(t){r.forEach(t.nodes(),(function(e){var n=t.node(e);if("selfedge"===n.dummy){var r=t.node(n.e.v),i=r.x+r.width/2,a=r.y,o=n.x-i,s=r.height/2;t.setEdge(n.e,n.label),t.removeNode(e),n.label.points=[{x:i+2*o/3,y:a-s},{x:i+5*o/6,y:a-s},{x:i+o,y:a},{x:i+5*o/6,y:a+s},{x:i+2*o/3,y:a+s}],n.label.x=n.x,n.label.y=n.y}}))}(t)})),e(" removeBorderNodes",(function(){!function(t){r.forEach(t.nodes(),(function(e){if(t.children(e).length){var n=t.node(e),i=t.node(n.borderTop),a=t.node(n.borderBottom),o=t.node(r.last(n.borderLeft)),s=t.node(r.last(n.borderRight));n.width=Math.abs(s.x-o.x),n.height=Math.abs(a.y-i.y),n.x=o.x+n.width/2,n.y=i.y+n.height/2}})),r.forEach(t.nodes(),(function(e){"border"===t.node(e).dummy&&t.removeNode(e)}))}(t)})),e(" normalize.undo",(function(){a.undo(t)})),e(" fixupEdgeLabelCoords",(function(){!function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);if(r.has(n,"x"))switch("l"!==n.labelpos&&"r"!==n.labelpos||(n.width-=n.labeloffset),n.labelpos){case"l":n.x-=n.width/2+n.labeloffset;break;case"r":n.x+=n.width/2+n.labeloffset}}))}(t)})),e(" undoCoordinateSystem",(function(){f.undo(t)})),e(" translateGraph",(function(){!function(t){var e=Number.POSITIVE_INFINITY,n=0,i=Number.POSITIVE_INFINITY,a=0,o=t.graph(),s=o.marginx||0,c=o.marginy||0;function l(t){var r=t.x,o=t.y,s=t.width,c=t.height;e=Math.min(e,r-s/2),n=Math.max(n,r+s/2),i=Math.min(i,o-c/2),a=Math.max(a,o+c/2)}r.forEach(t.nodes(),(function(e){l(t.node(e))})),r.forEach(t.edges(),(function(e){var n=t.edge(e);r.has(n,"x")&&l(n)})),e-=s,i-=c,r.forEach(t.nodes(),(function(n){var r=t.node(n);r.x-=e,r.y-=i})),r.forEach(t.edges(),(function(n){var a=t.edge(n);r.forEach(a.points,(function(t){t.x-=e,t.y-=i})),r.has(a,"x")&&(a.x-=e),r.has(a,"y")&&(a.y-=i)})),o.width=n-e+s,o.height=a-i+c}(t)})),e(" assignNodeIntersects",(function(){!function(t){r.forEach(t.edges(),(function(e){var n,r,i=t.edge(e),a=t.node(e.v),o=t.node(e.w);i.points?(n=i.points[0],r=i.points[i.points.length-1]):(i.points=[],n=o,r=a),i.points.unshift(g.intersectRect(a,n)),i.points.push(g.intersectRect(o,r))}))}(t)})),e(" reversePoints",(function(){!function(t){r.forEach(t.edges(),(function(e){var n=t.edge(e);n.reversed&&n.points.reverse()}))}(t)})),e(" acyclic.undo",(function(){i.undo(t)}))}(e,n)})),n(" updateInputGraph",(function(){!function(t,e){r.forEach(t.nodes(),(function(n){var r=t.node(n),i=e.node(n);r&&(r.x=i.x,r.y=i.y,e.children(n).length&&(r.width=i.width,r.height=i.height))})),r.forEach(t.edges(),(function(n){var i=t.edge(n),a=e.edge(n);i.points=a.points,r.has(a,"x")&&(i.x=a.x,i.y=a.y)})),t.graph().width=e.graph().width,t.graph().height=e.graph().height}(t,e)}))}))};var m=["nodesep","edgesep","ranksep","marginx","marginy"],b={ranksep:50,edgesep:20,nodesep:50,rankdir:"tb"},v=["acyclicer","ranker","rankdir","align"],_=["width","height"],x={width:0,height:0},k=["minlen","weight","width","height","labeloffset"],w={minlen:1,weight:1,width:0,height:0,labeloffset:10,labelpos:"r"},T=["labelpos"];function E(t,e){return r.mapValues(r.pick(t,e),Number)}function C(t){var e={};return r.forEach(t,(function(t,n){e[n.toLowerCase()]=t})),e}},8436:(t,e,n)=>{var r;try{r={cloneDeep:n(361),constant:n(5703),defaults:n(1747),each:n(6073),filter:n(3105),find:n(3311),flatten:n(5564),forEach:n(4486),forIn:n(2620),has:n(8721),isUndefined:n(2353),last:n(928),map:n(5161),mapValues:n(6604),max:n(6162),merge:n(3857),min:n(3632),minBy:n(2762),now:n(7771),pick:n(9722),range:n(6026),reduce:n(4061),sortBy:n(9734),uniqueId:n(3955),values:n(2628),zipObject:n(7287)}}catch(t){}r||(r=window._),t.exports=r},2981:(t,e,n)=>{var r=n(8436),i=n(1138);function a(t,e,n,o,s,c,l){var u=t.children(l);if(u.length){var h=i.addBorderNode(t,"_bt"),f=i.addBorderNode(t,"_bb"),d=t.node(l);t.setParent(h,l),d.borderTop=h,t.setParent(f,l),d.borderBottom=f,r.forEach(u,(function(r){a(t,e,n,o,s,c,r);var i=t.node(r),u=i.borderTop?i.borderTop:r,d=i.borderBottom?i.borderBottom:r,p=i.borderTop?o:2*o,g=u!==d?1:s-c[l]+1;t.setEdge(h,u,{weight:p,minlen:g,nestingEdge:!0}),t.setEdge(d,f,{weight:p,minlen:g,nestingEdge:!0})})),t.parent(l)||t.setEdge(e,h,{weight:0,minlen:s+c[l]})}else l!==e&&t.setEdge(e,l,{weight:0,minlen:n})}t.exports={run:function(t){var e=i.addDummyNode(t,"root",{},"_root"),n=function(t){var e={};function n(i,a){var o=t.children(i);o&&o.length&&r.forEach(o,(function(t){n(t,a+1)})),e[i]=a}return r.forEach(t.children(),(function(t){n(t,1)})),e}(t),o=r.max(r.values(n))-1,s=2*o+1;t.graph().nestingRoot=e,r.forEach(t.edges(),(function(e){t.edge(e).minlen*=s}));var c=function(t){return r.reduce(t.edges(),(function(e,n){return e+t.edge(n).weight}),0)}(t)+1;r.forEach(t.children(),(function(r){a(t,e,s,c,o,n,r)})),t.graph().nodeRankFactor=s},cleanup:function(t){var e=t.graph();t.removeNode(e.nestingRoot),delete e.nestingRoot,r.forEach(t.edges(),(function(e){t.edge(e).nestingEdge&&t.removeEdge(e)}))}}},5995:(t,e,n)=>{"use strict";var r=n(8436),i=n(1138);t.exports={run:function(t){t.graph().dummyChains=[],r.forEach(t.edges(),(function(e){!function(t,e){var n,r,a,o=e.v,s=t.node(o).rank,c=e.w,l=t.node(c).rank,u=e.name,h=t.edge(e),f=h.labelRank;if(l!==s+1){for(t.removeEdge(e),a=0,++s;s{var r=n(8436);t.exports=function(t,e,n){var i,a={};r.forEach(n,(function(n){for(var r,o,s=t.parent(n);s;){if((r=t.parent(s))?(o=a[r],a[r]=s):(o=i,i=s),o&&o!==s)return void e.setEdge(o,s);s=r}}))}},5439:(t,e,n)=>{var r=n(8436);t.exports=function(t,e){return r.map(e,(function(e){var n=t.inEdges(e);if(n.length){var i=r.reduce(n,(function(e,n){var r=t.edge(n),i=t.node(n.v);return{sum:e.sum+r.weight*i.order,weight:e.weight+r.weight}}),{sum:0,weight:0});return{v:e,barycenter:i.sum/i.weight,weight:i.weight}}return{v:e}}))}},3128:(t,e,n)=>{var r=n(8436),i=n(574).Graph;t.exports=function(t,e,n){var a=function(t){for(var e;t.hasNode(e=r.uniqueId("_root")););return e}(t),o=new i({compound:!0}).setGraph({root:a}).setDefaultNodeLabel((function(e){return t.node(e)}));return r.forEach(t.nodes(),(function(i){var s=t.node(i),c=t.parent(i);(s.rank===e||s.minRank<=e&&e<=s.maxRank)&&(o.setNode(i),o.setParent(i,c||a),r.forEach(t[n](i),(function(e){var n=e.v===i?e.w:e.v,a=o.edge(n,i),s=r.isUndefined(a)?0:a.weight;o.setEdge(n,i,{weight:t.edge(e).weight+s})})),r.has(s,"minRank")&&o.setNode(i,{borderLeft:s.borderLeft[e],borderRight:s.borderRight[e]}))})),o}},6630:(t,e,n)=>{"use strict";var r=n(8436);function i(t,e,n){for(var i=r.zipObject(n,r.map(n,(function(t,e){return e}))),a=r.flatten(r.map(e,(function(e){return r.sortBy(r.map(t.outEdges(e),(function(e){return{pos:i[e.w],weight:t.edge(e).weight}})),"pos")})),!0),o=1;o0;)e%2&&(n+=c[e+1]),c[e=e-1>>1]+=t.weight;l+=t.weight*n}))),l}t.exports=function(t,e){for(var n=0,r=1;r{"use strict";var r=n(8436),i=n(2588),a=n(6630),o=n(1026),s=n(3128),c=n(5093),l=n(574).Graph,u=n(1138);function h(t,e,n){return r.map(e,(function(e){return s(t,e,n)}))}function f(t,e){var n=new l;r.forEach(t,(function(t){var i=t.graph().root,a=o(t,i,n,e);r.forEach(a.vs,(function(e,n){t.node(e).order=n})),c(t,n,a.vs)}))}function d(t,e){r.forEach(e,(function(e){r.forEach(e,(function(e,n){t.node(e).order=n}))}))}t.exports=function(t){var e=u.maxRank(t),n=h(t,r.range(1,e+1),"inEdges"),o=h(t,r.range(e-1,-1,-1),"outEdges"),s=i(t);d(t,s);for(var c,l=Number.POSITIVE_INFINITY,p=0,g=0;g<4;++p,++g){f(p%2?n:o,p%4>=2),s=u.buildLayerMatrix(t);var y=a(t,s);y{"use strict";var r=n(8436);t.exports=function(t){var e={},n=r.filter(t.nodes(),(function(e){return!t.children(e).length})),i=r.max(r.map(n,(function(e){return t.node(e).rank}))),a=r.map(r.range(i+1),(function(){return[]})),o=r.sortBy(n,(function(e){return t.node(e).rank}));return r.forEach(o,(function n(i){if(!r.has(e,i)){e[i]=!0;var o=t.node(i);a[o.rank].push(i),r.forEach(t.successors(i),n)}})),a}},9567:(t,e,n)=>{"use strict";var r=n(8436);t.exports=function(t,e){var n={};return r.forEach(t,(function(t,e){var i=n[t.v]={indegree:0,in:[],out:[],vs:[t.v],i:e};r.isUndefined(t.barycenter)||(i.barycenter=t.barycenter,i.weight=t.weight)})),r.forEach(e.edges(),(function(t){var e=n[t.v],i=n[t.w];r.isUndefined(e)||r.isUndefined(i)||(i.indegree++,e.out.push(n[t.w]))})),function(t){var e=[];function n(t){return function(e){var n,i,a,o;e.merged||(r.isUndefined(e.barycenter)||r.isUndefined(t.barycenter)||e.barycenter>=t.barycenter)&&(i=e,a=0,o=0,(n=t).weight&&(a+=n.barycenter*n.weight,o+=n.weight),i.weight&&(a+=i.barycenter*i.weight,o+=i.weight),n.vs=i.vs.concat(n.vs),n.barycenter=a/o,n.weight=o,n.i=Math.min(i.i,n.i),i.merged=!0)}}function i(e){return function(n){n.in.push(e),0==--n.indegree&&t.push(n)}}for(;t.length;){var a=t.pop();e.push(a),r.forEach(a.in.reverse(),n(a)),r.forEach(a.out,i(a))}return r.map(r.filter(e,(function(t){return!t.merged})),(function(t){return r.pick(t,["vs","i","barycenter","weight"])}))}(r.filter(n,(function(t){return!t.indegree})))}},1026:(t,e,n)=>{var r=n(8436),i=n(5439),a=n(9567),o=n(7304);t.exports=function t(e,n,s,c){var l=e.children(n),u=e.node(n),h=u?u.borderLeft:void 0,f=u?u.borderRight:void 0,d={};h&&(l=r.filter(l,(function(t){return t!==h&&t!==f})));var p=i(e,l);r.forEach(p,(function(n){if(e.children(n.v).length){var i=t(e,n.v,s,c);d[n.v]=i,r.has(i,"barycenter")&&(a=n,o=i,r.isUndefined(a.barycenter)?(a.barycenter=o.barycenter,a.weight=o.weight):(a.barycenter=(a.barycenter*a.weight+o.barycenter*o.weight)/(a.weight+o.weight),a.weight+=o.weight))}var a,o}));var g=a(p,s);!function(t,e){r.forEach(t,(function(t){t.vs=r.flatten(t.vs.map((function(t){return e[t]?e[t].vs:t})),!0)}))}(g,d);var y=o(g,c);if(h&&(y.vs=r.flatten([h,y.vs,f],!0),e.predecessors(h).length)){var m=e.node(e.predecessors(h)[0]),b=e.node(e.predecessors(f)[0]);r.has(y,"barycenter")||(y.barycenter=0,y.weight=0),y.barycenter=(y.barycenter*y.weight+m.order+b.order)/(y.weight+2),y.weight+=2}return y}},7304:(t,e,n)=>{var r=n(8436),i=n(1138);function a(t,e,n){for(var i;e.length&&(i=r.last(e)).i<=n;)e.pop(),t.push(i.vs),n++;return n}t.exports=function(t,e){var n,o=i.partition(t,(function(t){return r.has(t,"barycenter")})),s=o.lhs,c=r.sortBy(o.rhs,(function(t){return-t.i})),l=[],u=0,h=0,f=0;s.sort((n=!!e,function(t,e){return t.barycentere.barycenter?1:n?e.i-t.i:t.i-e.i})),f=a(l,c,f),r.forEach(s,(function(t){f+=t.vs.length,l.push(t.vs),u+=t.barycenter*t.weight,h+=t.weight,f=a(l,c,f)}));var d={vs:r.flatten(l,!0)};return h&&(d.barycenter=u/h,d.weight=h),d}},4219:(t,e,n)=>{var r=n(8436);t.exports=function(t){var e=function(t){var e={},n=0;return r.forEach(t.children(),(function i(a){var o=n;r.forEach(t.children(a),i),e[a]={low:o,lim:n++}})),e}(t);r.forEach(t.graph().dummyChains,(function(n){for(var r=t.node(n),i=r.edgeObj,a=function(t,e,n,r){var i,a,o=[],s=[],c=Math.min(e[n].low,e[r].low),l=Math.max(e[n].lim,e[r].lim);i=n;do{i=t.parent(i),o.push(i)}while(i&&(e[i].low>c||l>e[i].lim));for(a=i,i=r;(i=t.parent(i))!==a;)s.push(i);return{path:o.concat(s.reverse()),lca:a}}(t,e,i.v,i.w),o=a.path,s=a.lca,c=0,l=o[c],u=!0;n!==i.w;){if(r=t.node(n),u){for(;(l=o[c])!==s&&t.node(l).maxRank{"use strict";var r=n(8436),i=n(574).Graph,a=n(1138);function o(t,e){var n={};return r.reduce(e,(function(e,i){var a=0,o=0,s=e.length,l=r.last(i);return r.forEach(i,(function(e,u){var h=function(t,e){if(t.node(e).dummy)return r.find(t.predecessors(e),(function(e){return t.node(e).dummy}))}(t,e),f=h?t.node(h).order:s;(h||e===l)&&(r.forEach(i.slice(o,u+1),(function(e){r.forEach(t.predecessors(e),(function(r){var i=t.node(r),o=i.order;!(os)&&c(n,e,l)}))}))}return r.reduce(e,(function(e,n){var a,o=-1,s=0;return r.forEach(n,(function(r,c){if("border"===t.node(r).dummy){var l=t.predecessors(r);l.length&&(a=t.node(l[0]).order,i(n,s,c,o,a),s=c,o=a)}i(n,s,n.length,a,e.length)})),n})),n}function c(t,e,n){if(e>n){var r=e;e=n,n=r}var i=t[e];i||(t[e]=i={}),i[n]=!0}function l(t,e,n){if(e>n){var i=e;e=n,n=i}return r.has(t[e],n)}function u(t,e,n,i){var a={},o={},s={};return r.forEach(e,(function(t){r.forEach(t,(function(t,e){a[t]=t,o[t]=t,s[t]=e}))})),r.forEach(e,(function(t){var e=-1;r.forEach(t,(function(t){var c=i(t);if(c.length){c=r.sortBy(c,(function(t){return s[t]}));for(var u=(c.length-1)/2,h=Math.floor(u),f=Math.ceil(u);h<=f;++h){var d=c[h];o[t]===t&&e{"use strict";var r=n(8436),i=n(1138),a=n(3573).positionX;t.exports=function(t){(function(t){var e=i.buildLayerMatrix(t),n=t.graph().ranksep,a=0;r.forEach(e,(function(e){var i=r.max(r.map(e,(function(e){return t.node(e).height})));r.forEach(e,(function(e){t.node(e).y=a+i/2})),a+=i+n}))})(t=i.asNonCompoundGraph(t)),r.forEach(a(t),(function(e,n){t.node(n).x=e}))}},300:(t,e,n)=>{"use strict";var r=n(8436),i=n(574).Graph,a=n(6681).slack;function o(t,e){return r.forEach(t.nodes(),(function n(i){r.forEach(e.nodeEdges(i),(function(r){var o=r.v,s=i===o?r.w:o;t.hasNode(s)||a(e,r)||(t.setNode(s,{}),t.setEdge(i,s,{}),n(s))}))})),t.nodeCount()}function s(t,e){return r.minBy(e.edges(),(function(n){if(t.hasNode(n.v)!==t.hasNode(n.w))return a(e,n)}))}function c(t,e,n){r.forEach(t.nodes(),(function(t){e.node(t).rank+=n}))}t.exports=function(t){var e,n,r=new i({directed:!1}),l=t.nodes()[0],u=t.nodeCount();for(r.setNode(l,{});o(r,t){"use strict";var r=n(6681).longestPath,i=n(300),a=n(2472);t.exports=function(t){switch(t.graph().ranker){case"network-simplex":default:!function(t){a(t)}(t);break;case"tight-tree":!function(t){r(t),i(t)}(t);break;case"longest-path":o(t)}};var o=r},2472:(t,e,n)=>{"use strict";var r=n(8436),i=n(300),a=n(6681).slack,o=n(6681).longestPath,s=n(574).alg.preorder,c=n(574).alg.postorder,l=n(1138).simplify;function u(t){t=l(t),o(t);var e,n=i(t);for(d(n),h(n,t);e=g(n);)m(n,t,e,y(n,t,e))}function h(t,e){var n=c(t,t.nodes());n=n.slice(0,n.length-1),r.forEach(n,(function(n){!function(t,e,n){var r=t.node(n).parent;t.edge(n,r).cutvalue=f(t,e,n)}(t,e,n)}))}function f(t,e,n){var i=t.node(n).parent,a=!0,o=e.edge(n,i),s=0;return o||(a=!1,o=e.edge(i,n)),s=o.weight,r.forEach(e.nodeEdges(n),(function(r){var o,c,l=r.v===n,u=l?r.w:r.v;if(u!==i){var h=l===a,f=e.edge(r).weight;if(s+=h?f:-f,o=n,c=u,t.hasEdge(o,c)){var d=t.edge(n,u).cutvalue;s+=h?-d:d}}})),s}function d(t,e){arguments.length<2&&(e=t.nodes()[0]),p(t,{},1,e)}function p(t,e,n,i,a){var o=n,s=t.node(i);return e[i]=!0,r.forEach(t.neighbors(i),(function(a){r.has(e,a)||(n=p(t,e,n,a,i))})),s.low=o,s.lim=n++,a?s.parent=a:delete s.parent,n}function g(t){return r.find(t.edges(),(function(e){return t.edge(e).cutvalue<0}))}function y(t,e,n){var i=n.v,o=n.w;e.hasEdge(i,o)||(i=n.w,o=n.v);var s=t.node(i),c=t.node(o),l=s,u=!1;s.lim>c.lim&&(l=c,u=!0);var h=r.filter(e.edges(),(function(e){return u===b(0,t.node(e.v),l)&&u!==b(0,t.node(e.w),l)}));return r.minBy(h,(function(t){return a(e,t)}))}function m(t,e,n,i){var a=n.v,o=n.w;t.removeEdge(a,o),t.setEdge(i.v,i.w,{}),d(t),h(t,e),function(t,e){var n=r.find(t.nodes(),(function(t){return!e.node(t).parent})),i=s(t,n);i=i.slice(1),r.forEach(i,(function(n){var r=t.node(n).parent,i=e.edge(n,r),a=!1;i||(i=e.edge(r,n),a=!0),e.node(n).rank=e.node(r).rank+(a?i.minlen:-i.minlen)}))}(t,e)}function b(t,e,n){return n.low<=e.lim&&e.lim<=n.lim}t.exports=u,u.initLowLimValues=d,u.initCutValues=h,u.calcCutValue=f,u.leaveEdge=g,u.enterEdge=y,u.exchangeEdges=m},6681:(t,e,n)=>{"use strict";var r=n(8436);t.exports={longestPath:function(t){var e={};r.forEach(t.sources(),(function n(i){var a=t.node(i);if(r.has(e,i))return a.rank;e[i]=!0;var o=r.min(r.map(t.outEdges(i),(function(e){return n(e.w)-t.edge(e).minlen})));return o!==Number.POSITIVE_INFINITY&&null!=o||(o=0),a.rank=o}))},slack:function(t,e){return t.node(e.w).rank-t.node(e.v).rank-t.edge(e).minlen}}},1138:(t,e,n)=>{"use strict";var r=n(8436),i=n(574).Graph;function a(t,e,n,i){var a;do{a=r.uniqueId(i)}while(t.hasNode(a));return n.dummy=e,t.setNode(a,n),a}function o(t){return r.max(r.map(t.nodes(),(function(e){var n=t.node(e).rank;if(!r.isUndefined(n))return n})))}t.exports={addDummyNode:a,simplify:function(t){var e=(new i).setGraph(t.graph());return r.forEach(t.nodes(),(function(n){e.setNode(n,t.node(n))})),r.forEach(t.edges(),(function(n){var r=e.edge(n.v,n.w)||{weight:0,minlen:1},i=t.edge(n);e.setEdge(n.v,n.w,{weight:r.weight+i.weight,minlen:Math.max(r.minlen,i.minlen)})})),e},asNonCompoundGraph:function(t){var e=new i({multigraph:t.isMultigraph()}).setGraph(t.graph());return r.forEach(t.nodes(),(function(n){t.children(n).length||e.setNode(n,t.node(n))})),r.forEach(t.edges(),(function(n){e.setEdge(n,t.edge(n))})),e},successorWeights:function(t){var e=r.map(t.nodes(),(function(e){var n={};return r.forEach(t.outEdges(e),(function(e){n[e.w]=(n[e.w]||0)+t.edge(e).weight})),n}));return r.zipObject(t.nodes(),e)},predecessorWeights:function(t){var e=r.map(t.nodes(),(function(e){var n={};return r.forEach(t.inEdges(e),(function(e){n[e.v]=(n[e.v]||0)+t.edge(e).weight})),n}));return r.zipObject(t.nodes(),e)},intersectRect:function(t,e){var n,r,i=t.x,a=t.y,o=e.x-i,s=e.y-a,c=t.width/2,l=t.height/2;if(!o&&!s)throw new Error("Not possible to find intersection inside of the rectangle");return Math.abs(s)*c>Math.abs(o)*l?(s<0&&(l=-l),n=l*o/s,r=l):(o<0&&(c=-c),n=c,r=c*s/o),{x:i+n,y:a+r}},buildLayerMatrix:function(t){var e=r.map(r.range(o(t)+1),(function(){return[]}));return r.forEach(t.nodes(),(function(n){var i=t.node(n),a=i.rank;r.isUndefined(a)||(e[a][i.order]=n)})),e},normalizeRanks:function(t){var e=r.min(r.map(t.nodes(),(function(e){return t.node(e).rank})));r.forEach(t.nodes(),(function(n){var i=t.node(n);r.has(i,"rank")&&(i.rank-=e)}))},removeEmptyRanks:function(t){var e=r.min(r.map(t.nodes(),(function(e){return t.node(e).rank}))),n=[];r.forEach(t.nodes(),(function(r){var i=t.node(r).rank-e;n[i]||(n[i]=[]),n[i].push(r)}));var i=0,a=t.graph().nodeRankFactor;r.forEach(n,(function(e,n){r.isUndefined(e)&&n%a!=0?--i:i&&r.forEach(e,(function(e){t.node(e).rank+=i}))}))},addBorderNode:function(t,e,n,r){var i={width:0,height:0};return arguments.length>=4&&(i.rank=n,i.order=r),a(t,"border",i,e)},maxRank:o,partition:function(t,e){var n={lhs:[],rhs:[]};return r.forEach(t,(function(t){e(t)?n.lhs.push(t):n.rhs.push(t)})),n},time:function(t,e){var n=r.now();try{return e()}finally{console.log(t+" time: "+(r.now()-n)+"ms")}},notime:function(t,e){return e()}}},8177:t=>{t.exports="0.8.5"},7856:function(t){t.exports=function(){"use strict";function t(e){return t="function"==typeof Symbol&&"symbol"==typeof Symbol.iterator?function(t){return typeof t}:function(t){return t&&"function"==typeof Symbol&&t.constructor===Symbol&&t!==Symbol.prototype?"symbol":typeof t},t(e)}function e(t,n){return e=Object.setPrototypeOf||function(t,e){return t.__proto__=e,t},e(t,n)}function n(){if("undefined"==typeof Reflect||!Reflect.construct)return!1;if(Reflect.construct.sham)return!1;if("function"==typeof Proxy)return!0;try{return Boolean.prototype.valueOf.call(Reflect.construct(Boolean,[],(function(){}))),!0}catch(t){return!1}}function r(t,i,a){return r=n()?Reflect.construct:function(t,n,r){var i=[null];i.push.apply(i,n);var a=new(Function.bind.apply(t,i));return r&&e(a,r.prototype),a},r.apply(null,arguments)}function i(t){return function(t){if(Array.isArray(t))return a(t)}(t)||function(t){if("undefined"!=typeof Symbol&&null!=t[Symbol.iterator]||null!=t["@@iterator"])return Array.from(t)}(t)||function(t,e){if(t){if("string"==typeof t)return a(t,e);var n=Object.prototype.toString.call(t).slice(8,-1);return"Object"===n&&t.constructor&&(n=t.constructor.name),"Map"===n||"Set"===n?Array.from(t):"Arguments"===n||/^(?:Ui|I)nt(?:8|16|32)(?:Clamped)?Array$/.test(n)?a(t,e):void 0}}(t)||function(){throw new TypeError("Invalid attempt to spread non-iterable instance.\nIn order to be iterable, non-array objects must have a [Symbol.iterator]() method.")}()}function a(t,e){(null==e||e>t.length)&&(e=t.length);for(var n=0,r=new Array(e);n1?n-1:0),i=1;i/gm),q=f(/^data-[\-\w.\u00B7-\uFFFF]/),H=f(/^aria-[\-\w]+$/),V=f(/^(?:(?:(?:f|ht)tps?|mailto|tel|callto|cid|xmpp):|[^a-z]|[a-z+.\-]+(?:[^a-z+.\-:]|$))/i),G=f(/^(?:\w+script|data):/i),X=f(/[\u0000-\u0020\u00A0\u1680\u180E\u2000-\u2029\u205F\u3000]/g),Z=f(/^html$/i),Q=function(){return"undefined"==typeof window?null:window},K=function(e,n){if("object"!==t(e)||"function"!=typeof e.createPolicy)return null;var r=null,i="data-tt-policy-suffix";n.currentScript&&n.currentScript.hasAttribute(i)&&(r=n.currentScript.getAttribute(i));var a="dompurify"+(r?"#"+r:"");try{return e.createPolicy(a,{createHTML:function(t){return t},createScriptURL:function(t){return t}})}catch(t){return console.warn("TrustedTypes policy "+a+" could not be created."),null}};return function e(){var n=arguments.length>0&&void 0!==arguments[0]?arguments[0]:Q(),r=function(t){return e(t)};if(r.version="2.3.10",r.removed=[],!n||!n.document||9!==n.document.nodeType)return r.isSupported=!1,r;var a=n.document,o=n.document,s=n.DocumentFragment,c=n.HTMLTemplateElement,l=n.Node,u=n.Element,f=n.NodeFilter,d=n.NamedNodeMap,p=void 0===d?n.NamedNodeMap||n.MozNamedAttrMap:d,g=n.HTMLFormElement,y=n.DOMParser,m=n.trustedTypes,A=u.prototype,J=D(A,"cloneNode"),tt=D(A,"nextSibling"),et=D(A,"childNodes"),nt=D(A,"parentNode");if("function"==typeof c){var rt=o.createElement("template");rt.content&&rt.content.ownerDocument&&(o=rt.content.ownerDocument)}var it=K(m,a),at=it?it.createHTML(""):"",ot=o,st=ot.implementation,ct=ot.createNodeIterator,lt=ot.createDocumentFragment,ut=ot.getElementsByTagName,ht=a.importNode,ft={};try{ft=N(o).documentMode?o.documentMode:{}}catch(t){}var dt={};r.isSupported="function"==typeof nt&&st&&void 0!==st.createHTMLDocument&&9!==ft;var pt,gt,yt=$,mt=W,bt=q,vt=H,_t=G,xt=X,kt=V,wt=null,Tt=M({},[].concat(i(O),i(B),i(L),i(F),i(P))),Et=null,Ct=M({},[].concat(i(j),i(z),i(Y),i(U))),St=Object.seal(Object.create(null,{tagNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},attributeNameCheck:{writable:!0,configurable:!1,enumerable:!0,value:null},allowCustomizedBuiltInElements:{writable:!0,configurable:!1,enumerable:!0,value:!1}})),At=null,Mt=null,Nt=!0,Dt=!0,Ot=!1,Bt=!1,Lt=!1,It=!1,Ft=!1,Rt=!1,Pt=!1,jt=!1,zt=!0,Yt=!0,Ut=!1,$t={},Wt=null,qt=M({},["annotation-xml","audio","colgroup","desc","foreignobject","head","iframe","math","mi","mn","mo","ms","mtext","noembed","noframes","noscript","plaintext","script","style","svg","template","thead","title","video","xmp"]),Ht=null,Vt=M({},["audio","video","img","source","image","track"]),Gt=null,Xt=M({},["alt","class","for","id","label","name","pattern","placeholder","role","summary","title","value","style","xmlns"]),Zt="http://www.w3.org/1998/Math/MathML",Qt="http://www.w3.org/2000/svg",Kt="http://www.w3.org/1999/xhtml",Jt=Kt,te=!1,ee=["application/xhtml+xml","text/html"],ne="text/html",re=null,ie=o.createElement("form"),ae=function(t){return t instanceof RegExp||t instanceof Function},oe=function(e){re&&re===e||(e&&"object"===t(e)||(e={}),e=N(e),pt=pt=-1===ee.indexOf(e.PARSER_MEDIA_TYPE)?ne:e.PARSER_MEDIA_TYPE,gt="application/xhtml+xml"===pt?function(t){return t}:x,wt="ALLOWED_TAGS"in e?M({},e.ALLOWED_TAGS,gt):Tt,Et="ALLOWED_ATTR"in e?M({},e.ALLOWED_ATTR,gt):Ct,Gt="ADD_URI_SAFE_ATTR"in e?M(N(Xt),e.ADD_URI_SAFE_ATTR,gt):Xt,Ht="ADD_DATA_URI_TAGS"in e?M(N(Vt),e.ADD_DATA_URI_TAGS,gt):Vt,Wt="FORBID_CONTENTS"in e?M({},e.FORBID_CONTENTS,gt):qt,At="FORBID_TAGS"in e?M({},e.FORBID_TAGS,gt):{},Mt="FORBID_ATTR"in e?M({},e.FORBID_ATTR,gt):{},$t="USE_PROFILES"in e&&e.USE_PROFILES,Nt=!1!==e.ALLOW_ARIA_ATTR,Dt=!1!==e.ALLOW_DATA_ATTR,Ot=e.ALLOW_UNKNOWN_PROTOCOLS||!1,Bt=e.SAFE_FOR_TEMPLATES||!1,Lt=e.WHOLE_DOCUMENT||!1,Rt=e.RETURN_DOM||!1,Pt=e.RETURN_DOM_FRAGMENT||!1,jt=e.RETURN_TRUSTED_TYPE||!1,Ft=e.FORCE_BODY||!1,zt=!1!==e.SANITIZE_DOM,Yt=!1!==e.KEEP_CONTENT,Ut=e.IN_PLACE||!1,kt=e.ALLOWED_URI_REGEXP||kt,Jt=e.NAMESPACE||Kt,e.CUSTOM_ELEMENT_HANDLING&&ae(e.CUSTOM_ELEMENT_HANDLING.tagNameCheck)&&(St.tagNameCheck=e.CUSTOM_ELEMENT_HANDLING.tagNameCheck),e.CUSTOM_ELEMENT_HANDLING&&ae(e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck)&&(St.attributeNameCheck=e.CUSTOM_ELEMENT_HANDLING.attributeNameCheck),e.CUSTOM_ELEMENT_HANDLING&&"boolean"==typeof e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements&&(St.allowCustomizedBuiltInElements=e.CUSTOM_ELEMENT_HANDLING.allowCustomizedBuiltInElements),Bt&&(Dt=!1),Pt&&(Rt=!0),$t&&(wt=M({},i(P)),Et=[],!0===$t.html&&(M(wt,O),M(Et,j)),!0===$t.svg&&(M(wt,B),M(Et,z),M(Et,U)),!0===$t.svgFilters&&(M(wt,L),M(Et,z),M(Et,U)),!0===$t.mathMl&&(M(wt,F),M(Et,Y),M(Et,U))),e.ADD_TAGS&&(wt===Tt&&(wt=N(wt)),M(wt,e.ADD_TAGS,gt)),e.ADD_ATTR&&(Et===Ct&&(Et=N(Et)),M(Et,e.ADD_ATTR,gt)),e.ADD_URI_SAFE_ATTR&&M(Gt,e.ADD_URI_SAFE_ATTR,gt),e.FORBID_CONTENTS&&(Wt===qt&&(Wt=N(Wt)),M(Wt,e.FORBID_CONTENTS,gt)),Yt&&(wt["#text"]=!0),Lt&&M(wt,["html","head","body"]),wt.table&&(M(wt,["tbody"]),delete At.tbody),h&&h(e),re=e)},se=M({},["mi","mo","mn","ms","mtext"]),ce=M({},["foreignobject","desc","title","annotation-xml"]),le=M({},["title","style","font","a","script"]),ue=M({},B);M(ue,L),M(ue,I);var he=M({},F);M(he,R);var fe=function(t){var e=nt(t);e&&e.tagName||(e={namespaceURI:Kt,tagName:"template"});var n=x(t.tagName),r=x(e.tagName);return t.namespaceURI===Qt?e.namespaceURI===Kt?"svg"===n:e.namespaceURI===Zt?"svg"===n&&("annotation-xml"===r||se[r]):Boolean(ue[n]):t.namespaceURI===Zt?e.namespaceURI===Kt?"math"===n:e.namespaceURI===Qt?"math"===n&&ce[r]:Boolean(he[n]):t.namespaceURI===Kt&&!(e.namespaceURI===Qt&&!ce[r])&&!(e.namespaceURI===Zt&&!se[r])&&!he[n]&&(le[n]||!ue[n])},de=function(t){_(r.removed,{element:t});try{t.parentNode.removeChild(t)}catch(e){try{t.outerHTML=at}catch(e){t.remove()}}},pe=function(t,e){try{_(r.removed,{attribute:e.getAttributeNode(t),from:e})}catch(t){_(r.removed,{attribute:null,from:e})}if(e.removeAttribute(t),"is"===t&&!Et[t])if(Rt||Pt)try{de(e)}catch(t){}else try{e.setAttribute(t,"")}catch(t){}},ge=function(t){var e,n;if(Ft)t=""+t;else{var r=k(t,/^[\r\n\t ]+/);n=r&&r[0]}"application/xhtml+xml"===pt&&(t=''+t+"");var i=it?it.createHTML(t):t;if(Jt===Kt)try{e=(new y).parseFromString(i,pt)}catch(t){}if(!e||!e.documentElement){e=st.createDocument(Jt,"template",null);try{e.documentElement.innerHTML=te?"":i}catch(t){}}var a=e.body||e.documentElement;return t&&n&&a.insertBefore(o.createTextNode(n),a.childNodes[0]||null),Jt===Kt?ut.call(e,Lt?"html":"body")[0]:Lt?e.documentElement:a},ye=function(t){return ct.call(t.ownerDocument||t,t,f.SHOW_ELEMENT|f.SHOW_COMMENT|f.SHOW_TEXT,null,!1)},me=function(t){return t instanceof g&&("string"!=typeof t.nodeName||"string"!=typeof t.textContent||"function"!=typeof t.removeChild||!(t.attributes instanceof p)||"function"!=typeof t.removeAttribute||"function"!=typeof t.setAttribute||"string"!=typeof t.namespaceURI||"function"!=typeof t.insertBefore)},be=function(e){return"object"===t(l)?e instanceof l:e&&"object"===t(e)&&"number"==typeof e.nodeType&&"string"==typeof e.nodeName},ve=function(t,e,n){dt[t]&&b(dt[t],(function(t){t.call(r,e,n,re)}))},_e=function(t){var e;if(ve("beforeSanitizeElements",t,null),me(t))return de(t),!0;if(C(/[\u0080-\uFFFF]/,t.nodeName))return de(t),!0;var n=gt(t.nodeName);if(ve("uponSanitizeElement",t,{tagName:n,allowedTags:wt}),t.hasChildNodes()&&!be(t.firstElementChild)&&(!be(t.content)||!be(t.content.firstElementChild))&&C(/<[/\w]/g,t.innerHTML)&&C(/<[/\w]/g,t.textContent))return de(t),!0;if("select"===n&&C(/