diff --git a/.env b/.env
new file mode 100644
index 0000000..9b3ce1a
--- /dev/null
+++ b/.env
@@ -0,0 +1,5 @@
+# Default image version (override using --env-file or -e)
+APP_VERSION=dev
+
+# This is the default network of taggers-dockerized, but you can override it
+taggers_network=taggers-dockerized_taggers-network
\ No newline at end of file
diff --git a/.github/workflows/publish-dev.yml b/.github/workflows/publish-dev.yml
new file mode 100644
index 0000000..5fb2186
--- /dev/null
+++ b/.github/workflows/publish-dev.yml
@@ -0,0 +1,43 @@
+name: Development build and push
+
+# Controls when the workflow will run
+on:
+ # Triggers the workflow on push or pull request events but only for the main branch
+ push:
+ branches: [ development ]
+
+ # Allows you to run this workflow manually from the Actions tab
+ workflow_dispatch:
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+
+ build-and-push:
+ runs-on: ubuntu-latest
+ steps:
+ # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
+ - uses: actions/checkout@v2
+ # The commit-version job made a commit, but default checkout still refers to the original commit that triggered this job
+ # so make sure we are looking at the right commit
+ - run: git checkout HEAD
+ - name: Version info
+ run: |
+ sed "s~{{GITHUB_REPOSITORY}}~$GITHUB_REPOSITORY~g; s/{{VERSION_LABEL}}/dev/g; s/{{GITHUB_REF_TYPE}}/$GITHUB_REF_TYPE/g; s/{{GITHUB_REF_NAME}}/$GITHUB_REF_NAME/g; s/{{GITHUB_SHA}}/$GITHUB_SHA/g; s/{{GITHUB_WORKFLOW}}/$GITHUB_WORKFLOW/g; s/{{GITHUB_RUN_NUMBER}}/$GITHUB_RUN_NUMBER/g; s/{{BUILD_DATE}}/$(date)/g" version.yml.template > server/src/main/resources/version.yml
+ sed "s~{{GITHUB_REPOSITORY}}~$GITHUB_REPOSITORY~g; s/{{VERSION_LABEL}}/dev/g; s/{{GITHUB_REF_TYPE}}/$GITHUB_REF_TYPE/g; s/{{GITHUB_REF_NAME}}/$GITHUB_REF_NAME/g; s/{{GITHUB_SHA}}/$GITHUB_SHA/g; s/{{GITHUB_WORKFLOW}}/$GITHUB_WORKFLOW/g; s/{{GITHUB_RUN_NUMBER}}/$GITHUB_RUN_NUMBER/g; s/{{BUILD_DATE}}/$(date)/g" version.yml.template > client/version.yml
+ cat server/src/main/resources/version.yml
+
+ - name: Build
+ run: |
+ ./scripts/build-all.sh
+
+ - name: Login to Docker Hub
+ uses: docker/login-action@v1
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Push
+ run: |
+ docker push instituutnederlandsetaal/galahad-proxy:dev
+ docker push instituutnederlandsetaal/galahad-server:dev
+ docker push instituutnederlandsetaal/galahad-client:dev
diff --git a/.github/workflows/reference-test.yml b/.github/workflows/reference-test.yml
new file mode 100644
index 0000000..3412866
--- /dev/null
+++ b/.github/workflows/reference-test.yml
@@ -0,0 +1,69 @@
+# This workflow will launch the latest docker containers of Galahad server and some taggers
+# and will run the reference test against it.
+# If the test fails, it will create a branch with the changes.
+
+name: Reference Test (currently runs on LAST(!) succesful build)
+
+# Controls when the workflow will run
+on:
+ # Triggers the workflow on push or pull request events but only for the main branch
+ push:
+ branches: [ main, development ]
+ # Allows you to run this workflow manually from the Actions tab
+ workflow_dispatch:
+
+# A workflow run is made up of one or more jobs that can run sequentially or in parallel
+jobs:
+ test:
+ # The type of runner that the job will run on
+ runs-on: ubuntu-latest
+
+ # Steps represent a sequence of tasks that will be executed as part of the job
+ steps:
+ # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it
+ - uses: actions/checkout@v2
+
+ - name: Login to Docker Hub
+ uses: docker/login-action@v1
+ with:
+ username: ${{ secrets.DOCKERHUB_USERNAME }}
+ password: ${{ secrets.DOCKERHUB_TOKEN }}
+
+ - name: Pull taggers and up taggers
+ # This need to happen first because we need to connect to the tagger network.
+ run: |
+ # Make sure that these file names do not override existing files in the git repo.
+ curl -o docker-compose-taggers.yml https://${{ secrets.GHA_TOKEN }}@raw.githubusercontent.com/INL/taggers-dockerized/development/docker-compose.yml
+ curl -o .taggers.env https://${{ secrets.GHA_TOKEN }}@raw.githubusercontent.com/INL/taggers-dockerized/development/.env
+ # TODO figure out which version we actually want to get and test against.
+ docker-compose -f docker-compose-taggers.yml --env-file .taggers.env pull --quiet # TODO this might be to much, we only need the INCLUDED_TAGGERS for the test
+ docker-compose -f docker-compose-taggers.yml -p taggers-dockerized --compatibility --env-file .taggers.env up -d # TODO see if we can handle the project name more cleanly
+
+ - name: Pull and up Galahad server
+ run: |
+ docker-compose pull --quiet server # This should be the correct version
+ docker-compose up -d server
+
+ - name: Setup git
+ run: |
+ git config user.email "<>"
+ git config user.name "GHA reference action"
+
+ - name: Inspect containers
+ run: |
+ sleep 60 # give the containers some time to start up
+ # inspect containers (useful for debug)
+ docker stats --all --no-stream
+ docker-compose logs
+ docker-compose -f docker-compose-taggers.yml -p taggers-dockerized logs
+
+ - name: Clean changes
+ run: |
+ rm docker-compose-taggers.yml # Needed to keep the git clean, otherwise test refuses to run
+ rm .taggers.env
+
+ - name: Run reference test
+ run: |
+ cd server/reference-test
+ # Run the test and push the differences branch if error, and exit if error.
+ yes | ./test.sh || (echo "Test failed. See branch with differences." && git push origin $(git branch | grep test_corpus_update_) && exit 1)
diff --git a/.github/workflows/tests.yml b/.github/workflows/tests.yml
new file mode 100644
index 0000000..bc894f5
--- /dev/null
+++ b/.github/workflows/tests.yml
@@ -0,0 +1,32 @@
+name: Tests
+
+# Controls when the workflow will run
+on:
+ # Triggers the workflow on push or pull request events but only for the main branch
+ push:
+ branches: [ development ]
+
+ # Allows you to run this workflow manually from the Actions tab
+ workflow_dispatch:
+
+jobs:
+ build:
+ runs-on: ubuntu-latest
+ steps:
+ - name: Checkout sources
+ uses: actions/checkout@v4
+
+ - name: Set Java version
+ uses: actions/setup-java@v4
+ with:
+ distribution: temurin
+ java-version: 17
+
+ - name: Setup Gradle
+ uses: gradle/actions/setup-gradle@v3
+ with:
+ gradle-version: 8.6
+
+ - name: Test with Gradle
+ working-directory: ./server
+ run: ./gradlew test --info
diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..3389355
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,303 @@
+# Custom #
+##########
+
+*.cache
+
+scripts/benchmarking/*
+!scripts/benchmarking/run-benchmarks.sh
+!scripts/benchmarking/1doc_100char
+!scripts/benchmarking/1doc_10kchar
+
+server/benchmarks.yml
+server/data/corpora/assays
+server/data/corpora/custom
+
+.scannerwork/
+**/target
+**/build
+*.iml
+
+.gradle
+/build/
+
+# Ignore Gradle GUI config
+gradle-app.setting
+
+# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
+!gradle-wrapper.jar
+
+# Cache of project
+.gradletasknamecache
+
+# From DSDD #
+#############
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw*
+
+
+# Compiled source #
+###################
+*.com
+*.class
+*.dll
+*.exe
+*.o
+*.so
+
+# Packages #
+############
+# it's better to unpack these files and commit the raw source
+# git has its own built in compression methods
+*.7z
+*.dmg
+*.gz
+*.iso
+*.jar
+*.rar
+# *.tar # as the models are tar files we will keep them
+*.zip
+
+# Logs and databases #
+######################
+*.log
+*.sql
+*.sqlite
+
+# OS generated files #
+######################
+.DS_Store
+.DS_Store?
+._*
+.Spotlight-V100
+.Trashes
+ehthumbs.db
+Thumbs.db
+
+# Covers JetBrains IDEs: IntelliJ, RubyMine, PhpStorm, AppCode, PyCharm, CLion, Android Studio and WebStorm
+# Reference: https://intellij-support.jetbrains.com/hc/en-us/articles/206544839
+
+# User-specific stuff
+.idea/**/workspace.xml
+.idea/**/tasks.xml
+.idea/**/usage.statistics.xml
+.idea/**/dictionaries
+.idea/**/shelf
+
+# Generated files
+.idea/**/contentModel.xml
+
+# Sensitive or high-churn files
+.idea/**/dataSources/
+.idea/**/dataSources.ids
+.idea/**/dataSources.local.xml
+.idea/**/sqlDataSources.xml
+.idea/**/dynamic.xml
+.idea/**/uiDesigner.xml
+.idea/**/dbnavigator.xml
+
+# Gradle
+.idea/**/gradle.xml
+.idea/**/libraries
+
+# Gradle and Maven with auto-import
+# When using Gradle or Maven with auto-import, you should exclude module files,
+# since they will be recreated, and may cause churn. Uncomment if using
+# auto-import.
+# .idea/artifacts
+# .idea/compiler.xml
+# .idea/jarRepositories.xml
+# .idea/modules.xml
+# .idea/*.iml
+# .idea/modules
+# *.iml
+# *.ipr
+
+# CMake
+cmake-build-*/
+
+# Mongo Explorer plugin
+.idea/**/mongoSettings.xml
+
+# File-based project format
+*.iws
+
+# IntelliJ
+out/
+
+# mpeltonen/sbt-idea plugin
+.idea_modules/
+
+# JIRA plugin
+atlassian-ide-plugin.xml
+
+# Cursive Clojure plugin
+.idea/replstate.xml
+
+# Crashlytics plugin (for Android Studio and IntelliJ)
+com_crashlytics_export_strings.xml
+crashlytics.properties
+crashlytics-build.properties
+fabric.properties
+
+# Editor-based Rest Client
+.idea/httpRequests
+
+# Android studio 3.1+ serialized cache file
+.idea/caches/build_file_checksums.ser
+
+### Python stuff
+# Byte-compiled / optimized / DLL files
+__pycache__/
+*.py[cod]
+*$py.class
+
+# C extensions
+*.so
+
+# Distribution / packaging
+.Python
+build/
+develop-eggs/
+dist/
+downloads/
+eggs/
+.eggs/
+lib/
+lib64/
+parts/
+sdist/
+var/
+wheels/
+share/python-wheels/
+*.egg-info/
+.installed.cfg
+*.egg
+MANIFEST
+
+# PyInstaller
+# Usually these files are written by a python script from a template
+# before PyInstaller builds the exe, so as to inject date/other infos into it.
+*.manifest
+*.spec
+
+# Installer logs
+pip-log.txt
+pip-delete-this-directory.txt
+
+# Unit test / coverage reports
+htmlcov/
+.tox/
+.nox/
+.coverage
+.coverage.*
+.cache
+nosetests.xml
+coverage.xml
+*.cover
+*.py,cover
+.hypothesis/
+.pytest_cache/
+cover/
+
+# Translations
+*.mo
+*.pot
+
+# Django stuff:
+*.log
+local_settings.py
+db.sqlite3
+db.sqlite3-journal
+
+# Flask stuff:
+instance/
+.webassets-cache
+
+# Scrapy stuff:
+.scrapy
+
+# Sphinx documentation
+docs/_build/
+
+# PyBuilder
+.pybuilder/
+target/
+
+# Jupyter Notebook
+.ipynb_checkpoints
+
+# IPython
+profile_default/
+ipython_config.py
+
+# pyenv
+# For a library or package, you might want to ignore these files since the code is
+# intended to run in multiple environments; otherwise, check them in:
+# .python-version
+
+# pipenv
+# According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control.
+# However, in case of collaboration, if having platform-specific dependencies or dependencies
+# having no cross-platform support, pipenv may install dependencies that don't work, or not
+# install all needed dependencies.
+#Pipfile.lock
+
+# PEP 582; used by e.g. github.com/David-OConnor/pyflow
+__pypackages__/
+
+# Celery stuff
+celerybeat-schedule
+celerybeat.pid
+
+# SageMath parsed files
+*.sage.py
+
+# Environments
+# .env <-- we use this for docker-compose, so skip it I guess
+.venv
+env/
+venv/
+ENV/
+env.bak/
+venv.bak/
+
+# Spyder project settings
+.spyderproject
+.spyproject
+
+# Rope project settings
+.ropeproject
+
+# mkdocs documentation
+/site
+
+# mypy
+.mypy_cache/
+.dmypy.json
+dmypy.json
+
+# Pyre type checker
+.pyre/
+
+# pytype static type analyzer
+.pytype/
+
+# Cython debug symbols
+cython_debug/
+scripts/tag-matching/*/*
diff --git a/LICENSE b/LICENSE
new file mode 100644
index 0000000..261eeb9
--- /dev/null
+++ b/LICENSE
@@ -0,0 +1,201 @@
+ Apache License
+ Version 2.0, January 2004
+ http://www.apache.org/licenses/
+
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
+
+ 1. Definitions.
+
+ "License" shall mean the terms and conditions for use, reproduction,
+ and distribution as defined by Sections 1 through 9 of this document.
+
+ "Licensor" shall mean the copyright owner or entity authorized by
+ the copyright owner that is granting the License.
+
+ "Legal Entity" shall mean the union of the acting entity and all
+ other entities that control, are controlled by, or are under common
+ control with that entity. For the purposes of this definition,
+ "control" means (i) the power, direct or indirect, to cause the
+ direction or management of such entity, whether by contract or
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
+ outstanding shares, or (iii) beneficial ownership of such entity.
+
+ "You" (or "Your") shall mean an individual or Legal Entity
+ exercising permissions granted by this License.
+
+ "Source" form shall mean the preferred form for making modifications,
+ including but not limited to software source code, documentation
+ source, and configuration files.
+
+ "Object" form shall mean any form resulting from mechanical
+ transformation or translation of a Source form, including but
+ not limited to compiled object code, generated documentation,
+ and conversions to other media types.
+
+ "Work" shall mean the work of authorship, whether in Source or
+ Object form, made available under the License, as indicated by a
+ copyright notice that is included in or attached to the work
+ (an example is provided in the Appendix below).
+
+ "Derivative Works" shall mean any work, whether in Source or Object
+ form, that is based on (or derived from) the Work and for which the
+ editorial revisions, annotations, elaborations, or other modifications
+ represent, as a whole, an original work of authorship. For the purposes
+ of this License, Derivative Works shall not include works that remain
+ separable from, or merely link (or bind by name) to the interfaces of,
+ the Work and Derivative Works thereof.
+
+ "Contribution" shall mean any work of authorship, including
+ the original version of the Work and any modifications or additions
+ to that Work or Derivative Works thereof, that is intentionally
+ submitted to Licensor for inclusion in the Work by the copyright owner
+ or by an individual or Legal Entity authorized to submit on behalf of
+ the copyright owner. For the purposes of this definition, "submitted"
+ means any form of electronic, verbal, or written communication sent
+ to the Licensor or its representatives, including but not limited to
+ communication on electronic mailing lists, source code control systems,
+ and issue tracking systems that are managed by, or on behalf of, the
+ Licensor for the purpose of discussing and improving the Work, but
+ excluding communication that is conspicuously marked or otherwise
+ designated in writing by the copyright owner as "Not a Contribution."
+
+ "Contributor" shall mean Licensor and any individual or Legal Entity
+ on behalf of whom a Contribution has been received by Licensor and
+ subsequently incorporated within the Work.
+
+ 2. Grant of Copyright License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ copyright license to reproduce, prepare Derivative Works of,
+ publicly display, publicly perform, sublicense, and distribute the
+ Work and such Derivative Works in Source or Object form.
+
+ 3. Grant of Patent License. Subject to the terms and conditions of
+ this License, each Contributor hereby grants to You a perpetual,
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
+ (except as stated in this section) patent license to make, have made,
+ use, offer to sell, sell, import, and otherwise transfer the Work,
+ where such license applies only to those patent claims licensable
+ by such Contributor that are necessarily infringed by their
+ Contribution(s) alone or by combination of their Contribution(s)
+ with the Work to which such Contribution(s) was submitted. If You
+ institute patent litigation against any entity (including a
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
+ or a Contribution incorporated within the Work constitutes direct
+ or contributory patent infringement, then any patent licenses
+ granted to You under this License for that Work shall terminate
+ as of the date such litigation is filed.
+
+ 4. Redistribution. You may reproduce and distribute copies of the
+ Work or Derivative Works thereof in any medium, with or without
+ modifications, and in Source or Object form, provided that You
+ meet the following conditions:
+
+ (a) You must give any other recipients of the Work or
+ Derivative Works a copy of this License; and
+
+ (b) You must cause any modified files to carry prominent notices
+ stating that You changed the files; and
+
+ (c) You must retain, in the Source form of any Derivative Works
+ that You distribute, all copyright, patent, trademark, and
+ attribution notices from the Source form of the Work,
+ excluding those notices that do not pertain to any part of
+ the Derivative Works; and
+
+ (d) If the Work includes a "NOTICE" text file as part of its
+ distribution, then any Derivative Works that You distribute must
+ include a readable copy of the attribution notices contained
+ within such NOTICE file, excluding those notices that do not
+ pertain to any part of the Derivative Works, in at least one
+ of the following places: within a NOTICE text file distributed
+ as part of the Derivative Works; within the Source form or
+ documentation, if provided along with the Derivative Works; or,
+ within a display generated by the Derivative Works, if and
+ wherever such third-party notices normally appear. The contents
+ of the NOTICE file are for informational purposes only and
+ do not modify the License. You may add Your own attribution
+ notices within Derivative Works that You distribute, alongside
+ or as an addendum to the NOTICE text from the Work, provided
+ that such additional attribution notices cannot be construed
+ as modifying the License.
+
+ You may add Your own copyright statement to Your modifications and
+ may provide additional or different license terms and conditions
+ for use, reproduction, or distribution of Your modifications, or
+ for any such Derivative Works as a whole, provided Your use,
+ reproduction, and distribution of the Work otherwise complies with
+ the conditions stated in this License.
+
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
+ any Contribution intentionally submitted for inclusion in the Work
+ by You to the Licensor shall be under the terms and conditions of
+ this License, without any additional terms or conditions.
+ Notwithstanding the above, nothing herein shall supersede or modify
+ the terms of any separate license agreement you may have executed
+ with Licensor regarding such Contributions.
+
+ 6. Trademarks. This License does not grant permission to use the trade
+ names, trademarks, service marks, or product names of the Licensor,
+ except as required for reasonable and customary use in describing the
+ origin of the Work and reproducing the content of the NOTICE file.
+
+ 7. Disclaimer of Warranty. Unless required by applicable law or
+ agreed to in writing, Licensor provides the Work (and each
+ Contributor provides its Contributions) on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
+ implied, including, without limitation, any warranties or conditions
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
+ PARTICULAR PURPOSE. You are solely responsible for determining the
+ appropriateness of using or redistributing the Work and assume any
+ risks associated with Your exercise of permissions under this License.
+
+ 8. Limitation of Liability. In no event and under no legal theory,
+ whether in tort (including negligence), contract, or otherwise,
+ unless required by applicable law (such as deliberate and grossly
+ negligent acts) or agreed to in writing, shall any Contributor be
+ liable to You for damages, including any direct, indirect, special,
+ incidental, or consequential damages of any character arising as a
+ result of this License or out of the use or inability to use the
+ Work (including but not limited to damages for loss of goodwill,
+ work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses), even if such Contributor
+ has been advised of the possibility of such damages.
+
+ 9. Accepting Warranty or Additional Liability. While redistributing
+ the Work or Derivative Works thereof, You may choose to offer,
+ and charge a fee for, acceptance of support, warranty, indemnity,
+ or other liability obligations and/or rights consistent with this
+ License. However, in accepting such obligations, You may act only
+ on Your own behalf and on Your sole responsibility, not on behalf
+ of any other Contributor, and only if You agree to indemnify,
+ defend, and hold each Contributor harmless for any liability
+ incurred by, or claims asserted against, such Contributor by reason
+ of your accepting any such warranty or additional liability.
+
+ END OF TERMS AND CONDITIONS
+
+ APPENDIX: How to apply the Apache License to your work.
+
+ To apply the Apache License to your work, attach the following
+ boilerplate notice, with the fields enclosed by brackets "[]"
+ replaced with your own identifying information. (Don't include
+ the brackets!) The text should be enclosed in the appropriate
+ comment syntax for the file format. We also recommend that a
+ file or class name and description of purpose be included on the
+ same "printed page" as the copyright notice for easier
+ identification within third-party archives.
+
+ Copyright [yyyy] [name of copyright owner]
+
+ Licensed under the Apache License, Version 2.0 (the "License");
+ you may not use this file except in compliance with the License.
+ You may obtain a copy of the License at
+
+ http://www.apache.org/licenses/LICENSE-2.0
+
+ Unless required by applicable law or agreed to in writing, software
+ distributed under the License is distributed on an "AS IS" BASIS,
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ See the License for the specific language governing permissions and
+ limitations under the License.
diff --git a/build.sh b/build.sh
new file mode 100644
index 0000000..cb0da3e
--- /dev/null
+++ b/build.sh
@@ -0,0 +1,3 @@
+docker build -t instituutnederlandsetaal/galahad-client:dev client
+docker build -t instituutnederlandsetaal/galahad-proxy:dev proxy
+docker build -t instituutnederlandsetaal/galahad-server:dev server
diff --git a/client/.browserslistrc b/client/.browserslistrc
new file mode 100644
index 0000000..d6471a3
--- /dev/null
+++ b/client/.browserslistrc
@@ -0,0 +1,2 @@
+> 1%
+last 2 versions
diff --git a/client/.dockerignore b/client/.dockerignore
new file mode 100644
index 0000000..569ce53
--- /dev/null
+++ b/client/.dockerignore
@@ -0,0 +1,2 @@
+**/node_modules
+**/dist
\ No newline at end of file
diff --git a/client/.eslintrc.js b/client/.eslintrc.js
new file mode 100644
index 0000000..2959ed2
--- /dev/null
+++ b/client/.eslintrc.js
@@ -0,0 +1,30 @@
+module.exports = {
+ root: true,
+ env: {
+ node: true
+ },
+ 'extends': [
+ 'plugin:vue/essential',
+ 'eslint:recommended',
+ '@vue/typescript/recommended'
+ ],
+ parserOptions: {
+ ecmaVersion: 2020
+ },
+ rules: {
+ 'no-console': process.env.NODE_ENV === 'production' ? 'error' : 'off',
+ 'no-debugger': process.env.NODE_ENV === 'production' ? 'error' : 'off'
+ },
+ overrides: [
+ {
+ files: [
+ '**/__tests__/*.{j,t}s?(x)',
+ '**/tests/unit/**/*.spec.{j,t}s?(x)'
+ ],
+ env: {
+ jest: true,
+ es2021: true,
+ }
+ }
+ ]
+}
diff --git a/client/.gitignore b/client/.gitignore
new file mode 100644
index 0000000..a0dddc6
--- /dev/null
+++ b/client/.gitignore
@@ -0,0 +1,21 @@
+.DS_Store
+node_modules
+/dist
+
+# local env files
+.env.local
+.env.*.local
+
+# Log files
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# Editor directories and files
+.idea
+.vscode
+*.suo
+*.ntvs*
+*.njsproj
+*.sln
+*.sw?
diff --git a/client/Dockerfile b/client/Dockerfile
new file mode 100644
index 0000000..f90e60b
--- /dev/null
+++ b/client/Dockerfile
@@ -0,0 +1,20 @@
+# Build stage
+FROM node:21.6.2-alpine3.19 as build
+COPY . .
+# Install project dependencies (somehow Python has to be installed)
+RUN apk --no-cache add --virtual .builds-deps build-base python3
+RUN npm ci
+# Builds to dist/
+RUN npm run build
+
+# Production stage.
+FROM node:21.6.2-alpine3.19
+WORKDIR /app
+# Install dependencies of server.js
+RUN npm install express connect-history-api-fallback && npm cache clean --force
+# Copy over build & server.
+COPY --from=build /dist ./dist
+COPY . .
+
+EXPOSE 8080
+CMD [ "node", "server.js" ]
\ No newline at end of file
diff --git a/client/README.md b/client/README.md
new file mode 100644
index 0000000..f2586d6
--- /dev/null
+++ b/client/README.md
@@ -0,0 +1,23 @@
+# galahad-client
+Project setup: `npm install`
+Compiles and hot-reloads for development: `npm run dev`
+Compiles and minifies for production: `npm run build`
+Run your unit tests: `npm run test:unit`
+Lints and fixes files: `npm run lint`
+
+[Configuration Reference](https://cli.vuejs.org/config/).
+
+# Dev overview
+The following folders are found in src/
+## Types
+Typescript defined types, mostly to reflect the API response type.
+## API
+Interface to call the server.
+## Stores
+Pinia stores for application data. Retrieves data via the API and uploads data.
+## Components
+Vue components. Some core components like custom tables and buttons are prefixed with G- (short for Galahad).
+The interface is based around GTabs that can display different GCards in a tab-like interface.
+
+## Views
+Vue views. Views in the Annotate tab make use of the AnnotateTab component which handles errors like empty corpora or empty selections.
diff --git a/client/index.html b/client/index.html
new file mode 100644
index 0000000..7984cbb
--- /dev/null
+++ b/client/index.html
@@ -0,0 +1,24 @@
+
+
+
+
+
+
+
+
+
+ GaLAHaD
+
+
+
+
+ We're sorry but GaLAHaD doesn't work properly without JavaScript enabled. Please enable it to
+ continue.
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/jest.config.js b/client/jest.config.js
new file mode 100644
index 0000000..8b359f8
--- /dev/null
+++ b/client/jest.config.js
@@ -0,0 +1,3 @@
+module.exports = {
+ preset: '@vue/cli-plugin-unit-jest/presets/typescript-and-babel'
+}
diff --git a/client/log4j2.xml b/client/log4j2.xml
new file mode 100644
index 0000000..8e59ea5
--- /dev/null
+++ b/client/log4j2.xml
@@ -0,0 +1,32 @@
+
+
+
+ /vol1/weblogs
+
+
+
+
+ [%date{dd/MM/yyyy:HH:mm:ss Z}] %threadName %-5level %msg%n
+
+
+
+
+ [%date{dd/MM/yyyy:HH:mm:ss Z}] %threadName %-5level %msg%n
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/nginx.conf b/client/nginx.conf
new file mode 100644
index 0000000..6f61d6c
--- /dev/null
+++ b/client/nginx.conf
@@ -0,0 +1,30 @@
+user nginx;
+worker_processes 1;
+error_log /var/log/nginx/error.log warn;
+pid /var/run/nginx.pid;
+events {
+ worker_connections 1024;
+}
+http {
+ include /etc/nginx/mime.types;
+ default_type application/octet-stream;
+ log_format main '$remote_addr - $remote_user [$time_local] "$request" '
+ '$status $body_bytes_sent "$http_referer" '
+ '"$http_user_agent" "$http_x_forwarded_for"';
+ access_log /var/log/nginx/access.log main;
+ sendfile on;
+ keepalive_timeout 65;
+ server {
+ listen 80;
+ server_name localhost;
+ location / {
+ root /app;
+ index index.html;
+ try_files $uri $uri/ /index.html;
+ }
+ error_page 500 502 503 504 /50x.html;
+ location = /50x.html {
+ root /usr/share/nginx/html;
+ }
+ }
+}
diff --git a/client/package-lock.json b/client/package-lock.json
new file mode 100644
index 0000000..e2ed835
--- /dev/null
+++ b/client/package-lock.json
@@ -0,0 +1,5444 @@
+{
+ "name": "galahad-client",
+ "version": "0.0.2-ALPHA-SNAPSHOT",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "galahad-client",
+ "version": "0.0.2-ALPHA-SNAPSHOT",
+ "dependencies": {
+ "@types/uuid": "^8.3.4",
+ "@vitejs/plugin-vue": "^5.0.4",
+ "axios": "^1.6.2",
+ "buffer": "^6.0.3",
+ "content-disposition": "^0.5.4",
+ "js-yaml": "^4.1.0",
+ "json-loader": "^0.5.7",
+ "pinia": "^2.0.16",
+ "safe-buffer": "^5.2.1",
+ "uuid": "^8.3.2",
+ "vue": "^3.2.37",
+ "vue-router": "^4.1.2",
+ "vue-slider-component": "^4.1.0-beta.1"
+ },
+ "devDependencies": {
+ "@rollup/plugin-yaml": "^4.0.1",
+ "@types/jest": "^28.1.6",
+ "@types/js-yaml": "^4.0.5",
+ "@typescript-eslint/eslint-plugin": "^5.30.7",
+ "@typescript-eslint/parser": "^5.30.7",
+ "@vue/eslint-config-typescript": "^11.0.0",
+ "eslint": "^8.20.0",
+ "eslint-plugin-vue": "^9.2.0",
+ "mutationobserver-shim": "^0.3.7",
+ "node-sass": "^9.0.0",
+ "sass": "^1.53.0",
+ "typescript": "^4.7.4",
+ "vite": "^5.1.7"
+ }
+ },
+ "node_modules/@aashutoshrathi/word-wrap": {
+ "version": "1.2.6",
+ "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz",
+ "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.23.5",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.23.5.tgz",
+ "integrity": "sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==",
+ "dev": true,
+ "dependencies": {
+ "@babel/highlight": "^7.23.4",
+ "chalk": "^2.4.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "dev": true
+ },
+ "node_modules/@babel/code-frame/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/code-frame/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.22.20",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.20.tgz",
+ "integrity": "sha512-Y4OZ+ytlatR8AI+8KZfKuL5urKp7qey08ha31L8b3BwewJAoJamTzyvxPR/5D+KkdJCGPq/+8TukHBlY10FX9A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.23.4",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.23.4.tgz",
+ "integrity": "sha512-acGdbYSfp2WheJoJm/EBBBLh/ID8KDc64ISZ9DYtBmC8/Q204PZJLHyzeB5qMzJ5trcOkybd78M4x2KWsUq++A==",
+ "dev": true,
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.22.20",
+ "chalk": "^2.4.2",
+ "js-tokens": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==",
+ "dev": true
+ },
+ "node_modules/@babel/highlight/node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/highlight/node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.24.0",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.24.0.tgz",
+ "integrity": "sha512-QuP/FxEAzMSjXygs8v4N9dvdXzEHN4W1oF3PxuWAtPo08UdM17u89RDMgjLn/mlc56iM0HlLmVkO/wgR+rDgHg==",
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@esbuild/aix-ppc64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/aix-ppc64/-/aix-ppc64-0.19.12.tgz",
+ "integrity": "sha512-bmoCYyWdEL3wDQIVbcyzRyeKLgk2WtWLTWz1ZIAZF/EGbNOwSA6ew3PftJ1PqMiOOGu0OyFMzG53L0zqIpPeNA==",
+ "cpu": [
+ "ppc64"
+ ],
+ "optional": true,
+ "os": [
+ "aix"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.19.12.tgz",
+ "integrity": "sha512-qg/Lj1mu3CdQlDEEiWrlC4eaPZ1KztwGJ9B6J+/6G+/4ewxJg7gqj8eVYWvao1bXrqGiW2rsBZFSX3q2lcW05w==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-arm64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.19.12.tgz",
+ "integrity": "sha512-P0UVNGIienjZv3f5zq0DP3Nt2IE/3plFzuaS96vihvD0Hd6H/q4WXUGpCxD/E8YrSXfNyRPbpTq+T8ZQioSuPA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/android-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.19.12.tgz",
+ "integrity": "sha512-3k7ZoUW6Q6YqhdhIaq/WZ7HwBpnFBlW905Fa4s4qWJyiNOgT1dOqDiVAQFwBH7gBRZr17gLrlFCRzF6jFh7Kew==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-arm64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.19.12.tgz",
+ "integrity": "sha512-B6IeSgZgtEzGC42jsI+YYu9Z3HKRxp8ZT3cqhvliEHovq8HSX2YX8lNocDn79gCKJXOSaEot9MVYky7AKjCs8g==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/darwin-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.19.12.tgz",
+ "integrity": "sha512-hKoVkKzFiToTgn+41qGhsUJXFlIjxI/jSYeZf3ugemDYZldIXIxhvwN6erJGlX4t5h417iFuheZ7l+YVn05N3A==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-arm64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.19.12.tgz",
+ "integrity": "sha512-4aRvFIXmwAcDBw9AueDQ2YnGmz5L6obe5kmPT8Vd+/+x/JMVKCgdcRwH6APrbpNXsPz+K653Qg8HB/oXvXVukA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/freebsd-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.19.12.tgz",
+ "integrity": "sha512-EYoXZ4d8xtBoVN7CEwWY2IN4ho76xjYXqSXMNccFSx2lgqOG/1TBPW0yPx1bJZk94qu3tX0fycJeeQsKovA8gg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "freebsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.19.12.tgz",
+ "integrity": "sha512-J5jPms//KhSNv+LO1S1TX1UWp1ucM6N6XuL6ITdKWElCu8wXP72l9MM0zDTzzeikVyqFE6U8YAV9/tFyj0ti+w==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-arm64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.19.12.tgz",
+ "integrity": "sha512-EoTjyYyLuVPfdPLsGVVVC8a0p1BFFvtpQDB/YLEhaXyf/5bczaGeN15QkR+O4S5LeJ92Tqotve7i1jn35qwvdA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ia32": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.19.12.tgz",
+ "integrity": "sha512-Thsa42rrP1+UIGaWz47uydHSBOgTUnwBwNq59khgIwktK6x60Hivfbux9iNR0eHCHzOLjLMLfUMLCypBkZXMHA==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-loong64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.19.12.tgz",
+ "integrity": "sha512-LiXdXA0s3IqRRjm6rV6XaWATScKAXjI4R4LoDlvO7+yQqFdlr1Bax62sRwkVvRIrwXxvtYEHHI4dm50jAXkuAA==",
+ "cpu": [
+ "loong64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-mips64el": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.19.12.tgz",
+ "integrity": "sha512-fEnAuj5VGTanfJ07ff0gOA6IPsvrVHLVb6Lyd1g2/ed67oU1eFzL0r9WL7ZzscD+/N6i3dWumGE1Un4f7Amf+w==",
+ "cpu": [
+ "mips64el"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-ppc64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.19.12.tgz",
+ "integrity": "sha512-nYJA2/QPimDQOh1rKWedNOe3Gfc8PabU7HT3iXWtNUbRzXS9+vgB0Fjaqr//XNbd82mCxHzik2qotuI89cfixg==",
+ "cpu": [
+ "ppc64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-riscv64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.19.12.tgz",
+ "integrity": "sha512-2MueBrlPQCw5dVJJpQdUYgeqIzDQgw3QtiAHUC4RBz9FXPrskyyU3VI1hw7C0BSKB9OduwSJ79FTCqtGMWqJHg==",
+ "cpu": [
+ "riscv64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-s390x": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.19.12.tgz",
+ "integrity": "sha512-+Pil1Nv3Umes4m3AZKqA2anfhJiVmNCYkPchwFJNEJN5QxmTs1uzyy4TvmDrCRNT2ApwSari7ZIgrPeUx4UZDg==",
+ "cpu": [
+ "s390x"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/linux-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.19.12.tgz",
+ "integrity": "sha512-B71g1QpxfwBvNrfyJdVDexenDIt1CiDN1TIXLbhOw0KhJzE78KIFGX6OJ9MrtC0oOqMWf+0xop4qEU8JrJTwCg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/netbsd-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.19.12.tgz",
+ "integrity": "sha512-3ltjQ7n1owJgFbuC61Oj++XhtzmymoCihNFgT84UAmJnxJfm4sYCiSLTXZtE00VWYpPMYc+ZQmB6xbSdVh0JWA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "netbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/openbsd-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.19.12.tgz",
+ "integrity": "sha512-RbrfTB9SWsr0kWmb9srfF+L933uMDdu9BIzdA7os2t0TXhCRjrQyCeOt6wVxr79CKD4c+p+YhCj31HBkYcXebw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "openbsd"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/sunos-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.19.12.tgz",
+ "integrity": "sha512-HKjJwRrW8uWtCQnQOz9qcU3mUZhTUQvi56Q8DPTLLB+DawoiQdjsYq+j+D3s9I8VFtDr+F9CjgXKKC4ss89IeA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "sunos"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-arm64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.19.12.tgz",
+ "integrity": "sha512-URgtR1dJnmGvX864pn1B2YUYNzjmXkuJOIqG2HdU62MVS4EHpU2946OZoTMnRUHklGtJdJZ33QfzdjGACXhn1A==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-ia32": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.19.12.tgz",
+ "integrity": "sha512-+ZOE6pUkMOJfmxmBZElNOx72NKpIa/HFOMGzu8fqzQJ5kgf6aTGrcJaFsNiVMH4JKpMipyK+7k0n2UXN7a8YKQ==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@esbuild/win32-x64": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.19.12.tgz",
+ "integrity": "sha512-T1QyPSDCyMXaO3pzBkF96E8xMkiRYbUEZADd29SyPGabqxMViNoii+NcK7eWJAEoU6RZyEm5lVSIjTmcdoB9HA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ],
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/@eslint-community/eslint-utils": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz",
+ "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==",
+ "dev": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0"
+ }
+ },
+ "node_modules/@eslint-community/regexpp": {
+ "version": "4.10.0",
+ "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.10.0.tgz",
+ "integrity": "sha512-Cu96Sd2By9mCNTx2iyKOmq10v22jUVQv0lQnlGNy16oE9589yE+QADPbrMGCkA51cKZSg3Pu/aTJVTGfL/qjUA==",
+ "dev": true,
+ "engines": {
+ "node": "^12.0.0 || ^14.0.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "2.1.4",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz",
+ "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==",
+ "dev": true,
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.6.0",
+ "globals": "^13.19.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/js": {
+ "version": "8.57.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.0.tgz",
+ "integrity": "sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@gar/promisify": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/@gar/promisify/-/promisify-1.1.3.tgz",
+ "integrity": "sha512-k2Ty1JcVojjJFwrg/ThKi2ujJ7XNLYaFGNB/bWT9wGR+oSMJHMa5w+CUq6p/pVrKeNNgA7pCqEcjSnHVoqJQFw==",
+ "dev": true
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.11.14",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.14.tgz",
+ "integrity": "sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==",
+ "dev": true,
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^2.0.2",
+ "debug": "^4.3.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.2.tgz",
+ "integrity": "sha512-6EwiSjwWYP7pTckG6I5eyFANjPhmPjUX9JRLUSfNPC7FX7zK9gyZAfUEaECL6ALTpGX5AjnBq3C9XmVWPitNpw==",
+ "dev": true
+ },
+ "node_modules/@jest/expect-utils": {
+ "version": "28.1.3",
+ "resolved": "https://registry.npmjs.org/@jest/expect-utils/-/expect-utils-28.1.3.tgz",
+ "integrity": "sha512-wvbi9LUrHJLn3NlDW6wF2hvIMtd4JUl2QNVrjq+IBSHirgfrR3o9RnVtxzdEGO2n9JyIWwHnLfby5KzqBGg2YA==",
+ "dev": true,
+ "dependencies": {
+ "jest-get-type": "^28.0.2"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
+ }
+ },
+ "node_modules/@jest/schemas": {
+ "version": "28.1.3",
+ "resolved": "https://registry.npmjs.org/@jest/schemas/-/schemas-28.1.3.tgz",
+ "integrity": "sha512-/l/VWsdt/aBXgjshLWOFyFt3IVdYypu5y2Wn2rOO1un6nkqIn8SLXzgIMYXFyYsRWDyF5EthmKJMIdJvk08grg==",
+ "dev": true,
+ "dependencies": {
+ "@sinclair/typebox": "^0.24.1"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
+ }
+ },
+ "node_modules/@jest/types": {
+ "version": "28.1.3",
+ "resolved": "https://registry.npmjs.org/@jest/types/-/types-28.1.3.tgz",
+ "integrity": "sha512-RyjiyMUZrKz/c+zlMFO1pm70DcIlST8AeWTkoUdZevew44wcNZQHsEVOiCVtgVnlFFD82FPaXycys58cf2muVQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^28.1.3",
+ "@types/istanbul-lib-coverage": "^2.0.0",
+ "@types/istanbul-reports": "^3.0.0",
+ "@types/node": "*",
+ "@types/yargs": "^17.0.8",
+ "chalk": "^4.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.15",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz",
+ "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg=="
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@npmcli/fs": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-2.1.2.tgz",
+ "integrity": "sha512-yOJKRvohFOaLqipNtwYB9WugyZKhC/DZC4VYPmpaCzDBrA8YpK3qHZ8/HGscMnE4GqbkLNuVcCnxkeQEdGt6LQ==",
+ "dev": true,
+ "dependencies": {
+ "@gar/promisify": "^1.1.3",
+ "semver": "^7.3.5"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@npmcli/move-file": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-2.0.1.tgz",
+ "integrity": "sha512-mJd2Z5TjYWq/ttPLLGqArdtnC74J6bOzg4rMDnN+p1xTacZ2yPRCk2y0oSWQtygLR9YVQXgOcONrwtnk3JupxQ==",
+ "deprecated": "This functionality has been moved to @npmcli/fs",
+ "dev": true,
+ "dependencies": {
+ "mkdirp": "^1.0.4",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/@rollup/plugin-yaml": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/@rollup/plugin-yaml/-/plugin-yaml-4.1.2.tgz",
+ "integrity": "sha512-RpupciIeZMUqhgFE97ba0s98mOFS7CWzN3EJNhJkqSv9XLlWYtwVdtE6cDw6ASOF/sZVFS7kRJXftaqM2Vakdw==",
+ "dev": true,
+ "dependencies": {
+ "@rollup/pluginutils": "^5.0.1",
+ "js-yaml": "^4.1.0",
+ "tosource": "^2.0.0-alpha.3"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/pluginutils": {
+ "version": "5.1.0",
+ "resolved": "https://registry.npmjs.org/@rollup/pluginutils/-/pluginutils-5.1.0.tgz",
+ "integrity": "sha512-XTIWOPPcpvyKI6L1NHo0lFlCyznUEyPmPY1mc3KpPVDYulHSTvyeLNVW00QTLIAFNhR3kYnJTQHeGqU4M3n09g==",
+ "dev": true,
+ "dependencies": {
+ "@types/estree": "^1.0.0",
+ "estree-walker": "^2.0.2",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "rollup": "^1.20.0||^2.0.0||^3.0.0||^4.0.0"
+ },
+ "peerDependenciesMeta": {
+ "rollup": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@rollup/rollup-android-arm-eabi": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm-eabi/-/rollup-android-arm-eabi-4.12.0.tgz",
+ "integrity": "sha512-+ac02NL/2TCKRrJu2wffk1kZ+RyqxVUlbjSagNgPm94frxtr+XDL12E5Ll1enWskLrtrZ2r8L3wED1orIibV/w==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-android-arm64": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-android-arm64/-/rollup-android-arm64-4.12.0.tgz",
+ "integrity": "sha512-OBqcX2BMe6nvjQ0Nyp7cC90cnumt8PXmO7Dp3gfAju/6YwG0Tj74z1vKrfRz7qAv23nBcYM8BCbhrsWqO7PzQQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "android"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-arm64": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-arm64/-/rollup-darwin-arm64-4.12.0.tgz",
+ "integrity": "sha512-X64tZd8dRE/QTrBIEs63kaOBG0b5GVEd3ccoLtyf6IdXtHdh8h+I56C2yC3PtC9Ucnv0CpNFJLqKFVgCYe0lOQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-darwin-x64": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-darwin-x64/-/rollup-darwin-x64-4.12.0.tgz",
+ "integrity": "sha512-cc71KUZoVbUJmGP2cOuiZ9HSOP14AzBAThn3OU+9LcA1+IUqswJyR1cAJj3Mg55HbjZP6OLAIscbQsQLrpgTOg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "darwin"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm-gnueabihf": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm-gnueabihf/-/rollup-linux-arm-gnueabihf-4.12.0.tgz",
+ "integrity": "sha512-a6w/Y3hyyO6GlpKL2xJ4IOh/7d+APaqLYdMf86xnczU3nurFTaVN9s9jOXQg97BE4nYm/7Ga51rjec5nfRdrvA==",
+ "cpu": [
+ "arm"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-gnu": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-gnu/-/rollup-linux-arm64-gnu-4.12.0.tgz",
+ "integrity": "sha512-0fZBq27b+D7Ar5CQMofVN8sggOVhEtzFUwOwPppQt0k+VR+7UHMZZY4y+64WJ06XOhBTKXtQB/Sv0NwQMXyNAA==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-arm64-musl": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-arm64-musl/-/rollup-linux-arm64-musl-4.12.0.tgz",
+ "integrity": "sha512-eTvzUS3hhhlgeAv6bfigekzWZjaEX9xP9HhxB0Dvrdbkk5w/b+1Sxct2ZuDxNJKzsRStSq1EaEkVSEe7A7ipgQ==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-riscv64-gnu": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-riscv64-gnu/-/rollup-linux-riscv64-gnu-4.12.0.tgz",
+ "integrity": "sha512-ix+qAB9qmrCRiaO71VFfY8rkiAZJL8zQRXveS27HS+pKdjwUfEhqo2+YF2oI+H/22Xsiski+qqwIBxVewLK7sw==",
+ "cpu": [
+ "riscv64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-gnu": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-gnu/-/rollup-linux-x64-gnu-4.12.0.tgz",
+ "integrity": "sha512-TenQhZVOtw/3qKOPa7d+QgkeM6xY0LtwzR8OplmyL5LrgTWIXpTQg2Q2ycBf8jm+SFW2Wt/DTn1gf7nFp3ssVA==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-linux-x64-musl": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-linux-x64-musl/-/rollup-linux-x64-musl-4.12.0.tgz",
+ "integrity": "sha512-LfFdRhNnW0zdMvdCb5FNuWlls2WbbSridJvxOvYWgSBOYZtgBfW9UGNJG//rwMqTX1xQE9BAodvMH9tAusKDUw==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "linux"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-arm64-msvc": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-arm64-msvc/-/rollup-win32-arm64-msvc-4.12.0.tgz",
+ "integrity": "sha512-JPDxovheWNp6d7AHCgsUlkuCKvtu3RB55iNEkaQcf0ttsDU/JZF+iQnYcQJSk/7PtT4mjjVG8N1kpwnI9SLYaw==",
+ "cpu": [
+ "arm64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-ia32-msvc": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-ia32-msvc/-/rollup-win32-ia32-msvc-4.12.0.tgz",
+ "integrity": "sha512-fjtuvMWRGJn1oZacG8IPnzIV6GF2/XG+h71FKn76OYFqySXInJtseAqdprVTDTyqPxQOG9Exak5/E9Z3+EJ8ZA==",
+ "cpu": [
+ "ia32"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@rollup/rollup-win32-x64-msvc": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/@rollup/rollup-win32-x64-msvc/-/rollup-win32-x64-msvc-4.12.0.tgz",
+ "integrity": "sha512-ZYmr5mS2wd4Dew/JjT0Fqi2NPB/ZhZ2VvPp7SmvPZb4Y1CG/LRcS6tcRo2cYU7zLK5A7cdbhWnnWmUjoI4qapg==",
+ "cpu": [
+ "x64"
+ ],
+ "optional": true,
+ "os": [
+ "win32"
+ ]
+ },
+ "node_modules/@sinclair/typebox": {
+ "version": "0.24.51",
+ "resolved": "https://registry.npmjs.org/@sinclair/typebox/-/typebox-0.24.51.tgz",
+ "integrity": "sha512-1P1OROm/rdubP5aFDSZQILU0vrLCJ4fvHt6EoqHEM+2D/G5MK3bIaymUKLit8Js9gbns5UyJnkP/TZROLw4tUA==",
+ "dev": true
+ },
+ "node_modules/@tootallnate/once": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-2.0.0.tgz",
+ "integrity": "sha512-XCuKFP5PS55gnMVu3dty8KPatLqUoy/ZYzDzAGCQ8JNFCkLXzmI7vNHCR+XpbZaMWQK/vQubr7PkYq8g470J/A==",
+ "dev": true,
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/@types/estree": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/@types/estree/-/estree-1.0.5.tgz",
+ "integrity": "sha512-/kYRxGDLWzHOB7q+wtSUQlFrtcdUccpfy+X+9iMBpHK8QLLhx2wIPYuS5DYtR9Wa/YlZAbIovy7qVdB1Aq6Lyw=="
+ },
+ "node_modules/@types/istanbul-lib-coverage": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-coverage/-/istanbul-lib-coverage-2.0.6.tgz",
+ "integrity": "sha512-2QF/t/auWm0lsy8XtKVPG19v3sSOQlJe/YHZgfjb/KBBHOGSV+J2q/S671rcq9uTBrLAXmZpqJiaQbMT+zNU1w==",
+ "dev": true
+ },
+ "node_modules/@types/istanbul-lib-report": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-lib-report/-/istanbul-lib-report-3.0.3.tgz",
+ "integrity": "sha512-NQn7AHQnk/RSLOxrBbGyJM/aVQ+pjj5HCgasFxc0K/KhoATfQ/47AyUl15I2yBUpihjmas+a+VJBOqecrFH+uA==",
+ "dev": true,
+ "dependencies": {
+ "@types/istanbul-lib-coverage": "*"
+ }
+ },
+ "node_modules/@types/istanbul-reports": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/@types/istanbul-reports/-/istanbul-reports-3.0.4.tgz",
+ "integrity": "sha512-pk2B1NWalF9toCRu6gjBzR69syFjP4Od8WRAX+0mmf9lAjCRicLOWc+ZrxZHx/0XRjotgkF9t6iaMJ+aXcOdZQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/istanbul-lib-report": "*"
+ }
+ },
+ "node_modules/@types/jest": {
+ "version": "28.1.8",
+ "resolved": "https://registry.npmjs.org/@types/jest/-/jest-28.1.8.tgz",
+ "integrity": "sha512-8TJkV++s7B6XqnDrzR1m/TT0A0h948Pnl/097veySPN67VRAgQ4gZ7n2KfJo2rVq6njQjdxU3GCCyDvAeuHoiw==",
+ "dev": true,
+ "dependencies": {
+ "expect": "^28.0.0",
+ "pretty-format": "^28.0.0"
+ }
+ },
+ "node_modules/@types/js-yaml": {
+ "version": "4.0.9",
+ "resolved": "https://registry.npmjs.org/@types/js-yaml/-/js-yaml-4.0.9.tgz",
+ "integrity": "sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==",
+ "dev": true
+ },
+ "node_modules/@types/json-schema": {
+ "version": "7.0.15",
+ "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.15.tgz",
+ "integrity": "sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==",
+ "dev": true
+ },
+ "node_modules/@types/minimist": {
+ "version": "1.2.5",
+ "resolved": "https://registry.npmjs.org/@types/minimist/-/minimist-1.2.5.tgz",
+ "integrity": "sha512-hov8bUuiLiyFPGyFPE1lwWhmzYbirOXQNNo40+y3zow8aFVTeyn3VWL0VFFfdNddA8S4Vf0Tc062rzyNr7Paag==",
+ "dev": true
+ },
+ "node_modules/@types/node": {
+ "version": "20.11.22",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.22.tgz",
+ "integrity": "sha512-/G+IxWxma6V3E+pqK1tSl2Fo1kl41pK1yeCyDsgkF9WlVAme4j5ISYM2zR11bgLFJGLN5sVK40T4RJNuiZbEjA==",
+ "devOptional": true,
+ "dependencies": {
+ "undici-types": "~5.26.4"
+ }
+ },
+ "node_modules/@types/normalize-package-data": {
+ "version": "2.4.4",
+ "resolved": "https://registry.npmjs.org/@types/normalize-package-data/-/normalize-package-data-2.4.4.tgz",
+ "integrity": "sha512-37i+OaWTh9qeK4LSHPsyRC7NahnGotNuZvjLSgcPzblpHB3rrCJxAOgI5gCdKm7coonsaX1Of0ILiTcnZjbfxA==",
+ "dev": true
+ },
+ "node_modules/@types/semver": {
+ "version": "7.5.8",
+ "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.8.tgz",
+ "integrity": "sha512-I8EUhyrgfLrcTkzV3TSsGyl1tSuPrEDzr0yd5m90UgNxQkyDXULk3b6MlQqTCpZpNtWe1K0hzclnZkTcLBe2UQ==",
+ "dev": true
+ },
+ "node_modules/@types/stack-utils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/@types/stack-utils/-/stack-utils-2.0.3.tgz",
+ "integrity": "sha512-9aEbYZ3TbYMznPdcdr3SmIrLXwC/AKZXQeCf9Pgao5CKb8CyHuEX5jzWPTkvregvhRJHcpRO6BFoGW9ycaOkYw==",
+ "dev": true
+ },
+ "node_modules/@types/uuid": {
+ "version": "8.3.4",
+ "resolved": "https://registry.npmjs.org/@types/uuid/-/uuid-8.3.4.tgz",
+ "integrity": "sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw=="
+ },
+ "node_modules/@types/yargs": {
+ "version": "17.0.32",
+ "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz",
+ "integrity": "sha512-xQ67Yc/laOG5uMfX/093MRlGGCIBzZMarVa+gfNKJxWAIgykYpVGkBdbqEzGDDfCrVUj6Hiff4mTZ5BA6TmAog==",
+ "dev": true,
+ "dependencies": {
+ "@types/yargs-parser": "*"
+ }
+ },
+ "node_modules/@types/yargs-parser": {
+ "version": "21.0.3",
+ "resolved": "https://registry.npmjs.org/@types/yargs-parser/-/yargs-parser-21.0.3.tgz",
+ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==",
+ "dev": true
+ },
+ "node_modules/@typescript-eslint/eslint-plugin": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.62.0.tgz",
+ "integrity": "sha512-TiZzBSJja/LbhNPvk6yc0JrX9XqhQ0hdh6M2svYfsHGejaKFIAGd9MQ+ERIMzLGlN/kZoYIgdxFV0PuljTKXag==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/regexpp": "^4.4.0",
+ "@typescript-eslint/scope-manager": "5.62.0",
+ "@typescript-eslint/type-utils": "5.62.0",
+ "@typescript-eslint/utils": "5.62.0",
+ "debug": "^4.3.4",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "natural-compare-lite": "^1.4.0",
+ "semver": "^7.3.7",
+ "tsutils": "^3.21.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "@typescript-eslint/parser": "^5.0.0",
+ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/parser": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-5.62.0.tgz",
+ "integrity": "sha512-VlJEV0fOQ7BExOsHYAGrgbEiZoi8D+Bl2+f6V2RrXerRSylnp+ZBHmPvaIa8cz0Ajx7WO7Z5RqfgYg7ED1nRhA==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/scope-manager": "5.62.0",
+ "@typescript-eslint/types": "5.62.0",
+ "@typescript-eslint/typescript-estree": "5.62.0",
+ "debug": "^4.3.4"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/scope-manager": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.62.0.tgz",
+ "integrity": "sha512-VXuvVvZeQCQb5Zgf4HAxc04q5j+WrNAtNh9OwCsCgpKqESMTu3tF/jhZ3xG6T4NZwWl65Bg8KuS2uEvhSfLl0w==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.62.0",
+ "@typescript-eslint/visitor-keys": "5.62.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/type-utils": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.62.0.tgz",
+ "integrity": "sha512-xsSQreu+VnfbqQpW5vnCJdq1Z3Q0U31qiWmRhr98ONQmcp/yhiPJFPq8MXiJVLiksmOKSjIldZzkebzHuCGzew==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/typescript-estree": "5.62.0",
+ "@typescript-eslint/utils": "5.62.0",
+ "debug": "^4.3.4",
+ "tsutils": "^3.21.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/types": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.62.0.tgz",
+ "integrity": "sha512-87NVngcbVXUahrRTqIK27gD2t5Cu1yuCXxbLcFtCzZGlfyVWWh8mLHkoxzjsB6DDNnvdL+fW8MiwPEJyGJQDgQ==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@typescript-eslint/typescript-estree": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.62.0.tgz",
+ "integrity": "sha512-CmcQ6uY7b9y694lKdRB8FEel7JbU/40iSAPomu++SjLMntB+2Leay2LO6i8VnJk58MtE9/nQSFIH6jpyRWyYzA==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.62.0",
+ "@typescript-eslint/visitor-keys": "5.62.0",
+ "debug": "^4.3.4",
+ "globby": "^11.1.0",
+ "is-glob": "^4.0.3",
+ "semver": "^7.3.7",
+ "tsutils": "^3.21.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@typescript-eslint/utils": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.62.0.tgz",
+ "integrity": "sha512-n8oxjeb5aIbPFEtmQxQYOLI0i9n5ySBEY/ZEHHZqKQSFnxio1rv6dthascc9dLuwrL0RC5mPCxB7vnAVGAYWAQ==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@types/json-schema": "^7.0.9",
+ "@types/semver": "^7.3.12",
+ "@typescript-eslint/scope-manager": "5.62.0",
+ "@typescript-eslint/types": "5.62.0",
+ "@typescript-eslint/typescript-estree": "5.62.0",
+ "eslint-scope": "^5.1.1",
+ "semver": "^7.3.7"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ },
+ "peerDependencies": {
+ "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/@typescript-eslint/visitor-keys": {
+ "version": "5.62.0",
+ "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.62.0.tgz",
+ "integrity": "sha512-07ny+LHRzQXepkGg6w0mFY41fVUNBrL2Roj/++7V1txKugfjm/Ci/qSND03r2RhlJhJYMcTn9AhhSSqQp0Ysyw==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/types": "5.62.0",
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/typescript-eslint"
+ }
+ },
+ "node_modules/@ungap/structured-clone": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
+ "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==",
+ "dev": true
+ },
+ "node_modules/@vitejs/plugin-vue": {
+ "version": "5.0.4",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-5.0.4.tgz",
+ "integrity": "sha512-WS3hevEszI6CEVEx28F8RjTX97k3KsrcY6kvTg7+Whm5y3oYvcqzVeGCU3hxSAn4uY2CLCkeokkGKpoctccilQ==",
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "peerDependencies": {
+ "vite": "^5.0.0",
+ "vue": "^3.2.25"
+ }
+ },
+ "node_modules/@vue/compiler-core": {
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.21.tgz",
+ "integrity": "sha512-MjXawxZf2SbZszLPYxaFCjxfibYrzr3eYbKxwpLR9EQN+oaziSu3qKVbwBERj1IFIB8OLUewxB5m/BFzi613og==",
+ "dependencies": {
+ "@babel/parser": "^7.23.9",
+ "@vue/shared": "3.4.21",
+ "entities": "^4.5.0",
+ "estree-walker": "^2.0.2",
+ "source-map-js": "^1.0.2"
+ }
+ },
+ "node_modules/@vue/compiler-dom": {
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.4.21.tgz",
+ "integrity": "sha512-IZC6FKowtT1sl0CR5DpXSiEB5ayw75oT2bma1BEhV7RRR1+cfwLrxc2Z8Zq/RGFzJ8w5r9QtCOvTjQgdn0IKmA==",
+ "dependencies": {
+ "@vue/compiler-core": "3.4.21",
+ "@vue/shared": "3.4.21"
+ }
+ },
+ "node_modules/@vue/compiler-sfc": {
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.4.21.tgz",
+ "integrity": "sha512-me7epoTxYlY+2CUM7hy9PCDdpMPfIwrOvAXud2Upk10g4YLv9UBW7kL798TvMeDhPthkZ0CONNrK2GoeI1ODiQ==",
+ "dependencies": {
+ "@babel/parser": "^7.23.9",
+ "@vue/compiler-core": "3.4.21",
+ "@vue/compiler-dom": "3.4.21",
+ "@vue/compiler-ssr": "3.4.21",
+ "@vue/shared": "3.4.21",
+ "estree-walker": "^2.0.2",
+ "magic-string": "^0.30.7",
+ "postcss": "^8.4.35",
+ "source-map-js": "^1.0.2"
+ }
+ },
+ "node_modules/@vue/compiler-ssr": {
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.4.21.tgz",
+ "integrity": "sha512-M5+9nI2lPpAsgXOGQobnIueVqc9sisBFexh5yMIMRAPYLa7+5wEJs8iqOZc1WAa9WQbx9GR2twgznU8LTIiZ4Q==",
+ "dependencies": {
+ "@vue/compiler-dom": "3.4.21",
+ "@vue/shared": "3.4.21"
+ }
+ },
+ "node_modules/@vue/devtools-api": {
+ "version": "6.6.1",
+ "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.6.1.tgz",
+ "integrity": "sha512-LgPscpE3Vs0x96PzSSB4IGVSZXZBZHpfxs+ZA1d+VEPwHdOXowy/Y2CsvCAIFrf+ssVU1pD1jidj505EpUnfbA=="
+ },
+ "node_modules/@vue/eslint-config-typescript": {
+ "version": "11.0.3",
+ "resolved": "https://registry.npmjs.org/@vue/eslint-config-typescript/-/eslint-config-typescript-11.0.3.tgz",
+ "integrity": "sha512-dkt6W0PX6H/4Xuxg/BlFj5xHvksjpSlVjtkQCpaYJBIEuKj2hOVU7r+TIe+ysCwRYFz/lGqvklntRkCAibsbPw==",
+ "dev": true,
+ "dependencies": {
+ "@typescript-eslint/eslint-plugin": "^5.59.1",
+ "@typescript-eslint/parser": "^5.59.1",
+ "vue-eslint-parser": "^9.1.1"
+ },
+ "engines": {
+ "node": "^14.17.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0",
+ "eslint-plugin-vue": "^9.0.0",
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/@vue/reactivity": {
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.4.21.tgz",
+ "integrity": "sha512-UhenImdc0L0/4ahGCyEzc/pZNwVgcglGy9HVzJ1Bq2Mm9qXOpP8RyNTjookw/gOCUlXSEtuZ2fUg5nrHcoqJcw==",
+ "dependencies": {
+ "@vue/shared": "3.4.21"
+ }
+ },
+ "node_modules/@vue/runtime-core": {
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.4.21.tgz",
+ "integrity": "sha512-pQthsuYzE1XcGZznTKn73G0s14eCJcjaLvp3/DKeYWoFacD9glJoqlNBxt3W2c5S40t6CCcpPf+jG01N3ULyrA==",
+ "dependencies": {
+ "@vue/reactivity": "3.4.21",
+ "@vue/shared": "3.4.21"
+ }
+ },
+ "node_modules/@vue/runtime-dom": {
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.4.21.tgz",
+ "integrity": "sha512-gvf+C9cFpevsQxbkRBS1NpU8CqxKw0ebqMvLwcGQrNpx6gqRDodqKqA+A2VZZpQ9RpK2f9yfg8VbW/EpdFUOJw==",
+ "dependencies": {
+ "@vue/runtime-core": "3.4.21",
+ "@vue/shared": "3.4.21",
+ "csstype": "^3.1.3"
+ }
+ },
+ "node_modules/@vue/server-renderer": {
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.4.21.tgz",
+ "integrity": "sha512-aV1gXyKSN6Rz+6kZ6kr5+Ll14YzmIbeuWe7ryJl5muJ4uwSwY/aStXTixx76TwkZFJLm1aAlA/HSWEJ4EyiMkg==",
+ "dependencies": {
+ "@vue/compiler-ssr": "3.4.21",
+ "@vue/shared": "3.4.21"
+ },
+ "peerDependencies": {
+ "vue": "3.4.21"
+ }
+ },
+ "node_modules/@vue/shared": {
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.4.21.tgz",
+ "integrity": "sha512-PuJe7vDIi6VYSinuEbUIQgMIRZGgM8e4R+G+/dQTk0X1NEdvgvvgv7m+rfmDH1gZzyA1OjjoWskvHlfRNfQf3g=="
+ },
+ "node_modules/abbrev": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
+ "integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
+ "dev": true
+ },
+ "node_modules/acorn": {
+ "version": "8.11.3",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.11.3.tgz",
+ "integrity": "sha512-Y9rRfJG5jcKOE0CLisYbojUjIrIEE7AGMzA/Sm4BslANhbS+cDMpgBdcPT91oJ7OuJ9hYJBx59RjbhxVnrF8Xg==",
+ "dev": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "dev": true,
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/agent-base": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
+ "integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
+ "dev": true,
+ "dependencies": {
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6.0.0"
+ }
+ },
+ "node_modules/agentkeepalive": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.5.0.tgz",
+ "integrity": "sha512-5GG/5IbQQpC9FpkRGsSvZI5QYeSCzlJHdpBQntCsuTOxhKD8lqKhrleg2Yi7yvMIf82Ycmmqln9U8V9qwEiJew==",
+ "dev": true,
+ "dependencies": {
+ "humanize-ms": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 8.0.0"
+ }
+ },
+ "node_modules/aggregate-error": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/aggregate-error/-/aggregate-error-3.1.0.tgz",
+ "integrity": "sha512-4I7Td01quW/RpocfNayFdFVk1qSuoh0E7JrbRJ16nH01HhKFQ88INq9Sd+nd72zqRySlr9BmDA8xlEJ6vJMrYA==",
+ "dev": true,
+ "dependencies": {
+ "clean-stack": "^2.0.0",
+ "indent-string": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dev": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "devOptional": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/aproba": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
+ "integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
+ "dev": true
+ },
+ "node_modules/are-we-there-yet": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-3.0.1.tgz",
+ "integrity": "sha512-QZW4EDmGwlYur0Yyf/b2uGucHQMa8aFUP7eu9ddR73vvhFyt4V0Vl3QHPcTNJ8l6qYOBdxgXdnBXQrHilfRQBg==",
+ "dev": true,
+ "dependencies": {
+ "delegates": "^1.0.0",
+ "readable-stream": "^3.6.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+ },
+ "node_modules/array-union": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz",
+ "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/arrify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/arrify/-/arrify-1.0.1.tgz",
+ "integrity": "sha512-3CYzex9M9FGQjCGMGyi6/31c8GJbgb0qGyrx5HWxPd0aCwh4cB2YjMb2Xf9UuoogrMrlO9cTqnB5rI5GHZTcUA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/async-foreach": {
+ "version": "0.1.3",
+ "resolved": "https://registry.npmjs.org/async-foreach/-/async-foreach-0.1.3.tgz",
+ "integrity": "sha512-VUeSMD8nEGBWaZK4lizI1sf3yEC7pnAQ/mrI7pC2fBz2s/tq5jWWEngTwaf0Gruu/OoXRGLGg1XFqpYBiGTYJA==",
+ "dev": true,
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/asynckit": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz",
+ "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q=="
+ },
+ "node_modules/axios": {
+ "version": "1.6.7",
+ "resolved": "https://registry.npmjs.org/axios/-/axios-1.6.7.tgz",
+ "integrity": "sha512-/hDJGff6/c7u0hDkvkGxR/oy6CbCs8ziCsC7SqmhjfozqiJGc8Z11wrv9z9lYfY4K8l+H9TpjcMDX0xOZmx+RA==",
+ "dependencies": {
+ "follow-redirects": "^1.15.4",
+ "form-data": "^4.0.0",
+ "proxy-from-env": "^1.1.0"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
+ "dev": true
+ },
+ "node_modules/base64-js": {
+ "version": "1.5.1",
+ "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
+ "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "devOptional": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/boolbase": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz",
+ "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==",
+ "dev": true
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "devOptional": true,
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/buffer": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
+ "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "base64-js": "^1.3.1",
+ "ieee754": "^1.2.1"
+ }
+ },
+ "node_modules/cacache": {
+ "version": "16.1.3",
+ "resolved": "https://registry.npmjs.org/cacache/-/cacache-16.1.3.tgz",
+ "integrity": "sha512-/+Emcj9DAXxX4cwlLmRI9c166RuL3w30zp4R7Joiv2cQTtTtA+jeuCAjH3ZlGnYS3tKENSrKhAzVVP9GVyzeYQ==",
+ "dev": true,
+ "dependencies": {
+ "@npmcli/fs": "^2.1.0",
+ "@npmcli/move-file": "^2.0.0",
+ "chownr": "^2.0.0",
+ "fs-minipass": "^2.1.0",
+ "glob": "^8.0.1",
+ "infer-owner": "^1.0.4",
+ "lru-cache": "^7.7.1",
+ "minipass": "^3.1.6",
+ "minipass-collect": "^1.0.2",
+ "minipass-flush": "^1.0.5",
+ "minipass-pipeline": "^1.2.4",
+ "mkdirp": "^1.0.4",
+ "p-map": "^4.0.0",
+ "promise-inflight": "^1.0.1",
+ "rimraf": "^3.0.2",
+ "ssri": "^9.0.0",
+ "tar": "^6.1.11",
+ "unique-filename": "^2.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/cacache/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/cacache/node_modules/glob": {
+ "version": "8.1.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-8.1.0.tgz",
+ "integrity": "sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^5.0.1",
+ "once": "^1.3.0"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/cacache/node_modules/minimatch": {
+ "version": "5.1.6",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.1.6.tgz",
+ "integrity": "sha512-lKwV/1brpG6mBUFHtb7NUmtABCb2WZZmm2wNiOA5hAb8VdCS4B3dtMWyvcoViccwAW/COERjXLt0zP1zXUN26g==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
+ "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase-keys": {
+ "version": "6.2.2",
+ "resolved": "https://registry.npmjs.org/camelcase-keys/-/camelcase-keys-6.2.2.tgz",
+ "integrity": "sha512-YrwaA0vEKazPBkn0ipTiMpSajYDSe+KjQfrjhcBMxJt/znbvlHd8Pw/Vamaz5EB4Wfhs3SUR3Z9mwRu/P3s3Yg==",
+ "dev": true,
+ "dependencies": {
+ "camelcase": "^5.3.1",
+ "map-obj": "^4.0.0",
+ "quick-lru": "^4.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz",
+ "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==",
+ "devOptional": true,
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "funding": {
+ "url": "https://paulmillr.com/funding/"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "devOptional": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/chownr": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
+ "integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ci-info": {
+ "version": "3.9.0",
+ "resolved": "https://registry.npmjs.org/ci-info/-/ci-info-3.9.0.tgz",
+ "integrity": "sha512-NIxF55hv4nSqQswkAeiOi1r83xy8JldOFDTWiug55KBu9Jnblncd2U6ViHmYgHf01TPZS77NJBhBMKdWj9HQMQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/sibiraj-s"
+ }
+ ],
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/clean-stack": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
+ "integrity": "sha512-4diC9HaTE+KRAMWhDhrGOECgWZxoevMc5TlkObMqNSsVU62PYzXZ/SMTjzyGAFF1YusgxGcSWTEXBhp0CPwQ1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-8.0.1.tgz",
+ "integrity": "sha512-BSeNnyus75C4//NQ9gQt1/csTXyo/8Sb+afLAkzAptFuMsod9HFokGNudZpi/oQV73hnVK+sR+5PVRMd+Dr7YQ==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.1",
+ "wrap-ansi": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/color-support": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
+ "integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
+ "dev": true,
+ "bin": {
+ "color-support": "bin.js"
+ }
+ },
+ "node_modules/combined-stream": {
+ "version": "1.0.8",
+ "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz",
+ "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==",
+ "dependencies": {
+ "delayed-stream": "~1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.8"
+ }
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
+ "dev": true
+ },
+ "node_modules/console-control-strings": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
+ "integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
+ "dev": true
+ },
+ "node_modules/content-disposition": {
+ "version": "0.5.4",
+ "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz",
+ "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==",
+ "dependencies": {
+ "safe-buffer": "5.2.1"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/core-util-is": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz",
+ "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==",
+ "dev": true
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dev": true,
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/cssesc": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz",
+ "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==",
+ "dev": true,
+ "bin": {
+ "cssesc": "bin/cssesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/csstype": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.3.tgz",
+ "integrity": "sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw=="
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decamelize": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
+ "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/decamelize-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/decamelize-keys/-/decamelize-keys-1.1.1.tgz",
+ "integrity": "sha512-WiPxgEirIV0/eIOMcnFBA3/IJZAZqKnwAwWyvvdi4lsr1WCN22nhdf/3db3DoZcUjTV2SqfzIwNyp6y2xs3nmg==",
+ "dev": true,
+ "dependencies": {
+ "decamelize": "^1.1.0",
+ "map-obj": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/decamelize-keys/node_modules/map-obj": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-1.0.1.tgz",
+ "integrity": "sha512-7N/q3lyZ+LVCp7PzuxrJr4KMbBE2hW7BT7YNia330OFxIf4d3r5zVpicP2650l7CPN6RM9zOJRl3NGpqSiw3Eg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==",
+ "dev": true
+ },
+ "node_modules/delayed-stream": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz",
+ "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==",
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/delegates": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
+ "integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
+ "dev": true
+ },
+ "node_modules/diff-sequences": {
+ "version": "28.1.1",
+ "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-28.1.1.tgz",
+ "integrity": "sha512-FU0iFaH/E23a+a718l8Qa/19bF9p06kgE0KipMOMadwa3SjnaElKzPaUC0vnibs6/B/9ni97s61mcejk8W1fQw==",
+ "dev": true,
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
+ }
+ },
+ "node_modules/dir-glob": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz",
+ "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==",
+ "dev": true,
+ "dependencies": {
+ "path-type": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dev": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
+ "dev": true
+ },
+ "node_modules/encoding": {
+ "version": "0.1.13",
+ "resolved": "https://registry.npmjs.org/encoding/-/encoding-0.1.13.tgz",
+ "integrity": "sha512-ETBauow1T35Y/WZMkio9jiM0Z5xjHHmJ4XmjZOq1l/dXz3lr2sRn87nJy20RupqSh1F2m3HHPSp8ShIPQJrJ3A==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "iconv-lite": "^0.6.2"
+ }
+ },
+ "node_modules/entities": {
+ "version": "4.5.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.5.0.tgz",
+ "integrity": "sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/env-paths": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/env-paths/-/env-paths-2.2.1.tgz",
+ "integrity": "sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/err-code": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/err-code/-/err-code-2.0.3.tgz",
+ "integrity": "sha512-2bmlRpNKBxT/CRmPOlyISQpNj+qSeYvcym/uT0Jx2bMOlKLtSy1ZmLuVxSEKKyor/N5yhvp/ZiG1oE3DEYMSFA==",
+ "dev": true
+ },
+ "node_modules/error-ex": {
+ "version": "1.3.2",
+ "resolved": "https://registry.npmjs.org/error-ex/-/error-ex-1.3.2.tgz",
+ "integrity": "sha512-7dFHNmqeFSEt2ZBsCriorKnn3Z2pj+fd9kmI6QoWw4//DL+icEBfc0U7qJCisqrTsKTjw4fNFy2pW9OqStD84g==",
+ "dev": true,
+ "dependencies": {
+ "is-arrayish": "^0.2.1"
+ }
+ },
+ "node_modules/esbuild": {
+ "version": "0.19.12",
+ "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.19.12.tgz",
+ "integrity": "sha512-aARqgq8roFBj054KvQr5f1sFu0D65G+miZRCuJyJ0G13Zwx7vRar5Zhn2tkQNzIXcBrNVsv/8stehpj+GAjgbg==",
+ "hasInstallScript": true,
+ "bin": {
+ "esbuild": "bin/esbuild"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "optionalDependencies": {
+ "@esbuild/aix-ppc64": "0.19.12",
+ "@esbuild/android-arm": "0.19.12",
+ "@esbuild/android-arm64": "0.19.12",
+ "@esbuild/android-x64": "0.19.12",
+ "@esbuild/darwin-arm64": "0.19.12",
+ "@esbuild/darwin-x64": "0.19.12",
+ "@esbuild/freebsd-arm64": "0.19.12",
+ "@esbuild/freebsd-x64": "0.19.12",
+ "@esbuild/linux-arm": "0.19.12",
+ "@esbuild/linux-arm64": "0.19.12",
+ "@esbuild/linux-ia32": "0.19.12",
+ "@esbuild/linux-loong64": "0.19.12",
+ "@esbuild/linux-mips64el": "0.19.12",
+ "@esbuild/linux-ppc64": "0.19.12",
+ "@esbuild/linux-riscv64": "0.19.12",
+ "@esbuild/linux-s390x": "0.19.12",
+ "@esbuild/linux-x64": "0.19.12",
+ "@esbuild/netbsd-x64": "0.19.12",
+ "@esbuild/openbsd-x64": "0.19.12",
+ "@esbuild/sunos-x64": "0.19.12",
+ "@esbuild/win32-arm64": "0.19.12",
+ "@esbuild/win32-ia32": "0.19.12",
+ "@esbuild/win32-x64": "0.19.12"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.2.tgz",
+ "integrity": "sha512-ErCHMCae19vR8vQGe50xIsVomy19rg6gFu3+r3jkEO46suLMWBksvVyoGgQV+jOfl84ZSOSlmv6Gxa89PmTGmA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.57.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.0.tgz",
+ "integrity": "sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.2.0",
+ "@eslint-community/regexpp": "^4.6.1",
+ "@eslint/eslintrc": "^2.1.4",
+ "@eslint/js": "8.57.0",
+ "@humanwhocodes/config-array": "^0.11.14",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "@ungap/structured-clone": "^1.2.0",
+ "ajv": "^6.12.4",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.2.2",
+ "eslint-visitor-keys": "^3.4.3",
+ "espree": "^9.6.1",
+ "esquery": "^1.4.2",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.19.0",
+ "graphemer": "^1.4.0",
+ "ignore": "^5.2.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.3",
+ "strip-ansi": "^6.0.1",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-plugin-vue": {
+ "version": "9.22.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.22.0.tgz",
+ "integrity": "sha512-7wCXv5zuVnBtZE/74z4yZ0CM8AjH6bk4MQGm7hZjUC2DBppKU5ioeOk5LGSg/s9a1ZJnIsdPLJpXnu1Rc+cVHg==",
+ "dev": true,
+ "dependencies": {
+ "@eslint-community/eslint-utils": "^4.4.0",
+ "natural-compare": "^1.4.0",
+ "nth-check": "^2.1.1",
+ "postcss-selector-parser": "^6.0.15",
+ "semver": "^7.6.0",
+ "vue-eslint-parser": "^9.4.2",
+ "xml-name-validator": "^4.0.0"
+ },
+ "engines": {
+ "node": "^14.17.0 || >=16.0.0"
+ },
+ "peerDependencies": {
+ "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.4.3",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz",
+ "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==",
+ "dev": true,
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint/node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.6.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz",
+ "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==",
+ "dev": true,
+ "dependencies": {
+ "acorn": "^8.9.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.4.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.5.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz",
+ "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esquery/node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dev": true,
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esrecurse/node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estree-walker": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz",
+ "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w=="
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/expect": {
+ "version": "28.1.3",
+ "resolved": "https://registry.npmjs.org/expect/-/expect-28.1.3.tgz",
+ "integrity": "sha512-eEh0xn8HlsuOBxFgIss+2mX85VAS4Qy3OSkjV7rlBWljtA4oWH37glVGyOZSZvErDT/yBywZdPGwCXuTvSG85g==",
+ "dev": true,
+ "dependencies": {
+ "@jest/expect-utils": "^28.1.3",
+ "jest-get-type": "^28.0.2",
+ "jest-matcher-utils": "^28.1.3",
+ "jest-message-util": "^28.1.3",
+ "jest-util": "^28.1.3"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==",
+ "dev": true
+ },
+ "node_modules/fast-glob": {
+ "version": "3.3.2",
+ "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.2.tgz",
+ "integrity": "sha512-oX2ruAFQwf/Orj8m737Y5adxDQO0LAB7/S5MnxCdTNDd4p6BsyIVsv9JQsATbTSq8KHRpLwIHbVlUNatxd+1Ow==",
+ "dev": true,
+ "dependencies": {
+ "@nodelib/fs.stat": "^2.0.2",
+ "@nodelib/fs.walk": "^1.2.3",
+ "glob-parent": "^5.1.2",
+ "merge2": "^1.3.0",
+ "micromatch": "^4.0.4"
+ },
+ "engines": {
+ "node": ">=8.6.0"
+ }
+ },
+ "node_modules/fast-glob/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==",
+ "dev": true
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==",
+ "dev": true
+ },
+ "node_modules/fastq": {
+ "version": "1.17.1",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.17.1.tgz",
+ "integrity": "sha512-sRVD3lWVIXWg6By68ZN7vho9a1pQcN/WBFaAAsDDFzlJjvoGx0P8z7V1t72grFJfJhu3YPZBuu25f7Kaw2jN1w==",
+ "dev": true,
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dev": true,
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "devOptional": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz",
+ "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==",
+ "dev": true,
+ "dependencies": {
+ "flatted": "^3.2.9",
+ "keyv": "^4.5.3",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.3.1",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz",
+ "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==",
+ "dev": true
+ },
+ "node_modules/follow-redirects": {
+ "version": "1.15.6",
+ "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.6.tgz",
+ "integrity": "sha512-wWN62YITEaOpSK584EZXJafH1AGpO8RVgElfkuXbTOrPX4fIfOyEpW/CsiNd8JdYrAoOvafRTOEnvsO++qCqFA==",
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://github.com/sponsors/RubenVerborgh"
+ }
+ ],
+ "engines": {
+ "node": ">=4.0"
+ },
+ "peerDependenciesMeta": {
+ "debug": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/form-data": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz",
+ "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==",
+ "dependencies": {
+ "asynckit": "^0.4.0",
+ "combined-stream": "^1.0.8",
+ "mime-types": "^2.1.12"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/fs-minipass": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
+ "integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
+ "dev": true,
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
+ "dev": true
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.3",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz",
+ "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==",
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz",
+ "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==",
+ "dev": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gauge": {
+ "version": "4.0.4",
+ "resolved": "https://registry.npmjs.org/gauge/-/gauge-4.0.4.tgz",
+ "integrity": "sha512-f9m+BEN5jkg6a0fZjleidjN51VE1X+mPFQ2DJ0uv1V39oCLCbsGe6yjbBnp7eK7z/+GAon99a3nHuqbuuthyPg==",
+ "dev": true,
+ "dependencies": {
+ "aproba": "^1.0.3 || ^2.0.0",
+ "color-support": "^1.1.3",
+ "console-control-strings": "^1.1.0",
+ "has-unicode": "^2.0.1",
+ "signal-exit": "^3.0.7",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1",
+ "wide-align": "^1.1.5"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/gaze": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/gaze/-/gaze-1.1.3.tgz",
+ "integrity": "sha512-BRdNm8hbWzFzWHERTrejLqwHDfS4GibPoq5wjTPIoJHoBtKGPg3xAFfxmM+9ztbXelxcf2hwQcaz1PtmFeue8g==",
+ "dev": true,
+ "dependencies": {
+ "globule": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 4.0.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-stdin": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/get-stdin/-/get-stdin-4.0.1.tgz",
+ "integrity": "sha512-F5aQMywwJ2n85s4hJPTT9RPxGmubonuB10MNYo17/xph174n2MIR33HRguhzVag10O/npM7SPk73LMZNP+FaWw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.3",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
+ "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.1.1",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "13.24.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz",
+ "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==",
+ "dev": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globby": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz",
+ "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==",
+ "dev": true,
+ "dependencies": {
+ "array-union": "^2.1.0",
+ "dir-glob": "^3.0.1",
+ "fast-glob": "^3.2.9",
+ "ignore": "^5.2.0",
+ "merge2": "^1.4.1",
+ "slash": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/globule": {
+ "version": "1.3.4",
+ "resolved": "https://registry.npmjs.org/globule/-/globule-1.3.4.tgz",
+ "integrity": "sha512-OPTIfhMBh7JbBYDpa5b+Q5ptmMWKwcNcFSR/0c6t8V4f3ZAVBEsKNY37QdVqmLRYSMhOUGYrY0QhSoEpzGr/Eg==",
+ "dev": true,
+ "dependencies": {
+ "glob": "~7.1.1",
+ "lodash": "^4.17.21",
+ "minimatch": "~3.0.2"
+ },
+ "engines": {
+ "node": ">= 0.10"
+ }
+ },
+ "node_modules/globule/node_modules/glob": {
+ "version": "7.1.7",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.7.tgz",
+ "integrity": "sha512-OvD9ENzPLbegENnYP5UUfJIirTg4+XwMWGaQfQTY0JenxNvvIKP3U3/tAQSPIu/lHxXYSZmpXlUHeqAIdKzBLQ==",
+ "dev": true,
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/globule/node_modules/minimatch": {
+ "version": "3.0.8",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.8.tgz",
+ "integrity": "sha512-6FsRAQsxQ61mw+qP1ZzbL9Bc78x2p5OqNgNpnoAFLTrX8n5Kxph0CsnhmKKNXTWjXqU5L0pGPR7hYk+XWZr60Q==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/graceful-fs": {
+ "version": "4.2.11",
+ "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.11.tgz",
+ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==",
+ "dev": true
+ },
+ "node_modules/graphemer": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz",
+ "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==",
+ "dev": true
+ },
+ "node_modules/hard-rejection": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/hard-rejection/-/hard-rejection-2.1.0.tgz",
+ "integrity": "sha512-VIZB+ibDhx7ObhAe7OVtoEbuP4h/MuOTHJ+J8h/eBXotJYl0fBgR72xDFCKgIh22OJZIOVNxBMWuhAr10r8HdA==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/has-unicode": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
+ "integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
+ "dev": true
+ },
+ "node_modules/hasown": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.1.tgz",
+ "integrity": "sha512-1/th4MHjnwncwXsIW6QMzlvYL9kG5e/CpVvLRZe4XPa8TOUNbCELqmvhDmnkNsAjwaG4+I8gJJL0JBvTTLO9qA==",
+ "dev": true,
+ "dependencies": {
+ "function-bind": "^1.1.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/hosted-git-info": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-4.1.0.tgz",
+ "integrity": "sha512-kyCuEOWjJqZuDbRHzL8V93NzQhwIB71oFWSyzVo+KPZI+pnQPPxucdkrOZvkLRnrf5URsQM+IJ09Dw29cRALIA==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/hosted-git-info/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/http-cache-semantics": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/http-cache-semantics/-/http-cache-semantics-4.1.1.tgz",
+ "integrity": "sha512-er295DKPVsV82j5kw1Gjt+ADA/XYHsajl82cGNQG2eyoPkvgUhX+nDIyelzhIWbbsXP39EHcI6l5tYs2FYqYXQ==",
+ "dev": true
+ },
+ "node_modules/http-proxy-agent": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-5.0.0.tgz",
+ "integrity": "sha512-n2hY8YdoRE1i7r6M0w9DIw5GgZN0G25P8zLCRQ8rjXtTU3vsNFBI/vWK/UIeE6g5MUUz6avwAPXmL6Fy9D/90w==",
+ "dev": true,
+ "dependencies": {
+ "@tootallnate/once": "2",
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/https-proxy-agent": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
+ "integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
+ "dev": true,
+ "dependencies": {
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/humanize-ms": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz",
+ "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==",
+ "dev": true,
+ "dependencies": {
+ "ms": "^2.0.0"
+ }
+ },
+ "node_modules/iconv-lite": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.6.3.tgz",
+ "integrity": "sha512-4fCk79wshMdzMp2rH06qWrJE4iolqLhCUH+OiuIgU++RB0+94NlDL81atO7GX55uUKueo0txHNtvEyI6D7WdMw==",
+ "dev": true,
+ "optional": true,
+ "dependencies": {
+ "safer-buffer": ">= 2.1.2 < 3.0.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/ieee754": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
+ "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/ignore": {
+ "version": "5.3.1",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.3.1.tgz",
+ "integrity": "sha512-5Fytz/IraMjqpwfd34ke28PTVMjZjJG2MPn5t7OE4eUCUNf8BAa7b5WUS9/Qvr6mwOQS7Mk6vdsMno5he+T8Xw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/immutable": {
+ "version": "4.3.5",
+ "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.5.tgz",
+ "integrity": "sha512-8eabxkth9gZatlwl5TBuJnCsoTADlL6ftEr7A4qgdaTsPyreilDSnUk57SO+jfKcNtxPa22U5KK6DSeAYhpBJw==",
+ "devOptional": true
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dev": true,
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/indent-string": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/indent-string/-/indent-string-4.0.0.tgz",
+ "integrity": "sha512-EdDDZu4A2OyIK7Lr/2zG+w5jmbuk1DVBnEwREQvBzspBJkCEbRa8GxU1lghYcaGJCnRWibjDXlq779X1/y5xwg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/infer-owner": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/infer-owner/-/infer-owner-1.0.4.tgz",
+ "integrity": "sha512-IClj+Xz94+d7irH5qRyfJonOdfTzuDaifE6ZPWfx0N0+/ATZCbuTPq2prFl526urkQd90WyUKIh1DfBQ2hMz9A==",
+ "dev": true
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "dev": true,
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
+ "dev": true
+ },
+ "node_modules/ip-address": {
+ "version": "9.0.5",
+ "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-9.0.5.tgz",
+ "integrity": "sha512-zHtQzGojZXTwZTHQqra+ETKd4Sn3vgi7uBmlPoXVWZqYvuKmtI0l/VZTjqGmJY9x88GGOaZ9+G9ES8hC4T4X8g==",
+ "dev": true,
+ "dependencies": {
+ "jsbn": "1.1.0",
+ "sprintf-js": "^1.1.3"
+ },
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/is-arrayish": {
+ "version": "0.2.1",
+ "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.2.1.tgz",
+ "integrity": "sha512-zz06S8t0ozoDXMG+ube26zeCTNXcKIPJZJi8hBrF4idCLms4CG9QtK7qBl1boi5ODzFpjswb5JPmHCbMpjaYzg==",
+ "dev": true
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "devOptional": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.13.1",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.13.1.tgz",
+ "integrity": "sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==",
+ "dev": true,
+ "dependencies": {
+ "hasown": "^2.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "devOptional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "devOptional": true,
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-lambda": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/is-lambda/-/is-lambda-1.0.1.tgz",
+ "integrity": "sha512-z7CMFGNrENq5iFB9Bqo64Xk6Y9sg+epq1myIcdHaGnbMTYOxvzsEtdYqQUylB7LxfkvgrrjP32T6Ywciio9UIQ==",
+ "dev": true
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "devOptional": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-1.1.0.tgz",
+ "integrity": "sha512-yvkRyxmFKEOQ4pNXCmJG5AEQNlXJS5LaONXo5/cLdTZdWvsZ1ioJEonLGAosKlMWE8lwUy/bJzMjcw8az73+Fg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/isarray": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz",
+ "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==",
+ "dev": true
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==",
+ "dev": true
+ },
+ "node_modules/jest-diff": {
+ "version": "28.1.3",
+ "resolved": "https://registry.npmjs.org/jest-diff/-/jest-diff-28.1.3.tgz",
+ "integrity": "sha512-8RqP1B/OXzjjTWkqMX67iqgwBVJRgCyKD3L9nq+6ZqJMdvjE8RgHktqZ6jNrkdMT+dJuYNI3rhQpxaz7drJHfw==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "diff-sequences": "^28.1.1",
+ "jest-get-type": "^28.0.2",
+ "pretty-format": "^28.1.3"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
+ }
+ },
+ "node_modules/jest-get-type": {
+ "version": "28.0.2",
+ "resolved": "https://registry.npmjs.org/jest-get-type/-/jest-get-type-28.0.2.tgz",
+ "integrity": "sha512-ioj2w9/DxSYHfOm5lJKCdcAmPJzQXmbM/Url3rhlghrPvT3tt+7a/+oXc9azkKmLvoiXjtV83bEWqi+vs5nlPA==",
+ "dev": true,
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
+ }
+ },
+ "node_modules/jest-matcher-utils": {
+ "version": "28.1.3",
+ "resolved": "https://registry.npmjs.org/jest-matcher-utils/-/jest-matcher-utils-28.1.3.tgz",
+ "integrity": "sha512-kQeJ7qHemKfbzKoGjHHrRKH6atgxMk8Enkk2iPQ3XwO6oE/KYD8lMYOziCkeSB9G4adPM4nR1DE8Tf5JeWH6Bw==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.0.0",
+ "jest-diff": "^28.1.3",
+ "jest-get-type": "^28.0.2",
+ "pretty-format": "^28.1.3"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
+ }
+ },
+ "node_modules/jest-message-util": {
+ "version": "28.1.3",
+ "resolved": "https://registry.npmjs.org/jest-message-util/-/jest-message-util-28.1.3.tgz",
+ "integrity": "sha512-PFdn9Iewbt575zKPf1286Ht9EPoJmYT7P0kY+RibeYZ2XtOr53pDLEFoTWXbd1h4JiGiWpTBC84fc8xMXQMb7g==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.12.13",
+ "@jest/types": "^28.1.3",
+ "@types/stack-utils": "^2.0.0",
+ "chalk": "^4.0.0",
+ "graceful-fs": "^4.2.9",
+ "micromatch": "^4.0.4",
+ "pretty-format": "^28.1.3",
+ "slash": "^3.0.0",
+ "stack-utils": "^2.0.3"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
+ }
+ },
+ "node_modules/jest-util": {
+ "version": "28.1.3",
+ "resolved": "https://registry.npmjs.org/jest-util/-/jest-util-28.1.3.tgz",
+ "integrity": "sha512-XdqfpHwpcSRko/C35uLYFM2emRAltIIKZiJ9eAmhjsj0CqZMa0p1ib0R5fWIqGhn1a103DebTbpqIaP1qCQ6tQ==",
+ "dev": true,
+ "dependencies": {
+ "@jest/types": "^28.1.3",
+ "@types/node": "*",
+ "chalk": "^4.0.0",
+ "ci-info": "^3.2.0",
+ "graceful-fs": "^4.2.9",
+ "picomatch": "^2.2.3"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
+ }
+ },
+ "node_modules/js-base64": {
+ "version": "2.6.4",
+ "resolved": "https://registry.npmjs.org/js-base64/-/js-base64-2.6.4.tgz",
+ "integrity": "sha512-pZe//GGmwJndub7ZghVHz7vjb2LgC1m8B07Au3eYqeqv9emhESByMXxaEgkUkEqJe87oBbSniGYoQNIBklc7IQ==",
+ "dev": true
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==",
+ "dev": true
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsbn": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/jsbn/-/jsbn-1.1.0.tgz",
+ "integrity": "sha512-4bYVV3aAMtDTTu4+xsDYa6sy9GyJ69/amsu9sYF2zqjiEoZA5xJi3BrfX3uY+/IekIu7MwdObdbDWpoZdBv3/A==",
+ "dev": true
+ },
+ "node_modules/json-buffer": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz",
+ "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==",
+ "dev": true
+ },
+ "node_modules/json-loader": {
+ "version": "0.5.7",
+ "resolved": "https://registry.npmjs.org/json-loader/-/json-loader-0.5.7.tgz",
+ "integrity": "sha512-QLPs8Dj7lnf3e3QYS1zkCo+4ZwqOiF9d/nZnYozTISxXWCfNs9yuky5rJw4/W34s7POaNlbZmQGaB5NiXCbP4w=="
+ },
+ "node_modules/json-parse-even-better-errors": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/json-parse-even-better-errors/-/json-parse-even-better-errors-2.3.1.tgz",
+ "integrity": "sha512-xyFwyhro/JEof6Ghe2iz2NcXoj2sloNsWr/XsERDK/oiPCfaNhl5ONfp+jQdAZRQQ0IJWNzH9zIZF7li91kh2w==",
+ "dev": true
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==",
+ "dev": true
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==",
+ "dev": true
+ },
+ "node_modules/keyv": {
+ "version": "4.5.4",
+ "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz",
+ "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==",
+ "dev": true,
+ "dependencies": {
+ "json-buffer": "3.0.1"
+ }
+ },
+ "node_modules/kind-of": {
+ "version": "6.0.3",
+ "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz",
+ "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/lines-and-columns": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz",
+ "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==",
+ "dev": true
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==",
+ "dev": true
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
+ "dev": true
+ },
+ "node_modules/lru-cache": {
+ "version": "7.18.3",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-7.18.3.tgz",
+ "integrity": "sha512-jumlc0BIUrS3qJGgIkWZsyfAM7NCWiBcCDhnd+3NNM5KbBmLTgHVfWBcg6W+rLUsIpzpERPsvwUP7CckAQSOoA==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/magic-string": {
+ "version": "0.30.7",
+ "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.7.tgz",
+ "integrity": "sha512-8vBuFF/I/+OSLRmdf2wwFCJCz+nSn0m6DPvGH1fS/KiQoSaR+sETbov0eIk9KhEKy8CYqIkIAnbohxT/4H0kuA==",
+ "dependencies": {
+ "@jridgewell/sourcemap-codec": "^1.4.15"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/make-fetch-happen": {
+ "version": "10.2.1",
+ "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-10.2.1.tgz",
+ "integrity": "sha512-NgOPbRiaQM10DYXvN3/hhGVI2M5MtITFryzBGxHM5p4wnFxsVCbxkrBrDsk+EZ5OB4jEOT7AjDxtdF+KVEFT7w==",
+ "dev": true,
+ "dependencies": {
+ "agentkeepalive": "^4.2.1",
+ "cacache": "^16.1.0",
+ "http-cache-semantics": "^4.1.0",
+ "http-proxy-agent": "^5.0.0",
+ "https-proxy-agent": "^5.0.0",
+ "is-lambda": "^1.0.1",
+ "lru-cache": "^7.7.1",
+ "minipass": "^3.1.6",
+ "minipass-collect": "^1.0.2",
+ "minipass-fetch": "^2.0.3",
+ "minipass-flush": "^1.0.5",
+ "minipass-pipeline": "^1.2.4",
+ "negotiator": "^0.6.3",
+ "promise-retry": "^2.0.1",
+ "socks-proxy-agent": "^7.0.0",
+ "ssri": "^9.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/map-obj": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/map-obj/-/map-obj-4.3.0.tgz",
+ "integrity": "sha512-hdN1wVrZbb29eBGiGjJbeP8JbKjq1urkHJ/LIP/NY48MZ1QVXUsQBV1G1zvYFHn1XE06cwjBsOI2K3Ulnj1YXQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/meow": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/meow/-/meow-9.0.0.tgz",
+ "integrity": "sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==",
+ "dev": true,
+ "dependencies": {
+ "@types/minimist": "^1.2.0",
+ "camelcase-keys": "^6.2.2",
+ "decamelize": "^1.2.0",
+ "decamelize-keys": "^1.1.0",
+ "hard-rejection": "^2.1.0",
+ "minimist-options": "4.1.0",
+ "normalize-package-data": "^3.0.0",
+ "read-pkg-up": "^7.0.1",
+ "redent": "^3.0.0",
+ "trim-newlines": "^3.0.0",
+ "type-fest": "^0.18.0",
+ "yargs-parser": "^20.2.3"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/meow/node_modules/type-fest": {
+ "version": "0.18.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.18.1.tgz",
+ "integrity": "sha512-OIAYXk8+ISY+qTOwkHtKqzAuxchoMiD9Udx+FSGQDuiRR+PJKJHc2NJAXlbhkGwTt/4/nKZxELY1w3ReWOL8mw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/merge2": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz",
+ "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/micromatch": {
+ "version": "4.0.5",
+ "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz",
+ "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==",
+ "dev": true,
+ "dependencies": {
+ "braces": "^3.0.2",
+ "picomatch": "^2.3.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/mime-db": {
+ "version": "1.52.0",
+ "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz",
+ "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==",
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/mime-types": {
+ "version": "2.1.35",
+ "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz",
+ "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==",
+ "dependencies": {
+ "mime-db": "1.52.0"
+ },
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/min-indent": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/min-indent/-/min-indent-1.0.1.tgz",
+ "integrity": "sha512-I9jwMn07Sy/IwOj3zVkVik2JTvgpaykDZEigL6Rx6N9LbMywwUSMtxET+7lVoDLLd3O3IXwJwvuuns8UB/HeAg==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/minimist-options": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/minimist-options/-/minimist-options-4.1.0.tgz",
+ "integrity": "sha512-Q4r8ghd80yhO/0j1O3B2BjweX3fiHg9cdOwjJd2J76Q135c+NDxGCqdYKQ1SKBuFfgWbAUzBfvYjPUEeNgqN1A==",
+ "dev": true,
+ "dependencies": {
+ "arrify": "^1.0.1",
+ "is-plain-obj": "^1.1.0",
+ "kind-of": "^6.0.3"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/minipass": {
+ "version": "3.3.6",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
+ "integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/minipass-collect": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/minipass-collect/-/minipass-collect-1.0.2.tgz",
+ "integrity": "sha512-6T6lH0H8OG9kITm/Jm6tdooIbogG9e0tLgpY6mphXSm/A9u8Nq1ryBG+Qspiub9LjWlBPsPS3tWQ/Botq4FdxA==",
+ "dev": true,
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/minipass-fetch": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-2.1.2.tgz",
+ "integrity": "sha512-LT49Zi2/WMROHYoqGgdlQIZh8mLPZmOrN2NdJjMXxYe4nkN6FUyuPuOAOedNJDrx0IRGg9+4guZewtp8hE6TxA==",
+ "dev": true,
+ "dependencies": {
+ "minipass": "^3.1.6",
+ "minipass-sized": "^1.0.3",
+ "minizlib": "^2.1.2"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ },
+ "optionalDependencies": {
+ "encoding": "^0.1.13"
+ }
+ },
+ "node_modules/minipass-flush": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/minipass-flush/-/minipass-flush-1.0.5.tgz",
+ "integrity": "sha512-JmQSYYpPUqX5Jyn1mXaRwOda1uQ8HP5KAT/oDSLCzt1BYRhQU0/hDtsB1ufZfEEzMZ9aAVmsBw8+FWsIXlClWw==",
+ "dev": true,
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/minipass-pipeline": {
+ "version": "1.2.4",
+ "resolved": "https://registry.npmjs.org/minipass-pipeline/-/minipass-pipeline-1.2.4.tgz",
+ "integrity": "sha512-xuIq7cIOt09RPRJ19gdi4b+RiNvDFYe5JH+ggNvBqGqpQXcru3PcRmOZuHBKWK1Txf9+cQ+HMVN4d6z46LZP7A==",
+ "dev": true,
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/minipass-sized": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/minipass-sized/-/minipass-sized-1.0.3.tgz",
+ "integrity": "sha512-MbkQQ2CTiBMlA2Dm/5cY+9SWFEN8pzzOXi6rlM5Xxq0Yqbda5ZQy9sU75a673FE9ZK0Zsbr6Y5iP6u9nktfg2g==",
+ "dev": true,
+ "dependencies": {
+ "minipass": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/minizlib": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
+ "integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
+ "dev": true,
+ "dependencies": {
+ "minipass": "^3.0.0",
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/mkdirp": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
+ "integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
+ "dev": true,
+ "bin": {
+ "mkdirp": "bin/cmd.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
+ "dev": true
+ },
+ "node_modules/mutationobserver-shim": {
+ "version": "0.3.7",
+ "resolved": "https://registry.npmjs.org/mutationobserver-shim/-/mutationobserver-shim-0.3.7.tgz",
+ "integrity": "sha512-oRIDTyZQU96nAiz2AQyngwx1e89iApl2hN5AOYwyxLUB47UYsU3Wv9lJWqH5y/QdiYkc5HQLi23ZNB3fELdHcQ==",
+ "dev": true
+ },
+ "node_modules/nan": {
+ "version": "2.18.0",
+ "resolved": "https://registry.npmjs.org/nan/-/nan-2.18.0.tgz",
+ "integrity": "sha512-W7tfG7vMOGtD30sHoZSSc/JVYiyDPEyQVso/Zz+/uQd0B0L46gtC+pHha5FFMRpil6fm/AoEcRWyOVi4+E/f8w==",
+ "dev": true
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.7",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.7.tgz",
+ "integrity": "sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==",
+ "dev": true
+ },
+ "node_modules/natural-compare-lite": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare-lite/-/natural-compare-lite-1.4.0.tgz",
+ "integrity": "sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==",
+ "dev": true
+ },
+ "node_modules/negotiator": {
+ "version": "0.6.3",
+ "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz",
+ "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.6"
+ }
+ },
+ "node_modules/node-gyp": {
+ "version": "8.4.1",
+ "resolved": "https://registry.npmjs.org/node-gyp/-/node-gyp-8.4.1.tgz",
+ "integrity": "sha512-olTJRgUtAb/hOXG0E93wZDs5YiJlgbXxTwQAFHyNlRsXQnYzUaF2aGgujZbw+hR8aF4ZG/rST57bWMWD16jr9w==",
+ "dev": true,
+ "dependencies": {
+ "env-paths": "^2.2.0",
+ "glob": "^7.1.4",
+ "graceful-fs": "^4.2.6",
+ "make-fetch-happen": "^9.1.0",
+ "nopt": "^5.0.0",
+ "npmlog": "^6.0.0",
+ "rimraf": "^3.0.2",
+ "semver": "^7.3.5",
+ "tar": "^6.1.2",
+ "which": "^2.0.2"
+ },
+ "bin": {
+ "node-gyp": "bin/node-gyp.js"
+ },
+ "engines": {
+ "node": ">= 10.12.0"
+ }
+ },
+ "node_modules/node-gyp/node_modules/@npmcli/fs": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/@npmcli/fs/-/fs-1.1.1.tgz",
+ "integrity": "sha512-8KG5RD0GVP4ydEzRn/I4BNDuxDtqVbOdm8675T49OIG/NGhaK0pjPX7ZcDlvKYbA+ulvVK3ztfcF4uBdOxuJbQ==",
+ "dev": true,
+ "dependencies": {
+ "@gar/promisify": "^1.0.1",
+ "semver": "^7.3.5"
+ }
+ },
+ "node_modules/node-gyp/node_modules/@npmcli/move-file": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@npmcli/move-file/-/move-file-1.1.2.tgz",
+ "integrity": "sha512-1SUf/Cg2GzGDyaf15aR9St9TWlb+XvbZXWpDx8YKs7MLzMH/BCeopv+y9vzrzgkfykCGuWOlSu3mZhj2+FQcrg==",
+ "deprecated": "This functionality has been moved to @npmcli/fs",
+ "dev": true,
+ "dependencies": {
+ "mkdirp": "^1.0.4",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/node-gyp/node_modules/@tootallnate/once": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@tootallnate/once/-/once-1.1.2.tgz",
+ "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/node-gyp/node_modules/cacache": {
+ "version": "15.3.0",
+ "resolved": "https://registry.npmjs.org/cacache/-/cacache-15.3.0.tgz",
+ "integrity": "sha512-VVdYzXEn+cnbXpFgWs5hTT7OScegHVmLhJIR8Ufqk3iFD6A6j5iSX1KuBTfNEv4tdJWE2PzA6IVFtcLC7fN9wQ==",
+ "dev": true,
+ "dependencies": {
+ "@npmcli/fs": "^1.0.0",
+ "@npmcli/move-file": "^1.0.1",
+ "chownr": "^2.0.0",
+ "fs-minipass": "^2.0.0",
+ "glob": "^7.1.4",
+ "infer-owner": "^1.0.4",
+ "lru-cache": "^6.0.0",
+ "minipass": "^3.1.1",
+ "minipass-collect": "^1.0.2",
+ "minipass-flush": "^1.0.5",
+ "minipass-pipeline": "^1.2.2",
+ "mkdirp": "^1.0.3",
+ "p-map": "^4.0.0",
+ "promise-inflight": "^1.0.1",
+ "rimraf": "^3.0.2",
+ "ssri": "^8.0.1",
+ "tar": "^6.0.2",
+ "unique-filename": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/node-gyp/node_modules/http-proxy-agent": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/http-proxy-agent/-/http-proxy-agent-4.0.1.tgz",
+ "integrity": "sha512-k0zdNgqWTGA6aeIRVpvfVob4fL52dTfaehylg0Y4UvSySvOq/Y+BOyPrgpUrA7HylqvU8vIZGsRuXmspskV0Tg==",
+ "dev": true,
+ "dependencies": {
+ "@tootallnate/once": "1",
+ "agent-base": "6",
+ "debug": "4"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/node-gyp/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/node-gyp/node_modules/make-fetch-happen": {
+ "version": "9.1.0",
+ "resolved": "https://registry.npmjs.org/make-fetch-happen/-/make-fetch-happen-9.1.0.tgz",
+ "integrity": "sha512-+zopwDy7DNknmwPQplem5lAZX/eCOzSvSNNcSKm5eVwTkOBzoktEfXsa9L23J/GIRhxRsaxzkPEhrJEpE2F4Gg==",
+ "dev": true,
+ "dependencies": {
+ "agentkeepalive": "^4.1.3",
+ "cacache": "^15.2.0",
+ "http-cache-semantics": "^4.1.0",
+ "http-proxy-agent": "^4.0.1",
+ "https-proxy-agent": "^5.0.0",
+ "is-lambda": "^1.0.1",
+ "lru-cache": "^6.0.0",
+ "minipass": "^3.1.3",
+ "minipass-collect": "^1.0.2",
+ "minipass-fetch": "^1.3.2",
+ "minipass-flush": "^1.0.5",
+ "minipass-pipeline": "^1.2.4",
+ "negotiator": "^0.6.2",
+ "promise-retry": "^2.0.1",
+ "socks-proxy-agent": "^6.0.0",
+ "ssri": "^8.0.0"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/node-gyp/node_modules/minipass-fetch": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/minipass-fetch/-/minipass-fetch-1.4.1.tgz",
+ "integrity": "sha512-CGH1eblLq26Y15+Azk7ey4xh0J/XfJfrCox5LDJiKqI2Q2iwOLOKrlmIaODiSQS8d18jalF6y2K2ePUm0CmShw==",
+ "dev": true,
+ "dependencies": {
+ "minipass": "^3.1.0",
+ "minipass-sized": "^1.0.3",
+ "minizlib": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "optionalDependencies": {
+ "encoding": "^0.1.12"
+ }
+ },
+ "node_modules/node-gyp/node_modules/socks-proxy-agent": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-6.2.1.tgz",
+ "integrity": "sha512-a6KW9G+6B3nWZ1yB8G7pJwL3ggLy1uTzKAgCb7ttblwqdz9fMGJUuTy3uFzEP48FAs9FLILlmzDlE2JJhVQaXQ==",
+ "dev": true,
+ "dependencies": {
+ "agent-base": "^6.0.2",
+ "debug": "^4.3.3",
+ "socks": "^2.6.2"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/node-gyp/node_modules/ssri": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/ssri/-/ssri-8.0.1.tgz",
+ "integrity": "sha512-97qShzy1AiyxvPNIkLWoGua7xoQzzPjQ0HAH4B0rWKo7SZ6USuPcrUiAFrws0UH8RrbWmgq3LMTObhPIHbbBeQ==",
+ "dev": true,
+ "dependencies": {
+ "minipass": "^3.1.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/node-gyp/node_modules/unique-filename": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-1.1.1.tgz",
+ "integrity": "sha512-Vmp0jIp2ln35UTXuryvjzkjGdRyf9b2lTXuSYUiPmzRcl3FDtYqAwOnTJkAngD9SWhnoJzDbTKwaOrZ+STtxNQ==",
+ "dev": true,
+ "dependencies": {
+ "unique-slug": "^2.0.0"
+ }
+ },
+ "node_modules/node-gyp/node_modules/unique-slug": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-2.0.2.tgz",
+ "integrity": "sha512-zoWr9ObaxALD3DOPfjPSqxt4fnZiWblxHIgeWqW8x7UqDzEtHEQLzji2cuJYQFCU6KmoJikOYAZlrTHHebjx2w==",
+ "dev": true,
+ "dependencies": {
+ "imurmurhash": "^0.1.4"
+ }
+ },
+ "node_modules/node-sass": {
+ "version": "9.0.0",
+ "resolved": "https://registry.npmjs.org/node-sass/-/node-sass-9.0.0.tgz",
+ "integrity": "sha512-yltEuuLrfH6M7Pq2gAj5B6Zm7m+gdZoG66wTqG6mIZV/zijq3M2OO2HswtT6oBspPyFhHDcaxWpsBm0fRNDHPg==",
+ "dev": true,
+ "hasInstallScript": true,
+ "dependencies": {
+ "async-foreach": "^0.1.3",
+ "chalk": "^4.1.2",
+ "cross-spawn": "^7.0.3",
+ "gaze": "^1.0.0",
+ "get-stdin": "^4.0.1",
+ "glob": "^7.0.3",
+ "lodash": "^4.17.15",
+ "make-fetch-happen": "^10.0.4",
+ "meow": "^9.0.0",
+ "nan": "^2.17.0",
+ "node-gyp": "^8.4.1",
+ "sass-graph": "^4.0.1",
+ "stdout-stream": "^1.4.0",
+ "true-case-path": "^2.2.1"
+ },
+ "bin": {
+ "node-sass": "bin/node-sass"
+ },
+ "engines": {
+ "node": ">=16"
+ }
+ },
+ "node_modules/nopt": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
+ "integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
+ "dev": true,
+ "dependencies": {
+ "abbrev": "1"
+ },
+ "bin": {
+ "nopt": "bin/nopt.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/normalize-package-data": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-3.0.3.tgz",
+ "integrity": "sha512-p2W1sgqij3zMMyRC067Dg16bfzVH+w7hyegmpIvZ4JNjqtGOVAIvLmjBx3yP7YTe9vKJgkoNOPjwQGogDoMXFA==",
+ "dev": true,
+ "dependencies": {
+ "hosted-git-info": "^4.0.1",
+ "is-core-module": "^2.5.0",
+ "semver": "^7.3.4",
+ "validate-npm-package-license": "^3.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "devOptional": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/npmlog": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/npmlog/-/npmlog-6.0.2.tgz",
+ "integrity": "sha512-/vBvz5Jfr9dT/aFWd0FIRf+T/Q2WBsLENygUaFUqstqsycmZAP/t5BvFJTK0viFmSUxiUKTUplWy5vt+rvKIxg==",
+ "dev": true,
+ "dependencies": {
+ "are-we-there-yet": "^3.0.0",
+ "console-control-strings": "^1.1.0",
+ "gauge": "^4.0.3",
+ "set-blocking": "^2.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/nth-check": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz",
+ "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==",
+ "dev": true,
+ "dependencies": {
+ "boolbase": "^1.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/nth-check?sponsor=1"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dev": true,
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.3",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz",
+ "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==",
+ "dev": true,
+ "dependencies": {
+ "@aashutoshrathi/word-wrap": "^1.2.3",
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dev": true,
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-map": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/p-map/-/p-map-4.0.0.tgz",
+ "integrity": "sha512-/bjOqmgETBYB5BoEeGVea8dmvHb2m9GLy1E9W43yeyfP6QQCZGFNa+XRceJEuDB6zqr+gKpIAmlLebMpykw/MQ==",
+ "dev": true,
+ "dependencies": {
+ "aggregate-error": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-try": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
+ "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dev": true,
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/parse-json": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/parse-json/-/parse-json-5.2.0.tgz",
+ "integrity": "sha512-ayCKvm/phCGxOkYRSCM82iDwct8/EonSEgCSxWxD7ve6jHggsFl4fZVQBPRNgQoKiuV/odhFrGzQXZwbifC8Rg==",
+ "dev": true,
+ "dependencies": {
+ "@babel/code-frame": "^7.0.0",
+ "error-ex": "^1.3.1",
+ "json-parse-even-better-errors": "^2.3.0",
+ "lines-and-columns": "^1.1.6"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "dev": true
+ },
+ "node_modules/path-type": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz",
+ "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "devOptional": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/pinia": {
+ "version": "2.1.7",
+ "resolved": "https://registry.npmjs.org/pinia/-/pinia-2.1.7.tgz",
+ "integrity": "sha512-+C2AHFtcFqjPih0zpYuvof37SFxMQ7OEG2zV9jRI12i9BOy3YQVAHwdKtyyc8pDcDyIc33WCIsZaCFWU7WWxGQ==",
+ "dependencies": {
+ "@vue/devtools-api": "^6.5.0",
+ "vue-demi": ">=0.14.5"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.4.0",
+ "typescript": ">=4.4.4",
+ "vue": "^2.6.14 || ^3.3.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ },
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/pinia/node_modules/vue-demi": {
+ "version": "0.14.7",
+ "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.7.tgz",
+ "integrity": "sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==",
+ "hasInstallScript": true,
+ "bin": {
+ "vue-demi-fix": "bin/vue-demi-fix.js",
+ "vue-demi-switch": "bin/vue-demi-switch.js"
+ },
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/antfu"
+ },
+ "peerDependencies": {
+ "@vue/composition-api": "^1.0.0-rc.1",
+ "vue": "^3.0.0-0 || ^2.6.0"
+ },
+ "peerDependenciesMeta": {
+ "@vue/composition-api": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/postcss": {
+ "version": "8.4.35",
+ "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.35.tgz",
+ "integrity": "sha512-u5U8qYpBCpN13BsiEB0CbR1Hhh4Gc0zLFuedrHJKMctHCHAGrMdG0PRM/KErzAL3CU6/eckEtmHNB3x6e3c0vA==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/postcss/"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/postcss"
+ },
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/ai"
+ }
+ ],
+ "dependencies": {
+ "nanoid": "^3.3.7",
+ "picocolors": "^1.0.0",
+ "source-map-js": "^1.0.2"
+ },
+ "engines": {
+ "node": "^10 || ^12 || >=14"
+ }
+ },
+ "node_modules/postcss-selector-parser": {
+ "version": "6.0.15",
+ "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.15.tgz",
+ "integrity": "sha512-rEYkQOMUCEMhsKbK66tbEU9QVIxbhN18YiniAwA7XQYTVBqrBy+P2p5JcdqsHgKM2zWylp8d7J6eszocfds5Sw==",
+ "dev": true,
+ "dependencies": {
+ "cssesc": "^3.0.0",
+ "util-deprecate": "^1.0.2"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/pretty-format": {
+ "version": "28.1.3",
+ "resolved": "https://registry.npmjs.org/pretty-format/-/pretty-format-28.1.3.tgz",
+ "integrity": "sha512-8gFb/To0OmxHR9+ZTb14Df2vNxdGCX8g1xWGUTqUw5TiZvcQf5sHKObd5UcPyLLyowNwDAMTF3XWOG1B6mxl1Q==",
+ "dev": true,
+ "dependencies": {
+ "@jest/schemas": "^28.1.3",
+ "ansi-regex": "^5.0.1",
+ "ansi-styles": "^5.0.0",
+ "react-is": "^18.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || ^16.10.0 || >=17.0.0"
+ }
+ },
+ "node_modules/pretty-format/node_modules/ansi-styles": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-5.2.0.tgz",
+ "integrity": "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/process-nextick-args": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz",
+ "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==",
+ "dev": true
+ },
+ "node_modules/promise-inflight": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/promise-inflight/-/promise-inflight-1.0.1.tgz",
+ "integrity": "sha512-6zWPyEOFaQBJYcGMHBKTKJ3u6TBsnMFOIZSa6ce1e/ZrrsOlnHRHbabMjLiBYKp+n44X9eUI6VUPaukCXHuG4g==",
+ "dev": true
+ },
+ "node_modules/promise-retry": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/promise-retry/-/promise-retry-2.0.1.tgz",
+ "integrity": "sha512-y+WKFlBR8BGXnsNlIHFGPZmyDf3DFMoLhaflAnyZgV6rG6xu+JwesTo2Q9R6XwYmtmwAFCkAk3e35jEdoeh/3g==",
+ "dev": true,
+ "dependencies": {
+ "err-code": "^2.0.2",
+ "retry": "^0.12.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/proxy-from-env": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
+ "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
+ },
+ "node_modules/punycode": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.1.tgz",
+ "integrity": "sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==",
+ "dev": true,
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/quick-lru": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/quick-lru/-/quick-lru-4.0.1.tgz",
+ "integrity": "sha512-ARhCpm70fzdcvNQfPoy49IaanKkTlRWF2JMzqhcJbhSFRZv7nPTvZJdcY7301IPmvW+/p0RgIWnQDLJxifsQ7g==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "18.2.0",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-18.2.0.tgz",
+ "integrity": "sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==",
+ "dev": true
+ },
+ "node_modules/read-pkg": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/read-pkg/-/read-pkg-5.2.0.tgz",
+ "integrity": "sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==",
+ "dev": true,
+ "dependencies": {
+ "@types/normalize-package-data": "^2.4.0",
+ "normalize-package-data": "^2.5.0",
+ "parse-json": "^5.0.0",
+ "type-fest": "^0.6.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg-up": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/read-pkg-up/-/read-pkg-up-7.0.1.tgz",
+ "integrity": "sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==",
+ "dev": true,
+ "dependencies": {
+ "find-up": "^4.1.0",
+ "read-pkg": "^5.2.0",
+ "type-fest": "^0.8.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/find-up": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
+ "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
+ "dev": true,
+ "dependencies": {
+ "locate-path": "^5.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/locate-path": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
+ "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
+ "dev": true,
+ "dependencies": {
+ "p-locate": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/p-limit": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
+ "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
+ "dev": true,
+ "dependencies": {
+ "p-try": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/p-locate": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
+ "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
+ "dev": true,
+ "dependencies": {
+ "p-limit": "^2.2.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg-up/node_modules/type-fest": {
+ "version": "0.8.1",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.8.1.tgz",
+ "integrity": "sha512-4dbzIzqvjtgiM5rw1k5rEHtBANKmdudhGyBEajN01fEyhaAIhsoKNy6y7+IN93IfpFtwY9iqi7kD+xwKhQsNJA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/read-pkg/node_modules/hosted-git-info": {
+ "version": "2.8.9",
+ "resolved": "https://registry.npmjs.org/hosted-git-info/-/hosted-git-info-2.8.9.tgz",
+ "integrity": "sha512-mxIDAb9Lsm6DoOJ7xH+5+X4y1LU/4Hi50L9C5sIswK3JzULS4bwk1FvjdBgvYR4bzT4tuUQiC15FE2f5HbLvYw==",
+ "dev": true
+ },
+ "node_modules/read-pkg/node_modules/normalize-package-data": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/normalize-package-data/-/normalize-package-data-2.5.0.tgz",
+ "integrity": "sha512-/5CMN3T0R4XTj4DcGaexo+roZSdSFW/0AOOTROrjxzCG1wrWXEsGbRKevjlIL+ZDE4sZlJr5ED4YW0yqmkK+eA==",
+ "dev": true,
+ "dependencies": {
+ "hosted-git-info": "^2.1.4",
+ "resolve": "^1.10.0",
+ "semver": "2 || 3 || 4 || 5",
+ "validate-npm-package-license": "^3.0.1"
+ }
+ },
+ "node_modules/read-pkg/node_modules/semver": {
+ "version": "5.7.2",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.2.tgz",
+ "integrity": "sha512-cBznnQ9KjJqU67B52RMC65CMarK2600WFnbkcaiwWq3xy/5haFJlshgnpjovMVJ+Hff49d8GEn0b87C5pDQ10g==",
+ "dev": true,
+ "bin": {
+ "semver": "bin/semver"
+ }
+ },
+ "node_modules/read-pkg/node_modules/type-fest": {
+ "version": "0.6.0",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.6.0.tgz",
+ "integrity": "sha512-q+MB8nYR1KDLrgr4G5yemftpMC7/QLqVndBmEEdqzmNj5dcFOO4Oo8qlwZE3ULT3+Zim1F8Kq4cBnikNhlCMlg==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/readable-stream": {
+ "version": "3.6.2",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
+ "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
+ "dev": true,
+ "dependencies": {
+ "inherits": "^2.0.3",
+ "string_decoder": "^1.1.1",
+ "util-deprecate": "^1.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "devOptional": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/redent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/redent/-/redent-3.0.0.tgz",
+ "integrity": "sha512-6tDA8g98We0zd0GvVeMT9arEOnTw9qM03L9cJXaCjrip1OO764RDBLBfrB4cwzNGDj5OA5ioymC9GkizgWJDUg==",
+ "dev": true,
+ "dependencies": {
+ "indent-string": "^4.0.0",
+ "strip-indent": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.8",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.8.tgz",
+ "integrity": "sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==",
+ "dev": true,
+ "dependencies": {
+ "is-core-module": "^2.13.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "dev": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/retry": {
+ "version": "0.12.0",
+ "resolved": "https://registry.npmjs.org/retry/-/retry-0.12.0.tgz",
+ "integrity": "sha512-9LkiTwjUh6rT555DtE9rTX+BKByPfrMzEAtnlEtdEwr3Nkffwiihqe2bWADg+OQRjt9gl6ICdmB/ZFDCGAtSow==",
+ "dev": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "dev": true,
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/rollup": {
+ "version": "4.12.0",
+ "resolved": "https://registry.npmjs.org/rollup/-/rollup-4.12.0.tgz",
+ "integrity": "sha512-wz66wn4t1OHIJw3+XU7mJJQV/2NAfw5OAk6G6Hoo3zcvz/XOfQ52Vgi+AN4Uxoxi0KBBwk2g8zPrTDA4btSB/Q==",
+ "dependencies": {
+ "@types/estree": "1.0.5"
+ },
+ "bin": {
+ "rollup": "dist/bin/rollup"
+ },
+ "engines": {
+ "node": ">=18.0.0",
+ "npm": ">=8.0.0"
+ },
+ "optionalDependencies": {
+ "@rollup/rollup-android-arm-eabi": "4.12.0",
+ "@rollup/rollup-android-arm64": "4.12.0",
+ "@rollup/rollup-darwin-arm64": "4.12.0",
+ "@rollup/rollup-darwin-x64": "4.12.0",
+ "@rollup/rollup-linux-arm-gnueabihf": "4.12.0",
+ "@rollup/rollup-linux-arm64-gnu": "4.12.0",
+ "@rollup/rollup-linux-arm64-musl": "4.12.0",
+ "@rollup/rollup-linux-riscv64-gnu": "4.12.0",
+ "@rollup/rollup-linux-x64-gnu": "4.12.0",
+ "@rollup/rollup-linux-x64-musl": "4.12.0",
+ "@rollup/rollup-win32-arm64-msvc": "4.12.0",
+ "@rollup/rollup-win32-ia32-msvc": "4.12.0",
+ "@rollup/rollup-win32-x64-msvc": "4.12.0",
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/safer-buffer": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz",
+ "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==",
+ "dev": true,
+ "optional": true
+ },
+ "node_modules/sass": {
+ "version": "1.71.1",
+ "resolved": "https://registry.npmjs.org/sass/-/sass-1.71.1.tgz",
+ "integrity": "sha512-wovtnV2PxzteLlfNzbgm1tFXPLoZILYAMJtvoXXkD7/+1uP41eKkIt1ypWq5/q2uT94qHjXehEYfmjKOvjL9sg==",
+ "devOptional": true,
+ "dependencies": {
+ "chokidar": ">=3.0.0 <4.0.0",
+ "immutable": "^4.0.0",
+ "source-map-js": ">=0.6.2 <2.0.0"
+ },
+ "bin": {
+ "sass": "sass.js"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ }
+ },
+ "node_modules/sass-graph": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/sass-graph/-/sass-graph-4.0.1.tgz",
+ "integrity": "sha512-5YCfmGBmxoIRYHnKK2AKzrAkCoQ8ozO+iumT8K4tXJXRVCPf+7s1/9KxTSW3Rbvf+7Y7b4FR3mWyLnQr3PHocA==",
+ "dev": true,
+ "dependencies": {
+ "glob": "^7.0.0",
+ "lodash": "^4.17.11",
+ "scss-tokenizer": "^0.4.3",
+ "yargs": "^17.2.1"
+ },
+ "bin": {
+ "sassgraph": "bin/sassgraph"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/scss-tokenizer": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/scss-tokenizer/-/scss-tokenizer-0.4.3.tgz",
+ "integrity": "sha512-raKLgf1LI5QMQnG+RxHz6oK0sL3x3I4FN2UDLqgLOGO8hodECNnNh5BXn7fAyBxrA8zVzdQizQ6XjNJQ+uBwMw==",
+ "dev": true,
+ "dependencies": {
+ "js-base64": "^2.4.9",
+ "source-map": "^0.7.3"
+ }
+ },
+ "node_modules/semver": {
+ "version": "7.6.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
+ "integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
+ "dev": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/semver/node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "dev": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/set-blocking": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
+ "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
+ "dev": true
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dev": true,
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/signal-exit": {
+ "version": "3.0.7",
+ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
+ "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
+ "dev": true
+ },
+ "node_modules/slash": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz",
+ "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/smart-buffer": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/smart-buffer/-/smart-buffer-4.2.0.tgz",
+ "integrity": "sha512-94hK0Hh8rPqQl2xXc3HsaBoOXKV20MToPkcXvwbISWLEs+64sBq5kFgn2kJDHb1Pry9yrP0dxrCI9RRci7RXKg==",
+ "dev": true,
+ "engines": {
+ "node": ">= 6.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/socks": {
+ "version": "2.8.1",
+ "resolved": "https://registry.npmjs.org/socks/-/socks-2.8.1.tgz",
+ "integrity": "sha512-B6w7tkwNid7ToxjZ08rQMT8M9BJAf8DKx8Ft4NivzH0zBUfd6jldGcisJn/RLgxcX3FPNDdNQCUEMMT79b+oCQ==",
+ "dev": true,
+ "dependencies": {
+ "ip-address": "^9.0.5",
+ "smart-buffer": "^4.2.0"
+ },
+ "engines": {
+ "node": ">= 10.0.0",
+ "npm": ">= 3.0.0"
+ }
+ },
+ "node_modules/socks-proxy-agent": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/socks-proxy-agent/-/socks-proxy-agent-7.0.0.tgz",
+ "integrity": "sha512-Fgl0YPZ902wEsAyiQ+idGd1A7rSFx/ayC1CQVMw5P+EQx2V0SgpGtf6OKFhVjPflPUl9YMmEOnmfjCdMUsygww==",
+ "dev": true,
+ "dependencies": {
+ "agent-base": "^6.0.2",
+ "debug": "^4.3.3",
+ "socks": "^2.6.2"
+ },
+ "engines": {
+ "node": ">= 10"
+ }
+ },
+ "node_modules/source-map": {
+ "version": "0.7.4",
+ "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.4.tgz",
+ "integrity": "sha512-l3BikUxvPOcn5E74dZiq5BGsTb5yEwhaTSzccU6t4sDOH8NWJCstKO5QT2CvtFoK6F0saL7p9xHAqHOlCPJygA==",
+ "dev": true,
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/source-map-js": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz",
+ "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/spdx-correct": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/spdx-correct/-/spdx-correct-3.2.0.tgz",
+ "integrity": "sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==",
+ "dev": true,
+ "dependencies": {
+ "spdx-expression-parse": "^3.0.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-exceptions": {
+ "version": "2.5.0",
+ "resolved": "https://registry.npmjs.org/spdx-exceptions/-/spdx-exceptions-2.5.0.tgz",
+ "integrity": "sha512-PiU42r+xO4UbUS1buo3LPJkjlO7430Xn5SVAhdpzzsPHsjbYVflnnFdATgabnLude+Cqu25p6N+g2lw/PFsa4w==",
+ "dev": true
+ },
+ "node_modules/spdx-expression-parse": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/spdx-expression-parse/-/spdx-expression-parse-3.0.1.tgz",
+ "integrity": "sha512-cbqHunsQWnJNE6KhVSMsMeH5H/L9EpymbzqTQ3uLwNCLZ1Q481oWaofqH7nO6V07xlXwY6PhQdQ2IedWx/ZK4Q==",
+ "dev": true,
+ "dependencies": {
+ "spdx-exceptions": "^2.1.0",
+ "spdx-license-ids": "^3.0.0"
+ }
+ },
+ "node_modules/spdx-license-ids": {
+ "version": "3.0.17",
+ "resolved": "https://registry.npmjs.org/spdx-license-ids/-/spdx-license-ids-3.0.17.tgz",
+ "integrity": "sha512-sh8PWc/ftMqAAdFiBu6Fy6JUOYjqDJBJvIhpfDMyHrr0Rbp5liZqd4TjtQ/RgfLjKFZb+LMx5hpml5qOWy0qvg==",
+ "dev": true
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.1.3.tgz",
+ "integrity": "sha512-Oo+0REFV59/rz3gfJNKQiBlwfHaSESl1pcGyABQsnnIfWOFt6JNj5gCog2U6MLZ//IGYD+nA8nI+mTShREReaA==",
+ "dev": true
+ },
+ "node_modules/ssri": {
+ "version": "9.0.1",
+ "resolved": "https://registry.npmjs.org/ssri/-/ssri-9.0.1.tgz",
+ "integrity": "sha512-o57Wcn66jMQvfHG1FlYbWeZWW/dHZhJXjpIcTfXldXEk5nz5lStPo3mK0OJQfGR3RbZUlbISexbljkJzuEj/8Q==",
+ "dev": true,
+ "dependencies": {
+ "minipass": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/stack-utils": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/stack-utils/-/stack-utils-2.0.6.tgz",
+ "integrity": "sha512-XlkWvfIm6RmsWtNJx+uqtKLS8eqFbxUg0ZzLXqY0caEy9l7hruX8IpiDnjsLavoBgqCCR71TqWO8MaXYheJ3RQ==",
+ "dev": true,
+ "dependencies": {
+ "escape-string-regexp": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/stack-utils/node_modules/escape-string-regexp": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-2.0.0.tgz",
+ "integrity": "sha512-UpzcLCXolUWcNu5HtVMHYdXJjArjsF9C0aNnquZYY4uW/Vu0miy5YoWvbV345HauVvcAUnpRuhMMcqTcGOY2+w==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/stdout-stream": {
+ "version": "1.4.1",
+ "resolved": "https://registry.npmjs.org/stdout-stream/-/stdout-stream-1.4.1.tgz",
+ "integrity": "sha512-j4emi03KXqJWcIeF8eIXkjMFN1Cmb8gUlDYGeBALLPo5qdyTfA9bOtl8m33lRoC+vFMkP3gl0WsDr6+gzxbbTA==",
+ "dev": true,
+ "dependencies": {
+ "readable-stream": "^2.0.1"
+ }
+ },
+ "node_modules/stdout-stream/node_modules/readable-stream": {
+ "version": "2.3.8",
+ "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz",
+ "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==",
+ "dev": true,
+ "dependencies": {
+ "core-util-is": "~1.0.0",
+ "inherits": "~2.0.3",
+ "isarray": "~1.0.0",
+ "process-nextick-args": "~2.0.0",
+ "safe-buffer": "~5.1.1",
+ "string_decoder": "~1.1.1",
+ "util-deprecate": "~1.0.1"
+ }
+ },
+ "node_modules/stdout-stream/node_modules/safe-buffer": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz",
+ "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==",
+ "dev": true
+ },
+ "node_modules/stdout-stream/node_modules/string_decoder": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz",
+ "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "~5.1.0"
+ }
+ },
+ "node_modules/string_decoder": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
+ "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "~5.2.0"
+ }
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dev": true,
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dev": true,
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-indent": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/strip-indent/-/strip-indent-3.0.0.tgz",
+ "integrity": "sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==",
+ "dev": true,
+ "dependencies": {
+ "min-indent": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "dev": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/tar": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
+ "integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
+ "dev": true,
+ "dependencies": {
+ "chownr": "^2.0.0",
+ "fs-minipass": "^2.0.0",
+ "minipass": "^5.0.0",
+ "minizlib": "^2.1.1",
+ "mkdirp": "^1.0.3",
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/tar/node_modules/minipass": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
+ "integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==",
+ "dev": true
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "devOptional": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/tosource": {
+ "version": "2.0.0-alpha.3",
+ "resolved": "https://registry.npmjs.org/tosource/-/tosource-2.0.0-alpha.3.tgz",
+ "integrity": "sha512-KAB2lrSS48y91MzFPFuDg4hLbvDiyTjOVgaK7Erw+5AmZXNq4sFRVn8r6yxSLuNs15PaokrDRpS61ERY9uZOug==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/trim-newlines": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/trim-newlines/-/trim-newlines-3.0.1.tgz",
+ "integrity": "sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/true-case-path": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/true-case-path/-/true-case-path-2.2.1.tgz",
+ "integrity": "sha512-0z3j8R7MCjy10kc/g+qg7Ln3alJTodw9aDuVWZa3uiWqfuBMKeAeP2ocWcxoyM3D73yz3Jt/Pu4qPr4wHSdB/Q==",
+ "dev": true
+ },
+ "node_modules/tslib": {
+ "version": "1.14.1",
+ "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
+ "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
+ "dev": true
+ },
+ "node_modules/tsutils": {
+ "version": "3.21.0",
+ "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-3.21.0.tgz",
+ "integrity": "sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==",
+ "dev": true,
+ "dependencies": {
+ "tslib": "^1.8.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ },
+ "peerDependencies": {
+ "typescript": ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dev": true,
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/typescript": {
+ "version": "4.9.5",
+ "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz",
+ "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==",
+ "devOptional": true,
+ "bin": {
+ "tsc": "bin/tsc",
+ "tsserver": "bin/tsserver"
+ },
+ "engines": {
+ "node": ">=4.2.0"
+ }
+ },
+ "node_modules/undici-types": {
+ "version": "5.26.5",
+ "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
+ "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
+ "devOptional": true
+ },
+ "node_modules/unique-filename": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/unique-filename/-/unique-filename-2.0.1.tgz",
+ "integrity": "sha512-ODWHtkkdx3IAR+veKxFV+VBkUMcN+FaqzUUd7IZzt+0zhDZFPFxhlqwPF3YQvMHx1TD0tdgYl+kuPnJ8E6ql7A==",
+ "dev": true,
+ "dependencies": {
+ "unique-slug": "^3.0.0"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/unique-slug": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/unique-slug/-/unique-slug-3.0.0.tgz",
+ "integrity": "sha512-8EyMynh679x/0gqE9fT9oilG+qEt+ibFyqjuVTsZn1+CMxH+XLlpvr2UZx4nVcCwTpx81nICr2JQFkM+HPLq4w==",
+ "dev": true,
+ "dependencies": {
+ "imurmurhash": "^0.1.4"
+ },
+ "engines": {
+ "node": "^12.13.0 || ^14.15.0 || >=16.0.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dev": true,
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/util-deprecate": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
+ "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
+ "dev": true
+ },
+ "node_modules/uuid": {
+ "version": "8.3.2",
+ "resolved": "https://registry.npmjs.org/uuid/-/uuid-8.3.2.tgz",
+ "integrity": "sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==",
+ "bin": {
+ "uuid": "dist/bin/uuid"
+ }
+ },
+ "node_modules/validate-npm-package-license": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz",
+ "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==",
+ "dev": true,
+ "dependencies": {
+ "spdx-correct": "^3.0.0",
+ "spdx-expression-parse": "^3.0.0"
+ }
+ },
+ "node_modules/vite": {
+ "version": "5.1.7",
+ "resolved": "https://registry.npmjs.org/vite/-/vite-5.1.7.tgz",
+ "integrity": "sha512-sgnEEFTZYMui/sTlH1/XEnVNHMujOahPLGMxn1+5sIT45Xjng1Ec1K78jRP15dSmVgg5WBin9yO81j3o9OxofA==",
+ "dependencies": {
+ "esbuild": "^0.19.3",
+ "postcss": "^8.4.35",
+ "rollup": "^4.2.0"
+ },
+ "bin": {
+ "vite": "bin/vite.js"
+ },
+ "engines": {
+ "node": "^18.0.0 || >=20.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/vitejs/vite?sponsor=1"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.3"
+ },
+ "peerDependencies": {
+ "@types/node": "^18.0.0 || >=20.0.0",
+ "less": "*",
+ "lightningcss": "^1.21.0",
+ "sass": "*",
+ "stylus": "*",
+ "sugarss": "*",
+ "terser": "^5.4.0"
+ },
+ "peerDependenciesMeta": {
+ "@types/node": {
+ "optional": true
+ },
+ "less": {
+ "optional": true
+ },
+ "lightningcss": {
+ "optional": true
+ },
+ "sass": {
+ "optional": true
+ },
+ "stylus": {
+ "optional": true
+ },
+ "sugarss": {
+ "optional": true
+ },
+ "terser": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue": {
+ "version": "3.4.21",
+ "resolved": "https://registry.npmjs.org/vue/-/vue-3.4.21.tgz",
+ "integrity": "sha512-5hjyV/jLEIKD/jYl4cavMcnzKwjMKohureP8ejn3hhEjwhWIhWeuzL2kJAjzl/WyVsgPY56Sy4Z40C3lVshxXA==",
+ "dependencies": {
+ "@vue/compiler-dom": "3.4.21",
+ "@vue/compiler-sfc": "3.4.21",
+ "@vue/runtime-dom": "3.4.21",
+ "@vue/server-renderer": "3.4.21",
+ "@vue/shared": "3.4.21"
+ },
+ "peerDependencies": {
+ "typescript": "*"
+ },
+ "peerDependenciesMeta": {
+ "typescript": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/vue-eslint-parser": {
+ "version": "9.4.2",
+ "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.4.2.tgz",
+ "integrity": "sha512-Ry9oiGmCAK91HrKMtCrKFWmSFWvYkpGglCeFAIqDdr9zdXmMMpJOmUJS7WWsW7fX81h6mwHmUZCQQ1E0PkSwYQ==",
+ "dev": true,
+ "dependencies": {
+ "debug": "^4.3.4",
+ "eslint-scope": "^7.1.1",
+ "eslint-visitor-keys": "^3.3.0",
+ "espree": "^9.3.1",
+ "esquery": "^1.4.0",
+ "lodash": "^4.17.21",
+ "semver": "^7.3.6"
+ },
+ "engines": {
+ "node": "^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ },
+ "peerDependencies": {
+ "eslint": ">=6.0.0"
+ }
+ },
+ "node_modules/vue-eslint-parser/node_modules/eslint-scope": {
+ "version": "7.2.2",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz",
+ "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==",
+ "dev": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/vue-eslint-parser/node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "dev": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/vue-router": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.3.0.tgz",
+ "integrity": "sha512-dqUcs8tUeG+ssgWhcPbjHvazML16Oga5w34uCUmsk7i0BcnskoLGwjpa15fqMr2Fa5JgVBrdL2MEgqz6XZ/6IQ==",
+ "dependencies": {
+ "@vue/devtools-api": "^6.5.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/posva"
+ },
+ "peerDependencies": {
+ "vue": "^3.2.0"
+ }
+ },
+ "node_modules/vue-slider-component": {
+ "version": "4.1.0-beta.7",
+ "resolved": "https://registry.npmjs.org/vue-slider-component/-/vue-slider-component-4.1.0-beta.7.tgz",
+ "integrity": "sha512-Qb7K920ZG7PoQswoF6Ias+i3W2rd3k4fpk04JUl82kEUcN86Yg6et7bVSKWt/7VpQe8a5IT3BqCKSCOZ7AJgCA=="
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dev": true,
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/wide-align": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
+ "integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^1.0.2 || 2 || 3 || 4"
+ }
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
+ "dev": true
+ },
+ "node_modules/xml-name-validator": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz",
+ "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "dev": true
+ },
+ "node_modules/yargs": {
+ "version": "17.7.2",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-17.7.2.tgz",
+ "integrity": "sha512-7dSzzRQ++CKnNI/krKnYRV7JKKPUXMEh61soaHKg9mrWEhzFWhFnxPxGl+69cD1Ou63C13NUPCnmIcrvqCuM6w==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^8.0.1",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.3",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^21.1.1"
+ },
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "20.2.9",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.9.tgz",
+ "integrity": "sha512-y11nGElTIV+CT3Zv9t7VKl+Q3hTQoT9a1Qzezhhl6Rp21gJ/IVTW7Z3y9EWXhuUBC2Shnf+DX0antecpAwSP8w==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs/node_modules/yargs-parser": {
+ "version": "21.1.1",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-21.1.1.tgz",
+ "integrity": "sha512-tVpsJW7DdjecAiFpbIB1e3qxIQsE6NoPc5/eTdrbbIC4h0LVsWhnoa3g+m2HclBIujHzsxZ4VJVA+GUuc2/LBw==",
+ "dev": true,
+ "engines": {
+ "node": ">=12"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ }
+}
diff --git a/client/package.json b/client/package.json
new file mode 100644
index 0000000..e1043e4
--- /dev/null
+++ b/client/package.json
@@ -0,0 +1,41 @@
+{
+ "name": "galahad-client",
+ "version": "0.0.2-ALPHA-SNAPSHOT",
+ "private": true,
+ "scripts": {
+ "dev": "vite",
+ "build": "vite build --base=/galahad/",
+ "serve": "vite preview",
+ "lint": "eslint --ext .js,.vue --ignore-path .gitignore --fix src"
+ },
+ "dependencies": {
+ "@types/uuid": "^8.3.4",
+ "@vitejs/plugin-vue": "^5.0.4",
+ "axios": "^1.6.2",
+ "buffer": "^6.0.3",
+ "content-disposition": "^0.5.4",
+ "js-yaml": "^4.1.0",
+ "json-loader": "^0.5.7",
+ "pinia": "^2.0.16",
+ "safe-buffer": "^5.2.1",
+ "uuid": "^8.3.2",
+ "vue": "^3.2.37",
+ "vue-router": "^4.1.2",
+ "vue-slider-component": "^4.1.0-beta.1"
+ },
+ "devDependencies": {
+ "@rollup/plugin-yaml": "^4.0.1",
+ "@types/jest": "^28.1.6",
+ "@types/js-yaml": "^4.0.5",
+ "@typescript-eslint/eslint-plugin": "^5.30.7",
+ "@typescript-eslint/parser": "^5.30.7",
+ "@vue/eslint-config-typescript": "^11.0.0",
+ "eslint": "^8.20.0",
+ "eslint-plugin-vue": "^9.2.0",
+ "mutationobserver-shim": "^0.3.7",
+ "node-sass": "^9.0.0",
+ "sass": "^1.53.0",
+ "typescript": "^4.7.4",
+ "vite": "^5.1.7"
+ }
+}
diff --git a/client/public/favicon.ico b/client/public/favicon.ico
new file mode 100644
index 0000000..8357ba8
Binary files /dev/null and b/client/public/favicon.ico differ
diff --git a/client/public/font-awesome/css/font-awesome.min.css b/client/public/font-awesome/css/font-awesome.min.css
new file mode 100644
index 0000000..540440c
--- /dev/null
+++ b/client/public/font-awesome/css/font-awesome.min.css
@@ -0,0 +1,4 @@
+/*!
+ * Font Awesome 4.7.0 by @davegandy - http://fontawesome.io - @fontawesome
+ * License - http://fontawesome.io/license (Font: SIL OFL 1.1, CSS: MIT License)
+ */@font-face{font-family:'FontAwesome';src:url('../fonts/fontawesome-webfont.eot?v=4.7.0');src:url('../fonts/fontawesome-webfont.eot?#iefix&v=4.7.0') format('embedded-opentype'),url('../fonts/fontawesome-webfont.woff2?v=4.7.0') format('woff2'),url('../fonts/fontawesome-webfont.woff?v=4.7.0') format('woff'),url('../fonts/fontawesome-webfont.ttf?v=4.7.0') format('truetype'),url('../fonts/fontawesome-webfont.svg?v=4.7.0#fontawesomeregular') format('svg');font-weight:normal;font-style:normal}.fa{display:inline-block;font:normal normal normal 14px/1 FontAwesome;font-size:inherit;text-rendering:auto;-webkit-font-smoothing:antialiased;-moz-osx-font-smoothing:grayscale}.fa-lg{font-size:1.33333333em;line-height:.75em;vertical-align:-15%}.fa-2x{font-size:2em}.fa-3x{font-size:3em}.fa-4x{font-size:4em}.fa-5x{font-size:5em}.fa-fw{width:1.28571429em;text-align:center}.fa-ul{padding-left:0;margin-left:2.14285714em;list-style-type:none}.fa-ul>li{position:relative}.fa-li{position:absolute;left:-2.14285714em;width:2.14285714em;top:.14285714em;text-align:center}.fa-li.fa-lg{left:-1.85714286em}.fa-border{padding:.2em .25em .15em;border:solid .08em #eee;border-radius:.1em}.fa-pull-left{float:left}.fa-pull-right{float:right}.fa.fa-pull-left{margin-right:.3em}.fa.fa-pull-right{margin-left:.3em}.pull-right{float:right}.pull-left{float:left}.fa.pull-left{margin-right:.3em}.fa.pull-right{margin-left:.3em}.fa-spin{-webkit-animation:fa-spin 2s infinite linear;animation:fa-spin 2s infinite linear}.fa-pulse{-webkit-animation:fa-spin 1s infinite steps(8);animation:fa-spin 1s infinite steps(8)}@-webkit-keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}@keyframes fa-spin{0%{-webkit-transform:rotate(0deg);transform:rotate(0deg)}100%{-webkit-transform:rotate(359deg);transform:rotate(359deg)}}.fa-rotate-90{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=1)";-webkit-transform:rotate(90deg);-ms-transform:rotate(90deg);transform:rotate(90deg)}.fa-rotate-180{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2)";-webkit-transform:rotate(180deg);-ms-transform:rotate(180deg);transform:rotate(180deg)}.fa-rotate-270{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=3)";-webkit-transform:rotate(270deg);-ms-transform:rotate(270deg);transform:rotate(270deg)}.fa-flip-horizontal{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=0, mirror=1)";-webkit-transform:scale(-1, 1);-ms-transform:scale(-1, 1);transform:scale(-1, 1)}.fa-flip-vertical{-ms-filter:"progid:DXImageTransform.Microsoft.BasicImage(rotation=2, mirror=1)";-webkit-transform:scale(1, -1);-ms-transform:scale(1, -1);transform:scale(1, -1)}:root .fa-rotate-90,:root .fa-rotate-180,:root .fa-rotate-270,:root .fa-flip-horizontal,:root .fa-flip-vertical{filter:none}.fa-stack{position:relative;display:inline-block;width:2em;height:2em;line-height:2em;vertical-align:middle}.fa-stack-1x,.fa-stack-2x{position:absolute;left:0;width:100%;text-align:center}.fa-stack-1x{line-height:inherit}.fa-stack-2x{font-size:2em}.fa-inverse{color:#fff}.fa-glass:before{content:"\f000"}.fa-music:before{content:"\f001"}.fa-search:before{content:"\f002"}.fa-envelope-o:before{content:"\f003"}.fa-heart:before{content:"\f004"}.fa-star:before{content:"\f005"}.fa-star-o:before{content:"\f006"}.fa-user:before{content:"\f007"}.fa-film:before{content:"\f008"}.fa-th-large:before{content:"\f009"}.fa-th:before{content:"\f00a"}.fa-th-list:before{content:"\f00b"}.fa-check:before{content:"\f00c"}.fa-remove:before,.fa-close:before,.fa-times:before{content:"\f00d"}.fa-search-plus:before{content:"\f00e"}.fa-search-minus:before{content:"\f010"}.fa-power-off:before{content:"\f011"}.fa-signal:before{content:"\f012"}.fa-gear:before,.fa-cog:before{content:"\f013"}.fa-trash-o:before{content:"\f014"}.fa-home:before{content:"\f015"}.fa-file-o:before{content:"\f016"}.fa-clock-o:before{content:"\f017"}.fa-road:before{content:"\f018"}.fa-download:before{content:"\f019"}.fa-arrow-circle-o-down:before{content:"\f01a"}.fa-arrow-circle-o-up:before{content:"\f01b"}.fa-inbox:before{content:"\f01c"}.fa-play-circle-o:before{content:"\f01d"}.fa-rotate-right:before,.fa-repeat:before{content:"\f01e"}.fa-refresh:before{content:"\f021"}.fa-list-alt:before{content:"\f022"}.fa-lock:before{content:"\f023"}.fa-flag:before{content:"\f024"}.fa-headphones:before{content:"\f025"}.fa-volume-off:before{content:"\f026"}.fa-volume-down:before{content:"\f027"}.fa-volume-up:before{content:"\f028"}.fa-qrcode:before{content:"\f029"}.fa-barcode:before{content:"\f02a"}.fa-tag:before{content:"\f02b"}.fa-tags:before{content:"\f02c"}.fa-book:before{content:"\f02d"}.fa-bookmark:before{content:"\f02e"}.fa-print:before{content:"\f02f"}.fa-camera:before{content:"\f030"}.fa-font:before{content:"\f031"}.fa-bold:before{content:"\f032"}.fa-italic:before{content:"\f033"}.fa-text-height:before{content:"\f034"}.fa-text-width:before{content:"\f035"}.fa-align-left:before{content:"\f036"}.fa-align-center:before{content:"\f037"}.fa-align-right:before{content:"\f038"}.fa-align-justify:before{content:"\f039"}.fa-list:before{content:"\f03a"}.fa-dedent:before,.fa-outdent:before{content:"\f03b"}.fa-indent:before{content:"\f03c"}.fa-video-camera:before{content:"\f03d"}.fa-photo:before,.fa-image:before,.fa-picture-o:before{content:"\f03e"}.fa-pencil:before{content:"\f040"}.fa-map-marker:before{content:"\f041"}.fa-adjust:before{content:"\f042"}.fa-tint:before{content:"\f043"}.fa-edit:before,.fa-pencil-square-o:before{content:"\f044"}.fa-share-square-o:before{content:"\f045"}.fa-check-square-o:before{content:"\f046"}.fa-arrows:before{content:"\f047"}.fa-step-backward:before{content:"\f048"}.fa-fast-backward:before{content:"\f049"}.fa-backward:before{content:"\f04a"}.fa-play:before{content:"\f04b"}.fa-pause:before{content:"\f04c"}.fa-stop:before{content:"\f04d"}.fa-forward:before{content:"\f04e"}.fa-fast-forward:before{content:"\f050"}.fa-step-forward:before{content:"\f051"}.fa-eject:before{content:"\f052"}.fa-chevron-left:before{content:"\f053"}.fa-chevron-right:before{content:"\f054"}.fa-plus-circle:before{content:"\f055"}.fa-minus-circle:before{content:"\f056"}.fa-times-circle:before{content:"\f057"}.fa-check-circle:before{content:"\f058"}.fa-question-circle:before{content:"\f059"}.fa-info-circle:before{content:"\f05a"}.fa-crosshairs:before{content:"\f05b"}.fa-times-circle-o:before{content:"\f05c"}.fa-check-circle-o:before{content:"\f05d"}.fa-ban:before{content:"\f05e"}.fa-arrow-left:before{content:"\f060"}.fa-arrow-right:before{content:"\f061"}.fa-arrow-up:before{content:"\f062"}.fa-arrow-down:before{content:"\f063"}.fa-mail-forward:before,.fa-share:before{content:"\f064"}.fa-expand:before{content:"\f065"}.fa-compress:before{content:"\f066"}.fa-plus:before{content:"\f067"}.fa-minus:before{content:"\f068"}.fa-asterisk:before{content:"\f069"}.fa-exclamation-circle:before{content:"\f06a"}.fa-gift:before{content:"\f06b"}.fa-leaf:before{content:"\f06c"}.fa-fire:before{content:"\f06d"}.fa-eye:before{content:"\f06e"}.fa-eye-slash:before{content:"\f070"}.fa-warning:before,.fa-exclamation-triangle:before{content:"\f071"}.fa-plane:before{content:"\f072"}.fa-calendar:before{content:"\f073"}.fa-random:before{content:"\f074"}.fa-comment:before{content:"\f075"}.fa-magnet:before{content:"\f076"}.fa-chevron-up:before{content:"\f077"}.fa-chevron-down:before{content:"\f078"}.fa-retweet:before{content:"\f079"}.fa-shopping-cart:before{content:"\f07a"}.fa-folder:before{content:"\f07b"}.fa-folder-open:before{content:"\f07c"}.fa-arrows-v:before{content:"\f07d"}.fa-arrows-h:before{content:"\f07e"}.fa-bar-chart-o:before,.fa-bar-chart:before{content:"\f080"}.fa-twitter-square:before{content:"\f081"}.fa-facebook-square:before{content:"\f082"}.fa-camera-retro:before{content:"\f083"}.fa-key:before{content:"\f084"}.fa-gears:before,.fa-cogs:before{content:"\f085"}.fa-comments:before{content:"\f086"}.fa-thumbs-o-up:before{content:"\f087"}.fa-thumbs-o-down:before{content:"\f088"}.fa-star-half:before{content:"\f089"}.fa-heart-o:before{content:"\f08a"}.fa-sign-out:before{content:"\f08b"}.fa-linkedin-square:before{content:"\f08c"}.fa-thumb-tack:before{content:"\f08d"}.fa-external-link:before{content:"\f08e"}.fa-sign-in:before{content:"\f090"}.fa-trophy:before{content:"\f091"}.fa-github-square:before{content:"\f092"}.fa-upload:before{content:"\f093"}.fa-lemon-o:before{content:"\f094"}.fa-phone:before{content:"\f095"}.fa-square-o:before{content:"\f096"}.fa-bookmark-o:before{content:"\f097"}.fa-phone-square:before{content:"\f098"}.fa-twitter:before{content:"\f099"}.fa-facebook-f:before,.fa-facebook:before{content:"\f09a"}.fa-github:before{content:"\f09b"}.fa-unlock:before{content:"\f09c"}.fa-credit-card:before{content:"\f09d"}.fa-feed:before,.fa-rss:before{content:"\f09e"}.fa-hdd-o:before{content:"\f0a0"}.fa-bullhorn:before{content:"\f0a1"}.fa-bell:before{content:"\f0f3"}.fa-certificate:before{content:"\f0a3"}.fa-hand-o-right:before{content:"\f0a4"}.fa-hand-o-left:before{content:"\f0a5"}.fa-hand-o-up:before{content:"\f0a6"}.fa-hand-o-down:before{content:"\f0a7"}.fa-arrow-circle-left:before{content:"\f0a8"}.fa-arrow-circle-right:before{content:"\f0a9"}.fa-arrow-circle-up:before{content:"\f0aa"}.fa-arrow-circle-down:before{content:"\f0ab"}.fa-globe:before{content:"\f0ac"}.fa-wrench:before{content:"\f0ad"}.fa-tasks:before{content:"\f0ae"}.fa-filter:before{content:"\f0b0"}.fa-briefcase:before{content:"\f0b1"}.fa-arrows-alt:before{content:"\f0b2"}.fa-group:before,.fa-users:before{content:"\f0c0"}.fa-chain:before,.fa-link:before{content:"\f0c1"}.fa-cloud:before{content:"\f0c2"}.fa-flask:before{content:"\f0c3"}.fa-cut:before,.fa-scissors:before{content:"\f0c4"}.fa-copy:before,.fa-files-o:before{content:"\f0c5"}.fa-paperclip:before{content:"\f0c6"}.fa-save:before,.fa-floppy-o:before{content:"\f0c7"}.fa-square:before{content:"\f0c8"}.fa-navicon:before,.fa-reorder:before,.fa-bars:before{content:"\f0c9"}.fa-list-ul:before{content:"\f0ca"}.fa-list-ol:before{content:"\f0cb"}.fa-strikethrough:before{content:"\f0cc"}.fa-underline:before{content:"\f0cd"}.fa-table:before{content:"\f0ce"}.fa-magic:before{content:"\f0d0"}.fa-truck:before{content:"\f0d1"}.fa-pinterest:before{content:"\f0d2"}.fa-pinterest-square:before{content:"\f0d3"}.fa-google-plus-square:before{content:"\f0d4"}.fa-google-plus:before{content:"\f0d5"}.fa-money:before{content:"\f0d6"}.fa-caret-down:before{content:"\f0d7"}.fa-caret-up:before{content:"\f0d8"}.fa-caret-left:before{content:"\f0d9"}.fa-caret-right:before{content:"\f0da"}.fa-columns:before{content:"\f0db"}.fa-unsorted:before,.fa-sort:before{content:"\f0dc"}.fa-sort-down:before,.fa-sort-desc:before{content:"\f0dd"}.fa-sort-up:before,.fa-sort-asc:before{content:"\f0de"}.fa-envelope:before{content:"\f0e0"}.fa-linkedin:before{content:"\f0e1"}.fa-rotate-left:before,.fa-undo:before{content:"\f0e2"}.fa-legal:before,.fa-gavel:before{content:"\f0e3"}.fa-dashboard:before,.fa-tachometer:before{content:"\f0e4"}.fa-comment-o:before{content:"\f0e5"}.fa-comments-o:before{content:"\f0e6"}.fa-flash:before,.fa-bolt:before{content:"\f0e7"}.fa-sitemap:before{content:"\f0e8"}.fa-umbrella:before{content:"\f0e9"}.fa-paste:before,.fa-clipboard:before{content:"\f0ea"}.fa-lightbulb-o:before{content:"\f0eb"}.fa-exchange:before{content:"\f0ec"}.fa-cloud-download:before{content:"\f0ed"}.fa-cloud-upload:before{content:"\f0ee"}.fa-user-md:before{content:"\f0f0"}.fa-stethoscope:before{content:"\f0f1"}.fa-suitcase:before{content:"\f0f2"}.fa-bell-o:before{content:"\f0a2"}.fa-coffee:before{content:"\f0f4"}.fa-cutlery:before{content:"\f0f5"}.fa-file-text-o:before{content:"\f0f6"}.fa-building-o:before{content:"\f0f7"}.fa-hospital-o:before{content:"\f0f8"}.fa-ambulance:before{content:"\f0f9"}.fa-medkit:before{content:"\f0fa"}.fa-fighter-jet:before{content:"\f0fb"}.fa-beer:before{content:"\f0fc"}.fa-h-square:before{content:"\f0fd"}.fa-plus-square:before{content:"\f0fe"}.fa-angle-double-left:before{content:"\f100"}.fa-angle-double-right:before{content:"\f101"}.fa-angle-double-up:before{content:"\f102"}.fa-angle-double-down:before{content:"\f103"}.fa-angle-left:before{content:"\f104"}.fa-angle-right:before{content:"\f105"}.fa-angle-up:before{content:"\f106"}.fa-angle-down:before{content:"\f107"}.fa-desktop:before{content:"\f108"}.fa-laptop:before{content:"\f109"}.fa-tablet:before{content:"\f10a"}.fa-mobile-phone:before,.fa-mobile:before{content:"\f10b"}.fa-circle-o:before{content:"\f10c"}.fa-quote-left:before{content:"\f10d"}.fa-quote-right:before{content:"\f10e"}.fa-spinner:before{content:"\f110"}.fa-circle:before{content:"\f111"}.fa-mail-reply:before,.fa-reply:before{content:"\f112"}.fa-github-alt:before{content:"\f113"}.fa-folder-o:before{content:"\f114"}.fa-folder-open-o:before{content:"\f115"}.fa-smile-o:before{content:"\f118"}.fa-frown-o:before{content:"\f119"}.fa-meh-o:before{content:"\f11a"}.fa-gamepad:before{content:"\f11b"}.fa-keyboard-o:before{content:"\f11c"}.fa-flag-o:before{content:"\f11d"}.fa-flag-checkered:before{content:"\f11e"}.fa-terminal:before{content:"\f120"}.fa-code:before{content:"\f121"}.fa-mail-reply-all:before,.fa-reply-all:before{content:"\f122"}.fa-star-half-empty:before,.fa-star-half-full:before,.fa-star-half-o:before{content:"\f123"}.fa-location-arrow:before{content:"\f124"}.fa-crop:before{content:"\f125"}.fa-code-fork:before{content:"\f126"}.fa-unlink:before,.fa-chain-broken:before{content:"\f127"}.fa-question:before{content:"\f128"}.fa-info:before{content:"\f129"}.fa-exclamation:before{content:"\f12a"}.fa-superscript:before{content:"\f12b"}.fa-subscript:before{content:"\f12c"}.fa-eraser:before{content:"\f12d"}.fa-puzzle-piece:before{content:"\f12e"}.fa-microphone:before{content:"\f130"}.fa-microphone-slash:before{content:"\f131"}.fa-shield:before{content:"\f132"}.fa-calendar-o:before{content:"\f133"}.fa-fire-extinguisher:before{content:"\f134"}.fa-rocket:before{content:"\f135"}.fa-maxcdn:before{content:"\f136"}.fa-chevron-circle-left:before{content:"\f137"}.fa-chevron-circle-right:before{content:"\f138"}.fa-chevron-circle-up:before{content:"\f139"}.fa-chevron-circle-down:before{content:"\f13a"}.fa-html5:before{content:"\f13b"}.fa-css3:before{content:"\f13c"}.fa-anchor:before{content:"\f13d"}.fa-unlock-alt:before{content:"\f13e"}.fa-bullseye:before{content:"\f140"}.fa-ellipsis-h:before{content:"\f141"}.fa-ellipsis-v:before{content:"\f142"}.fa-rss-square:before{content:"\f143"}.fa-play-circle:before{content:"\f144"}.fa-ticket:before{content:"\f145"}.fa-minus-square:before{content:"\f146"}.fa-minus-square-o:before{content:"\f147"}.fa-level-up:before{content:"\f148"}.fa-level-down:before{content:"\f149"}.fa-check-square:before{content:"\f14a"}.fa-pencil-square:before{content:"\f14b"}.fa-external-link-square:before{content:"\f14c"}.fa-share-square:before{content:"\f14d"}.fa-compass:before{content:"\f14e"}.fa-toggle-down:before,.fa-caret-square-o-down:before{content:"\f150"}.fa-toggle-up:before,.fa-caret-square-o-up:before{content:"\f151"}.fa-toggle-right:before,.fa-caret-square-o-right:before{content:"\f152"}.fa-euro:before,.fa-eur:before{content:"\f153"}.fa-gbp:before{content:"\f154"}.fa-dollar:before,.fa-usd:before{content:"\f155"}.fa-rupee:before,.fa-inr:before{content:"\f156"}.fa-cny:before,.fa-rmb:before,.fa-yen:before,.fa-jpy:before{content:"\f157"}.fa-ruble:before,.fa-rouble:before,.fa-rub:before{content:"\f158"}.fa-won:before,.fa-krw:before{content:"\f159"}.fa-bitcoin:before,.fa-btc:before{content:"\f15a"}.fa-file:before{content:"\f15b"}.fa-file-text:before{content:"\f15c"}.fa-sort-alpha-asc:before{content:"\f15d"}.fa-sort-alpha-desc:before{content:"\f15e"}.fa-sort-amount-asc:before{content:"\f160"}.fa-sort-amount-desc:before{content:"\f161"}.fa-sort-numeric-asc:before{content:"\f162"}.fa-sort-numeric-desc:before{content:"\f163"}.fa-thumbs-up:before{content:"\f164"}.fa-thumbs-down:before{content:"\f165"}.fa-youtube-square:before{content:"\f166"}.fa-youtube:before{content:"\f167"}.fa-xing:before{content:"\f168"}.fa-xing-square:before{content:"\f169"}.fa-youtube-play:before{content:"\f16a"}.fa-dropbox:before{content:"\f16b"}.fa-stack-overflow:before{content:"\f16c"}.fa-instagram:before{content:"\f16d"}.fa-flickr:before{content:"\f16e"}.fa-adn:before{content:"\f170"}.fa-bitbucket:before{content:"\f171"}.fa-bitbucket-square:before{content:"\f172"}.fa-tumblr:before{content:"\f173"}.fa-tumblr-square:before{content:"\f174"}.fa-long-arrow-down:before{content:"\f175"}.fa-long-arrow-up:before{content:"\f176"}.fa-long-arrow-left:before{content:"\f177"}.fa-long-arrow-right:before{content:"\f178"}.fa-apple:before{content:"\f179"}.fa-windows:before{content:"\f17a"}.fa-android:before{content:"\f17b"}.fa-linux:before{content:"\f17c"}.fa-dribbble:before{content:"\f17d"}.fa-skype:before{content:"\f17e"}.fa-foursquare:before{content:"\f180"}.fa-trello:before{content:"\f181"}.fa-female:before{content:"\f182"}.fa-male:before{content:"\f183"}.fa-gittip:before,.fa-gratipay:before{content:"\f184"}.fa-sun-o:before{content:"\f185"}.fa-moon-o:before{content:"\f186"}.fa-archive:before{content:"\f187"}.fa-bug:before{content:"\f188"}.fa-vk:before{content:"\f189"}.fa-weibo:before{content:"\f18a"}.fa-renren:before{content:"\f18b"}.fa-pagelines:before{content:"\f18c"}.fa-stack-exchange:before{content:"\f18d"}.fa-arrow-circle-o-right:before{content:"\f18e"}.fa-arrow-circle-o-left:before{content:"\f190"}.fa-toggle-left:before,.fa-caret-square-o-left:before{content:"\f191"}.fa-dot-circle-o:before{content:"\f192"}.fa-wheelchair:before{content:"\f193"}.fa-vimeo-square:before{content:"\f194"}.fa-turkish-lira:before,.fa-try:before{content:"\f195"}.fa-plus-square-o:before{content:"\f196"}.fa-space-shuttle:before{content:"\f197"}.fa-slack:before{content:"\f198"}.fa-envelope-square:before{content:"\f199"}.fa-wordpress:before{content:"\f19a"}.fa-openid:before{content:"\f19b"}.fa-institution:before,.fa-bank:before,.fa-university:before{content:"\f19c"}.fa-mortar-board:before,.fa-graduation-cap:before{content:"\f19d"}.fa-yahoo:before{content:"\f19e"}.fa-google:before{content:"\f1a0"}.fa-reddit:before{content:"\f1a1"}.fa-reddit-square:before{content:"\f1a2"}.fa-stumbleupon-circle:before{content:"\f1a3"}.fa-stumbleupon:before{content:"\f1a4"}.fa-delicious:before{content:"\f1a5"}.fa-digg:before{content:"\f1a6"}.fa-pied-piper-pp:before{content:"\f1a7"}.fa-pied-piper-alt:before{content:"\f1a8"}.fa-drupal:before{content:"\f1a9"}.fa-joomla:before{content:"\f1aa"}.fa-language:before{content:"\f1ab"}.fa-fax:before{content:"\f1ac"}.fa-building:before{content:"\f1ad"}.fa-child:before{content:"\f1ae"}.fa-paw:before{content:"\f1b0"}.fa-spoon:before{content:"\f1b1"}.fa-cube:before{content:"\f1b2"}.fa-cubes:before{content:"\f1b3"}.fa-behance:before{content:"\f1b4"}.fa-behance-square:before{content:"\f1b5"}.fa-steam:before{content:"\f1b6"}.fa-steam-square:before{content:"\f1b7"}.fa-recycle:before{content:"\f1b8"}.fa-automobile:before,.fa-car:before{content:"\f1b9"}.fa-cab:before,.fa-taxi:before{content:"\f1ba"}.fa-tree:before{content:"\f1bb"}.fa-spotify:before{content:"\f1bc"}.fa-deviantart:before{content:"\f1bd"}.fa-soundcloud:before{content:"\f1be"}.fa-database:before{content:"\f1c0"}.fa-file-pdf-o:before{content:"\f1c1"}.fa-file-word-o:before{content:"\f1c2"}.fa-file-excel-o:before{content:"\f1c3"}.fa-file-powerpoint-o:before{content:"\f1c4"}.fa-file-photo-o:before,.fa-file-picture-o:before,.fa-file-image-o:before{content:"\f1c5"}.fa-file-zip-o:before,.fa-file-archive-o:before{content:"\f1c6"}.fa-file-sound-o:before,.fa-file-audio-o:before{content:"\f1c7"}.fa-file-movie-o:before,.fa-file-video-o:before{content:"\f1c8"}.fa-file-code-o:before{content:"\f1c9"}.fa-vine:before{content:"\f1ca"}.fa-codepen:before{content:"\f1cb"}.fa-jsfiddle:before{content:"\f1cc"}.fa-life-bouy:before,.fa-life-buoy:before,.fa-life-saver:before,.fa-support:before,.fa-life-ring:before{content:"\f1cd"}.fa-circle-o-notch:before{content:"\f1ce"}.fa-ra:before,.fa-resistance:before,.fa-rebel:before{content:"\f1d0"}.fa-ge:before,.fa-empire:before{content:"\f1d1"}.fa-git-square:before{content:"\f1d2"}.fa-git:before{content:"\f1d3"}.fa-y-combinator-square:before,.fa-yc-square:before,.fa-hacker-news:before{content:"\f1d4"}.fa-tencent-weibo:before{content:"\f1d5"}.fa-qq:before{content:"\f1d6"}.fa-wechat:before,.fa-weixin:before{content:"\f1d7"}.fa-send:before,.fa-paper-plane:before{content:"\f1d8"}.fa-send-o:before,.fa-paper-plane-o:before{content:"\f1d9"}.fa-history:before{content:"\f1da"}.fa-circle-thin:before{content:"\f1db"}.fa-header:before{content:"\f1dc"}.fa-paragraph:before{content:"\f1dd"}.fa-sliders:before{content:"\f1de"}.fa-share-alt:before{content:"\f1e0"}.fa-share-alt-square:before{content:"\f1e1"}.fa-bomb:before{content:"\f1e2"}.fa-soccer-ball-o:before,.fa-futbol-o:before{content:"\f1e3"}.fa-tty:before{content:"\f1e4"}.fa-binoculars:before{content:"\f1e5"}.fa-plug:before{content:"\f1e6"}.fa-slideshare:before{content:"\f1e7"}.fa-twitch:before{content:"\f1e8"}.fa-yelp:before{content:"\f1e9"}.fa-newspaper-o:before{content:"\f1ea"}.fa-wifi:before{content:"\f1eb"}.fa-calculator:before{content:"\f1ec"}.fa-paypal:before{content:"\f1ed"}.fa-google-wallet:before{content:"\f1ee"}.fa-cc-visa:before{content:"\f1f0"}.fa-cc-mastercard:before{content:"\f1f1"}.fa-cc-discover:before{content:"\f1f2"}.fa-cc-amex:before{content:"\f1f3"}.fa-cc-paypal:before{content:"\f1f4"}.fa-cc-stripe:before{content:"\f1f5"}.fa-bell-slash:before{content:"\f1f6"}.fa-bell-slash-o:before{content:"\f1f7"}.fa-trash:before{content:"\f1f8"}.fa-copyright:before{content:"\f1f9"}.fa-at:before{content:"\f1fa"}.fa-eyedropper:before{content:"\f1fb"}.fa-paint-brush:before{content:"\f1fc"}.fa-birthday-cake:before{content:"\f1fd"}.fa-area-chart:before{content:"\f1fe"}.fa-pie-chart:before{content:"\f200"}.fa-line-chart:before{content:"\f201"}.fa-lastfm:before{content:"\f202"}.fa-lastfm-square:before{content:"\f203"}.fa-toggle-off:before{content:"\f204"}.fa-toggle-on:before{content:"\f205"}.fa-bicycle:before{content:"\f206"}.fa-bus:before{content:"\f207"}.fa-ioxhost:before{content:"\f208"}.fa-angellist:before{content:"\f209"}.fa-cc:before{content:"\f20a"}.fa-shekel:before,.fa-sheqel:before,.fa-ils:before{content:"\f20b"}.fa-meanpath:before{content:"\f20c"}.fa-buysellads:before{content:"\f20d"}.fa-connectdevelop:before{content:"\f20e"}.fa-dashcube:before{content:"\f210"}.fa-forumbee:before{content:"\f211"}.fa-leanpub:before{content:"\f212"}.fa-sellsy:before{content:"\f213"}.fa-shirtsinbulk:before{content:"\f214"}.fa-simplybuilt:before{content:"\f215"}.fa-skyatlas:before{content:"\f216"}.fa-cart-plus:before{content:"\f217"}.fa-cart-arrow-down:before{content:"\f218"}.fa-diamond:before{content:"\f219"}.fa-ship:before{content:"\f21a"}.fa-user-secret:before{content:"\f21b"}.fa-motorcycle:before{content:"\f21c"}.fa-street-view:before{content:"\f21d"}.fa-heartbeat:before{content:"\f21e"}.fa-venus:before{content:"\f221"}.fa-mars:before{content:"\f222"}.fa-mercury:before{content:"\f223"}.fa-intersex:before,.fa-transgender:before{content:"\f224"}.fa-transgender-alt:before{content:"\f225"}.fa-venus-double:before{content:"\f226"}.fa-mars-double:before{content:"\f227"}.fa-venus-mars:before{content:"\f228"}.fa-mars-stroke:before{content:"\f229"}.fa-mars-stroke-v:before{content:"\f22a"}.fa-mars-stroke-h:before{content:"\f22b"}.fa-neuter:before{content:"\f22c"}.fa-genderless:before{content:"\f22d"}.fa-facebook-official:before{content:"\f230"}.fa-pinterest-p:before{content:"\f231"}.fa-whatsapp:before{content:"\f232"}.fa-server:before{content:"\f233"}.fa-user-plus:before{content:"\f234"}.fa-user-times:before{content:"\f235"}.fa-hotel:before,.fa-bed:before{content:"\f236"}.fa-viacoin:before{content:"\f237"}.fa-train:before{content:"\f238"}.fa-subway:before{content:"\f239"}.fa-medium:before{content:"\f23a"}.fa-yc:before,.fa-y-combinator:before{content:"\f23b"}.fa-optin-monster:before{content:"\f23c"}.fa-opencart:before{content:"\f23d"}.fa-expeditedssl:before{content:"\f23e"}.fa-battery-4:before,.fa-battery:before,.fa-battery-full:before{content:"\f240"}.fa-battery-3:before,.fa-battery-three-quarters:before{content:"\f241"}.fa-battery-2:before,.fa-battery-half:before{content:"\f242"}.fa-battery-1:before,.fa-battery-quarter:before{content:"\f243"}.fa-battery-0:before,.fa-battery-empty:before{content:"\f244"}.fa-mouse-pointer:before{content:"\f245"}.fa-i-cursor:before{content:"\f246"}.fa-object-group:before{content:"\f247"}.fa-object-ungroup:before{content:"\f248"}.fa-sticky-note:before{content:"\f249"}.fa-sticky-note-o:before{content:"\f24a"}.fa-cc-jcb:before{content:"\f24b"}.fa-cc-diners-club:before{content:"\f24c"}.fa-clone:before{content:"\f24d"}.fa-balance-scale:before{content:"\f24e"}.fa-hourglass-o:before{content:"\f250"}.fa-hourglass-1:before,.fa-hourglass-start:before{content:"\f251"}.fa-hourglass-2:before,.fa-hourglass-half:before{content:"\f252"}.fa-hourglass-3:before,.fa-hourglass-end:before{content:"\f253"}.fa-hourglass:before{content:"\f254"}.fa-hand-grab-o:before,.fa-hand-rock-o:before{content:"\f255"}.fa-hand-stop-o:before,.fa-hand-paper-o:before{content:"\f256"}.fa-hand-scissors-o:before{content:"\f257"}.fa-hand-lizard-o:before{content:"\f258"}.fa-hand-spock-o:before{content:"\f259"}.fa-hand-pointer-o:before{content:"\f25a"}.fa-hand-peace-o:before{content:"\f25b"}.fa-trademark:before{content:"\f25c"}.fa-registered:before{content:"\f25d"}.fa-creative-commons:before{content:"\f25e"}.fa-gg:before{content:"\f260"}.fa-gg-circle:before{content:"\f261"}.fa-tripadvisor:before{content:"\f262"}.fa-odnoklassniki:before{content:"\f263"}.fa-odnoklassniki-square:before{content:"\f264"}.fa-get-pocket:before{content:"\f265"}.fa-wikipedia-w:before{content:"\f266"}.fa-safari:before{content:"\f267"}.fa-chrome:before{content:"\f268"}.fa-firefox:before{content:"\f269"}.fa-opera:before{content:"\f26a"}.fa-internet-explorer:before{content:"\f26b"}.fa-tv:before,.fa-television:before{content:"\f26c"}.fa-contao:before{content:"\f26d"}.fa-500px:before{content:"\f26e"}.fa-amazon:before{content:"\f270"}.fa-calendar-plus-o:before{content:"\f271"}.fa-calendar-minus-o:before{content:"\f272"}.fa-calendar-times-o:before{content:"\f273"}.fa-calendar-check-o:before{content:"\f274"}.fa-industry:before{content:"\f275"}.fa-map-pin:before{content:"\f276"}.fa-map-signs:before{content:"\f277"}.fa-map-o:before{content:"\f278"}.fa-map:before{content:"\f279"}.fa-commenting:before{content:"\f27a"}.fa-commenting-o:before{content:"\f27b"}.fa-houzz:before{content:"\f27c"}.fa-vimeo:before{content:"\f27d"}.fa-black-tie:before{content:"\f27e"}.fa-fonticons:before{content:"\f280"}.fa-reddit-alien:before{content:"\f281"}.fa-edge:before{content:"\f282"}.fa-credit-card-alt:before{content:"\f283"}.fa-codiepie:before{content:"\f284"}.fa-modx:before{content:"\f285"}.fa-fort-awesome:before{content:"\f286"}.fa-usb:before{content:"\f287"}.fa-product-hunt:before{content:"\f288"}.fa-mixcloud:before{content:"\f289"}.fa-scribd:before{content:"\f28a"}.fa-pause-circle:before{content:"\f28b"}.fa-pause-circle-o:before{content:"\f28c"}.fa-stop-circle:before{content:"\f28d"}.fa-stop-circle-o:before{content:"\f28e"}.fa-shopping-bag:before{content:"\f290"}.fa-shopping-basket:before{content:"\f291"}.fa-hashtag:before{content:"\f292"}.fa-bluetooth:before{content:"\f293"}.fa-bluetooth-b:before{content:"\f294"}.fa-percent:before{content:"\f295"}.fa-gitlab:before{content:"\f296"}.fa-wpbeginner:before{content:"\f297"}.fa-wpforms:before{content:"\f298"}.fa-envira:before{content:"\f299"}.fa-universal-access:before{content:"\f29a"}.fa-wheelchair-alt:before{content:"\f29b"}.fa-question-circle-o:before{content:"\f29c"}.fa-blind:before{content:"\f29d"}.fa-audio-description:before{content:"\f29e"}.fa-volume-control-phone:before{content:"\f2a0"}.fa-braille:before{content:"\f2a1"}.fa-assistive-listening-systems:before{content:"\f2a2"}.fa-asl-interpreting:before,.fa-american-sign-language-interpreting:before{content:"\f2a3"}.fa-deafness:before,.fa-hard-of-hearing:before,.fa-deaf:before{content:"\f2a4"}.fa-glide:before{content:"\f2a5"}.fa-glide-g:before{content:"\f2a6"}.fa-signing:before,.fa-sign-language:before{content:"\f2a7"}.fa-low-vision:before{content:"\f2a8"}.fa-viadeo:before{content:"\f2a9"}.fa-viadeo-square:before{content:"\f2aa"}.fa-snapchat:before{content:"\f2ab"}.fa-snapchat-ghost:before{content:"\f2ac"}.fa-snapchat-square:before{content:"\f2ad"}.fa-pied-piper:before{content:"\f2ae"}.fa-first-order:before{content:"\f2b0"}.fa-yoast:before{content:"\f2b1"}.fa-themeisle:before{content:"\f2b2"}.fa-google-plus-circle:before,.fa-google-plus-official:before{content:"\f2b3"}.fa-fa:before,.fa-font-awesome:before{content:"\f2b4"}.fa-handshake-o:before{content:"\f2b5"}.fa-envelope-open:before{content:"\f2b6"}.fa-envelope-open-o:before{content:"\f2b7"}.fa-linode:before{content:"\f2b8"}.fa-address-book:before{content:"\f2b9"}.fa-address-book-o:before{content:"\f2ba"}.fa-vcard:before,.fa-address-card:before{content:"\f2bb"}.fa-vcard-o:before,.fa-address-card-o:before{content:"\f2bc"}.fa-user-circle:before{content:"\f2bd"}.fa-user-circle-o:before{content:"\f2be"}.fa-user-o:before{content:"\f2c0"}.fa-id-badge:before{content:"\f2c1"}.fa-drivers-license:before,.fa-id-card:before{content:"\f2c2"}.fa-drivers-license-o:before,.fa-id-card-o:before{content:"\f2c3"}.fa-quora:before{content:"\f2c4"}.fa-free-code-camp:before{content:"\f2c5"}.fa-telegram:before{content:"\f2c6"}.fa-thermometer-4:before,.fa-thermometer:before,.fa-thermometer-full:before{content:"\f2c7"}.fa-thermometer-3:before,.fa-thermometer-three-quarters:before{content:"\f2c8"}.fa-thermometer-2:before,.fa-thermometer-half:before{content:"\f2c9"}.fa-thermometer-1:before,.fa-thermometer-quarter:before{content:"\f2ca"}.fa-thermometer-0:before,.fa-thermometer-empty:before{content:"\f2cb"}.fa-shower:before{content:"\f2cc"}.fa-bathtub:before,.fa-s15:before,.fa-bath:before{content:"\f2cd"}.fa-podcast:before{content:"\f2ce"}.fa-window-maximize:before{content:"\f2d0"}.fa-window-minimize:before{content:"\f2d1"}.fa-window-restore:before{content:"\f2d2"}.fa-times-rectangle:before,.fa-window-close:before{content:"\f2d3"}.fa-times-rectangle-o:before,.fa-window-close-o:before{content:"\f2d4"}.fa-bandcamp:before{content:"\f2d5"}.fa-grav:before{content:"\f2d6"}.fa-etsy:before{content:"\f2d7"}.fa-imdb:before{content:"\f2d8"}.fa-ravelry:before{content:"\f2d9"}.fa-eercast:before{content:"\f2da"}.fa-microchip:before{content:"\f2db"}.fa-snowflake-o:before{content:"\f2dc"}.fa-superpowers:before{content:"\f2dd"}.fa-wpexplorer:before{content:"\f2de"}.fa-meetup:before{content:"\f2e0"}.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0, 0, 0, 0);border:0}.sr-only-focusable:active,.sr-only-focusable:focus{position:static;width:auto;height:auto;margin:0;overflow:visible;clip:auto}
diff --git a/client/public/font-awesome/fonts/fontawesome-webfont.ttf b/client/public/font-awesome/fonts/fontawesome-webfont.ttf
new file mode 100644
index 0000000..35acda2
Binary files /dev/null and b/client/public/font-awesome/fonts/fontawesome-webfont.ttf differ
diff --git a/client/public/font-awesome/fonts/fontawesome-webfont.woff b/client/public/font-awesome/fonts/fontawesome-webfont.woff
new file mode 100644
index 0000000..400014a
Binary files /dev/null and b/client/public/font-awesome/fonts/fontawesome-webfont.woff differ
diff --git a/client/public/font-awesome/fonts/fontawesome-webfont.woff2 b/client/public/font-awesome/fonts/fontawesome-webfont.woff2
new file mode 100644
index 0000000..4d13fc6
Binary files /dev/null and b/client/public/font-awesome/fonts/fontawesome-webfont.woff2 differ
diff --git a/client/public/galahad-graal-groot.png b/client/public/galahad-graal-groot.png
new file mode 100644
index 0000000..2f146ef
Binary files /dev/null and b/client/public/galahad-graal-groot.png differ
diff --git a/client/public/galahad-graal-klein.png b/client/public/galahad-graal-klein.png
new file mode 100644
index 0000000..eae4f3b
Binary files /dev/null and b/client/public/galahad-graal-klein.png differ
diff --git a/client/public/galahad-graal-medium.png b/client/public/galahad-graal-medium.png
new file mode 100644
index 0000000..c05bb8f
Binary files /dev/null and b/client/public/galahad-graal-medium.png differ
diff --git a/client/server.js b/client/server.js
new file mode 100644
index 0000000..778a04b
--- /dev/null
+++ b/client/server.js
@@ -0,0 +1,39 @@
+const express = require('express')
+const history = require('connect-history-api-fallback')
+
+const app = express()
+const port = 8080
+
+// Due to production settings at INT, the Vue app has to be run at the /galahad path
+// This path is set at build time and therefore can not be configured on the docker container itself
+// We use this standard redirect so that the app also works in other contexts
+// e.g. dev server
+
+// See https://stackoverflow.com/questions/44226622/vue-router-and-express for router funkiness
+
+const staticFileMiddleware = express.static('dist')
+
+// app.get(/^(?!\/galahad)[\s\S]*/, (req, res) => {
+// // If path does not start with '/galahad'
+// res.redirect('/galahad')
+// })
+
+
+// This is a bit ugly, but it works for now
+// Need to look at it again later
+// The staticfiles at root are somehow used if the client is behind the portal
+// but '/galahad' is the public path
+app.use( staticFileMiddleware )
+app.use('/galahad', staticFileMiddleware )
+app.use(history({
+ disableDotRule: true,
+ index: '/galahad/index.html',
+ verbose: true
+}))
+app.use('/galahad', staticFileMiddleware )
+app.use( staticFileMiddleware )
+
+app.listen(port, () => {
+ console.log(`App listening at http://localhost:${port}`)
+})
+
diff --git a/client/src/App.vue b/client/src/App.vue
new file mode 100644
index 0000000..9455778
--- /dev/null
+++ b/client/src/App.vue
@@ -0,0 +1,229 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Help
+ About
+ Contribute
+ Lancelot
+
+
+
+
+
+ Something went wrong. If the connection to the server failed, the app will reload automatically.
+ {{ error }}
+ Please try again or contact
+ for support
+
+
+
+
+
+
+
diff --git a/client/src/api/api.ts b/client/src/api/api.ts
new file mode 100644
index 0000000..b88cbe6
--- /dev/null
+++ b/client/src/api/api.ts
@@ -0,0 +1,9 @@
+import axios from 'axios'
+
+export const setAxiosBaseUrl = () => {
+ // This defines the server API url
+ const apiURL = window.location.hostname === 'localhost'
+ ? window.location.protocol + '//' + window.location.hostname + ':8010' // assume dev
+ : window.location.protocol + '//' + window.location.hostname + '/galahad/api/' // assume prod
+ axios.defaults.baseURL = apiURL
+}
diff --git a/client/src/api/application.ts b/client/src/api/application.ts
new file mode 100644
index 0000000..4dec750
--- /dev/null
+++ b/client/src/api/application.ts
@@ -0,0 +1,30 @@
+// Libraries & stores
+import axios, { AxiosResponse } from "axios"
+// API & types
+import { User } from '@/types/user'
+
+// Paths
+const benchmarksPath = `/benchmarks`
+const userPath = `/user`
+const versionPath = `/version`
+
+// Custom types
+type BenchmarksResponse = AxiosResponse
+type UserResponse = AxiosResponse
+type VersionResponse = AxiosResponse
+
+// Public methods
+/**
+ * Poll user account.
+ */
+export function getUser(): Promise {
+ return axios.get(userPath)
+}
+
+export function getBenchmarks(): Promise {
+ return axios.get(benchmarksPath)
+}
+
+export function getVersion(): Promise {
+ return axios.get(versionPath)
+}
diff --git a/client/src/api/assays.ts b/client/src/api/assays.ts
new file mode 100644
index 0000000..9846618
--- /dev/null
+++ b/client/src/api/assays.ts
@@ -0,0 +1,34 @@
+/**
+ * API calls for fetching assays.
+ */
+
+// Libraries & stores
+import axios, { AxiosResponse } from "axios"
+// Types & API
+import { UUID } from "@/types/corpora"
+import { Assays, IndividualAssay } from "@/types/assays"
+
+const assaysPath = `/assays`
+const assayPath = (corpus: UUID, job: string) => `/corpora/${corpus}/jobs/${job}/evaluation/assay`
+
+// Custom types
+type AssaysResponse = AxiosResponse
+type AssayResponse = AxiosResponse
+
+// Public methods
+/**
+ * Fetch all assays.
+ */
+export function getAssays(): Promise {
+ return axios.get(assaysPath)
+}
+
+/**
+ * Fetch single assay of job on corpus.
+ * @param uuid UUID of assay.
+ * @param job tagger job name.
+ */
+// Currently unused
+export function getAssay(corpus: UUID, job: string): Promise {
+ return axios.get(assayPath(corpus, job))
+}
diff --git a/client/src/api/corpora.ts b/client/src/api/corpora.ts
new file mode 100644
index 0000000..00aeb4d
--- /dev/null
+++ b/client/src/api/corpora.ts
@@ -0,0 +1,74 @@
+/**
+ * API calls for creating, updating, deleting a corpus, and fetching the list of corpora.
+ */
+
+// Libraries & stores
+import axios, { AxiosResponse } from "axios"
+// Types & API
+import { UUID, CorpusMetadata, MutableCorpusMetadata } from "@/types/corpora"
+
+// Paths
+const corporaPath = `/corpora`
+const datasetCorporaPath = `/datasets_corpora`
+const publicCorporaPath = `/public_corpora`
+const corpusPath = (uuid: UUID) => `${corporaPath}/${uuid}`
+
+// Custom types
+type CorporaResponse = AxiosResponse
+type CorpusResponse = AxiosResponse
+
+// Public methods
+/**
+ * Fetch all corpora for which the user has read access:
+ * public (datasets), shared (collaborator/viewer), and owned.
+ */
+export function getCorpora(): Promise {
+ return axios.get(corporaPath)
+}
+
+/**
+ * Fetch a single corpus by UUID.
+ * @param uuid UUID of corpus.
+ */
+export function getCorpus(uuid: UUID): Promise {
+ return axios.get(corpusPath(uuid))
+}
+
+/**
+ * Fetch all datasets. Note that all datasets are public. Cheaper than getCorpora().
+ */
+export function getDatasetCorpora(): Promise {
+ return axios.get(datasetCorporaPath)
+}
+
+/**
+ * Fetch all public corpora. Cheaper than getCorpora().
+ */
+export function getPublicCorpora(): Promise {
+ return axios.get(publicCorporaPath)
+}
+
+/**
+ * Create a new corpus with the given metadata.
+ * @param corpus Metadata of new corpus.
+ */
+export function postCorpus(corpus: MutableCorpusMetadata): Promise> {
+ return axios.post(corporaPath, corpus)
+}
+
+/**
+ * Delete a corpus by UUID.
+ * @param uuid UUID of corpus to delete.
+ */
+export function deleteCorpus(uuid: UUID) {
+ return axios.delete(corpusPath(uuid))
+}
+
+/**
+ * Update the metadata of a corpus.
+ * @param uuid UUID of corpus to update.
+ * @param metadata New metadata.
+ */
+export function patchCorpus(uuid: UUID, metadata: MutableCorpusMetadata) {
+ return axios.patch(corpusPath(uuid), metadata)
+}
diff --git a/client/src/api/documents.ts b/client/src/api/documents.ts
new file mode 100644
index 0000000..455a549
--- /dev/null
+++ b/client/src/api/documents.ts
@@ -0,0 +1,68 @@
+/**
+ * API calls for fetching documents for a corpus, uploading and deleting documents,
+ * and downloading the uploaded source document.
+ */
+
+// Libraries & stores
+import axios, { AxiosResponse } from "axios"
+// Types & API
+import { UUID } from "@/types/corpora"
+import { DocumentMetadata } from "@/types/documents"
+import * as Utils from "@/api/utils"
+import { BlobResponse } from "@/api/utils"
+
+// Paths
+const documentsPath = (corpus: UUID) => `/corpora/${corpus}/documents`
+const documentPath = (corpus: UUID, document: string) => `${documentsPath(corpus)}/${document}`
+const rawDocumentPath = (corpus: UUID, document: string) => `${documentPath(corpus, document)}/raw`
+
+// Custom types
+type DocumentsResponse = AxiosResponse
+type DocumentResponse = AxiosResponse
+
+// Public methods
+/**
+ * Fetch all documents for a corpus.
+ * @param corpus UUID of the corpus.
+ */
+export function getDocuments(corpus: UUID): Promise {
+ return axios.get(documentsPath(corpus))
+}
+
+// unused
+export function getDocument(corpus: UUID, document: string): Promise {
+ return axios.get(documentPath(corpus, document))
+}
+
+/**
+ * Upload new document.
+ * @param corpus UUID of the corpus.
+ * @param document Document name.
+ * @param contentType Content type of the document. Must be explicitly set for tsv-files. Others are induced.
+ */
+export function postDocument(corpus: UUID, document: FormData, contentType?: any) {
+ return axios.post(documentsPath(corpus), document, { headers: contentType })
+}
+
+/**
+ * Delete uploaded document.
+ * @param corpus UUID of the corpus.
+ * @param document Document name.
+ */
+export function deleteDocument(corpus: UUID, document: string) {
+ return axios.delete(documentPath(corpus, document))
+}
+
+// unused
+export function patchDocument(corpus: UUID, document: string, documentData: DocumentMetadata) {
+ return axios.patch(documentPath(corpus, document), documentData)
+}
+
+/**
+ * Download the uploaded source document.
+ * @param corpus UUID of the corpus.
+ * @param document Document name.
+ */
+export function getRawDocument(corpus: UUID, document: string): Promise {
+ return Utils.getBlob(rawDocumentPath(corpus, document))
+}
diff --git a/client/src/api/evaluation.ts b/client/src/api/evaluation.ts
new file mode 100644
index 0000000..a307a43
--- /dev/null
+++ b/client/src/api/evaluation.ts
@@ -0,0 +1,105 @@
+/**
+ * API calls for fetching evaluation metrics and downloading them as a zip report.
+ */
+
+// Libraries & stores
+import axios, { AxiosResponse } from "axios"
+// API & types
+import { ConfusionWrapper, DistributionWrapper, Metrics } from "@/types/evaluation"
+import { UUID } from "@/types/corpora"
+import * as Utils from "@/api/utils"
+import { BlobResponse } from "@/api/utils"
+
+// Paths
+const evaluationPath = (corpus: UUID, hypothesis: string) => `/corpora/${corpus}/jobs/${hypothesis}/evaluation`
+const confusionPath = (corpus: UUID, hypothesis: string, reference: string) => {
+ return `${evaluationPath(corpus, hypothesis)}/confusion?reference=${reference}`
+}
+const posConfusionPath = (corpus: UUID, hypothesis: string, reference: string, hypothesisPOS: string, referencePOS: string) => {
+ return `${evaluationPath(corpus, hypothesis)}/confusion/download?reference=${reference}&hypoPosFilter=${hypothesisPOS}&refPosFilter=${referencePOS}`
+}
+
+const distributionPath = (corpus: UUID, hypothesis: string) => `${evaluationPath(corpus, hypothesis)}/distribution`
+const metricsPath = (corpus: UUID, hypothesis: string, reference: string) => {
+ return `${evaluationPath(corpus, hypothesis)}/metrics?reference=${reference}`
+}
+const metricsSamplesPath = (corpus: UUID, hypothesis: string, reference: string, setting: string, classType: string, group?: string) => {
+ return `${evaluationPath(corpus, hypothesis)}/metrics/download?reference=${reference}&setting=${setting}&class=${classType}` + (group ? `&group=${group}` : '')
+}
+
+const downloadPath = (corpus: UUID, hypothesis: string, reference: string) => {
+ return `${evaluationPath(corpus, hypothesis)}/download?reference=${reference}`
+}
+
+// Custom Types
+type ConfusionResponse = AxiosResponse
+type DistributionResponse = AxiosResponse
+type MetricsResponse = AxiosResponse
+
+// Public methods
+/**
+ * Fetch term frequency distribution.
+ * @param corpus UUID of the corpus.
+ * @param hypothesis Tagging job name as hypothesis layer.
+ */
+export function getDistribution(corpus: UUID, hypothesis: string): Promise {
+ return axios.get(distributionPath(corpus, hypothesis))
+}
+
+/**
+ * Fetch term confusion matrix.
+ * @param corpus UUID of the corpus.
+ * @param hypothesis Tagger job name as hypothesis layer.
+ * @param reference Tagger job name as reference layer.
+ */
+export function getConfusion(corpus: UUID, hypothesis: string, reference: string): Promise {
+ return axios.get(confusionPath(corpus, hypothesis, reference))
+}
+
+/**
+ * Fetch Lemma & PoS accuracy metrics.
+ * @param corpus UUID of the corpus.
+ * @param hypothesis Tagger job name as hypothesis layer.
+ * @param reference Tagger job name as reference layer.
+ */
+export function getMetrics(corpus: UUID, hypothesis: string, reference: string): Promise {
+ return axios.get(metricsPath(corpus, hypothesis, reference))
+}
+
+/**
+ * Download evaluation zip.
+ * @param corpus UUID of the corpus.
+ * @param hypothesis Tagger job name as hypothesis layer.
+ * @param reference Tagger job name as reference layer.
+ */
+export function getDownloadEvaluation(corpus: UUID, hypothesis: string, reference: string): Promise {
+ return Utils.getBlob(downloadPath(corpus, hypothesis, reference))
+}
+
+/**
+ * Download PoS confusion entries as zip.
+ * @param corpus UUID of the corpus.
+ * @param hypothesis Tagger job name as hypothesis layer.
+ * @param reference Tagger job name as reference layer.
+ * @param hypothesisPOS PoS tag of the hypothesis layer to filter on.
+ * @param referencePOS PoS tag of the reference layer to filter on.
+ */
+export function getDownloadPosConfusion(corpus: UUID, hypothesis: string, reference: string, hypothesisPOS: string, referencePOS: string): Promise {
+ return Utils.getBlob(posConfusionPath(corpus, hypothesis, reference, hypothesisPOS, referencePOS))
+}
+
+/**
+ * Download metrics samples as zip.
+ * @param corpus UUID of the corpus.
+ * @param hypothesis Tagger job name as hypothesis layer.
+ * @param reference Tagger job name as reference layer.
+ * @param setting Setting for the metrics. E.g. 'posByPos'
+ * @param classType Class type for the metrics. E.g. 'truePositive'.
+ * @param group Group for the metrics. E.g. 'pos' or 'lemma'.
+ */
+export function getMetricsSamples(corpus: UUID, hypothesis: string, reference: string, setting: string, classType: string, group?: string): Promise {
+ if (group) {
+ group = encodeURIComponent(group)
+ }
+ return Utils.getBlob(metricsSamplesPath(corpus, hypothesis, reference, setting, classType, group))
+}
diff --git a/client/src/api/export.ts b/client/src/api/export.ts
new file mode 100644
index 0000000..49fac5c
--- /dev/null
+++ b/client/src/api/export.ts
@@ -0,0 +1,72 @@
+/**
+ * API calls for exporting corpora and documents.
+ * Either converted to a certain format or merged with their original file if the format supports it.
+ */
+
+// Types & API
+import * as Utils from "@/api/utils"
+import { BlobResponse } from "@/api/utils"
+import { Format } from "@/types/documents"
+import { UUID } from "@/types/corpora"
+
+// Paths
+const convertCorpusPath = (corpus: UUID, job: string, format: Format, posHeadOnly: Boolean) => {
+ return `/corpora/${corpus}/jobs/${job}/export/convert?format=${format}&posHeadOnly=${posHeadOnly}`
+}
+const mergeCorpusPath = (corpus: UUID, job: string, format: Format, posHeadOnly: Boolean) => {
+ return `/corpora/${corpus}/jobs/${job}/export/merge?format=${format}&posHeadOnly=${posHeadOnly}`
+}
+const convertDocumentPath = (corpus: UUID, job: string, document: string, format: Format) => {
+ return `/corpora/${corpus}/jobs/${job}/documents/${document}/export/convert?format=${format}`
+}
+const mergeDocumentPath = (corpus: UUID, job: string, document: string) => {
+ return `/corpora/${corpus}/jobs/${job}/documents/${document}/export/merge`
+}
+
+// Public methods
+/**
+ * Download a corpus converted to the desired format.
+ * @param corpus UUID of the corpus.
+ * @param job Tagger job name.
+ * @param format Use enum here.
+ * @param posHeadOnly Whether to include only the head of the POS tags in the export.
+ */
+export function convertCorpus(corpus: UUID, job: string, format: Format, posHeadOnly: boolean): Promise {
+ return Utils.getBlob(convertCorpusPath(corpus, job, format, posHeadOnly))
+}
+
+/**
+ * Download a corpus converted to the desired format, merging any files of that format.
+ * @param corpus UUID of the corpus.
+ * @param job Tagger job name.
+ * @param format Use enum here.
+ * @param posHeadOnly Whether to include only the head of the POS tags in the export.
+ */
+export function mergeCorpus(corpus: UUID, job: string, format: Format, posHeadOnly: boolean): Promise {
+ return Utils.getBlob(mergeCorpusPath(corpus, job, format, posHeadOnly))
+}
+
+/**
+ * Download a single document from a job convert to the desired format.
+ * @param corpus UUID of the corpus.
+ * @param job Tagger job name.
+ * @param document Document name.
+ * @param format Use enum here.
+ */
+// Currently unused
+export function convertDocument(corpus: UUID, job: string, document: string, format: Format): Promise {
+ return Utils.getBlob(convertDocumentPath(corpus, job, document, format))
+}
+
+/**
+ * Download a single document from a job convert to the desired format,
+ * merging it if the original file is of that format.
+ * @param corpus UUID of the corpus.
+ * @param job Tagger job name.
+ * @param document Document name.
+ * @param format Use enum here.
+ */
+// Currently unused
+export function mergeDocument(corpus: UUID, job: string, document: string): Promise {
+ return Utils.getBlob(mergeDocumentPath(corpus, job, document))
+}
diff --git a/client/src/api/jobs.ts b/client/src/api/jobs.ts
new file mode 100644
index 0000000..22c3262
--- /dev/null
+++ b/client/src/api/jobs.ts
@@ -0,0 +1,91 @@
+/**
+ * API calls for getting existing jobs and job layer results, posting and cancelling jobs, and polling job progress.
+ */
+
+// Libraries & stores
+import axios, { AxiosResponse } from "axios"
+// Types & API
+import { Job, Progress, JobDocumentResult } from "@/types/jobs"
+import { UUID } from "@/types/corpora"
+
+// Paths
+const jobsPath = (corpus: UUID) => `/corpora/${corpus}/jobs`
+const jobPath = (corpus: UUID, job: string) => `/corpora/${corpus}/jobs/${job}`
+const jobDocumentResultPath = (corpus: UUID, job: string, document: string) => {
+ return `/corpora/${corpus}/jobs/${job}/documents/${document}/result`
+}
+const jobHasErrorPath = (corpus: UUID, job: string) => `/corpora/${corpus}/jobs/${job}/hasError`
+const jobIsBusyPath = (corpus: UUID, job: string) => `/corpora/${corpus}/jobs/${job}/isBusy`
+const jobProgressPath = (corpus: UUID, job: string) => `/corpora/${corpus}/jobs/${job}/progress`
+
+// Custom types
+type JobsResponse = AxiosResponse
+type JobResponse = AxiosResponse
+type JobDocumentResultResponse = AxiosResponse
+export type ProgressResponse = AxiosResponse
+
+// Public methods
+/**
+ * Fetch all jobs for a corpus.
+ * @param corpus UUID of the corpus.
+ * @param includePotentialJobs If true, will also include untagged jobs (so all jobs will be present).
+ */
+export function getJobs(corpus: UUID, includePotentialJobs: boolean): Promise {
+ return axios.get(jobsPath(corpus), { params: { includePotentialJobs: includePotentialJobs } })
+}
+
+/**
+ * Fetch a single job for a corpus.
+ * @param corpus UUID of the corpus.
+ * @param job Tagger job name.
+ */
+export function getJob(corpus: UUID, job: string) {
+ return axios.get(jobPath(corpus, job)) as Promise
+}
+
+/**
+ * Post a job to start it. Will return an immediate ProgressResponse.busy=true to give the illusion of a started job.
+ * @param corpus UUID of the corpus.
+ * @param job Tagger job name.
+ */
+export function postJob(corpus: UUID, job: string): Promise {
+ return axios.post(jobPath(corpus, job))
+}
+
+/**
+ * Cancel or delete a job.
+ * @param corpus UUID of the corpus.
+ * @param job Tagger job name.
+ * @param hard True to delete the job, false to cancel it.
+ */
+export function cancelOrDeleteJob(corpus: UUID, job: string, hard: boolean): Promise {
+ return axios.delete(jobPath(corpus, job), { params: { hard: hard } })
+}
+
+/**
+ * Get the layer result of a tagger job. Only includes a preview (to spare bandwidth).
+ * @param corpus UUID of the corpus.
+ * @param job Tagger job name.
+ * @param document Document name.
+ */
+export function getJobDocumentResult(corpus: UUID, job: string, document: string): Promise {
+ return axios.get(jobDocumentResultPath(corpus, job, document))
+}
+
+export function getJobHasError(corpus: UUID, job: string): Promise> {
+ return axios.get(jobHasErrorPath(corpus, job))
+}
+
+/**
+ * Simplified job progress poll.
+ */
+export function getJobIsBusy(corpus: UUID, job: string): Promise> {
+ return axios.get(jobIsBusyPath(corpus, job))
+}
+
+/**
+ * Poll for job progress.
+ */
+export function getJobProgress(corpus: UUID, job: string): Promise {
+ return axios.get(jobProgressPath(corpus, job))
+}
diff --git a/client/src/api/taggers.ts b/client/src/api/taggers.ts
new file mode 100644
index 0000000..86f7452
--- /dev/null
+++ b/client/src/api/taggers.ts
@@ -0,0 +1,51 @@
+/**
+ * API calls for fetching taggers and their health status.
+ */
+
+// Libraries & stores
+import axios, { AxiosResponse } from "axios"
+// Types & API
+import { Tagger, TaggerHealth } from "@/types/taggers"
+
+// Paths
+const taggersPath = `/taggers`
+const taggerPath = (tagger: string) => `${taggersPath}/${tagger}`
+const taggerHealthPath = (tagger: string) => `${taggerPath(tagger)}/health`
+
+// Custom types
+type TaggersResponse = AxiosResponse
+type TaggerResponse = AxiosResponse
+type TaggerHealthResponse = AxiosResponse
+type TaggersBusyResponse = AxiosResponse
+
+// Public methods
+/**
+ * Get all taggers.
+ */
+export function getTaggers(): Promise {
+ return axios.get(taggersPath)
+}
+
+/**
+ * Get single tagger by name.
+ * @param tagger Tagger name.
+ */
+export function getTagger(tagger: string): Promise {
+ return axios.get(taggerPath(tagger))
+}
+
+/**
+ * Get tagger health status.
+ * @param tagger Tagger name.
+ */
+export function getTaggerHealth(tagger: string): Promise {
+ return axios.get(taggerHealthPath(tagger))
+}
+
+/**
+ * Get how many docs are currently actively processing.
+ * Summed over all taggers & corpora on the server.
+ */
+export function getDocsAtTaggers(): Promise {
+ return axios.get(`${taggersPath}/active`)
+}
diff --git a/client/src/api/tagset.ts b/client/src/api/tagset.ts
new file mode 100644
index 0000000..829f780
--- /dev/null
+++ b/client/src/api/tagset.ts
@@ -0,0 +1,30 @@
+/**
+ * API calls for fetching tagsets.
+ */
+
+// Libraries & stores
+import axios, { AxiosResponse } from "axios"
+// Types & API
+import { Tagset } from "@/types/tagset"
+
+const tagsetsPath = `/tagsets`
+
+// Custom types
+type TagsetsResponse = AxiosResponse
+type TagsetResponse = AxiosResponse
+
+// Public methods
+/**
+ * Get all tagsets.
+ */
+export function getTagsets(): Promise {
+ return axios.get(tagsetsPath)
+}
+
+/**
+ * Get single tagset by name.
+ * @param tagset Tagset name.
+ */
+export function getTagset(tagset: string): Promise {
+ return axios.get(`${tagsetsPath}/${tagset}`)
+}
diff --git a/client/src/api/utils.ts b/client/src/api/utils.ts
new file mode 100644
index 0000000..45027e8
--- /dev/null
+++ b/client/src/api/utils.ts
@@ -0,0 +1,67 @@
+/**
+ * Utils for handling the blobs from some API responses.
+ */
+
+// Libraries & stores
+import { AppStore } from "@/stores"
+import axios, { AxiosResponse, AxiosError } from "axios"
+import { parse } from 'content-disposition'
+
+// Custom types
+export type BlobResponse = AxiosResponse
+
+/**
+ * Fetch a blob from a path.
+ * @param path Request path.
+ */
+export function getBlob(path: string): Promise {
+ return axios.get(path, { responseType: 'blob' })
+}
+
+/**
+ * Downloads a file from a response object.
+ * @param response Response with blob data.
+ */
+export function browserDownloadResponseFile(response: BlobResponse) {
+ // Parse potential UTF8 filename.
+ const filename = parse(response.headers['content-disposition']).parameters.filename
+ // DOM link.
+ const linkEl = document.createElement('a')
+ linkEl.href = window.URL.createObjectURL(new Blob([response.data]))
+ linkEl.setAttribute('download', filename)
+ document.body.appendChild(linkEl)
+ linkEl.click()
+}
+
+/**
+ * Axios does not support multiple responseTypes. When trying to download a blob,
+ * we also receive a blob on error instead of json. So first parse to json.
+ * https://medium.com/@fakiolinho/handle-blobs-requests-with-axios-the-right-way-bb905bdb1c04
+ * @param error Axios error.
+ * @param intent Human readable explanation.
+ * @param app appStore.
+ */
+export function handleBlobError(error: AxiosError, intent: string, app: AppStore) {
+ const reader = new FileReader()
+ // Setup the onload that fires after reading.
+ reader.onload = () => {
+ const json = JSON.parse(reader.result as string)
+ const errObj = {
+ response: {
+ data: json
+ }
+ } as AxiosError
+ app.handleServerError(intent, errObj)
+ }
+ // Now, read.
+ reader.readAsText(error.response?.data as Blob)
+}
+
+// https://stackoverflow.com/a/18650828
+export function formatBytes(bytes: number, decimals = 2) {
+ if (!+bytes) return "0 Bytes"
+ const dm = 0 > decimals ? 0 : decimals
+ const d = Math.floor(Math.log(bytes) / Math.log(1024))
+ const sizes = ["Bytes", "KB", "MB", "GB", "TB", "PB", "EB", "ZB", "YB"]
+ return `${parseFloat((bytes / Math.pow(1024, d)).toFixed(dm))} ${sizes[d]}`
+}
diff --git a/client/src/assets/Schoolboek-Regular.woff b/client/src/assets/Schoolboek-Regular.woff
new file mode 100644
index 0000000..05a782a
Binary files /dev/null and b/client/src/assets/Schoolboek-Regular.woff differ
diff --git a/client/src/assets/get_started/choosing-layers.png b/client/src/assets/get_started/choosing-layers.png
new file mode 100644
index 0000000..d98b57c
Binary files /dev/null and b/client/src/assets/get_started/choosing-layers.png differ
diff --git a/client/src/assets/get_started/create-corpus.png b/client/src/assets/get_started/create-corpus.png
new file mode 100644
index 0000000..9b2b731
Binary files /dev/null and b/client/src/assets/get_started/create-corpus.png differ
diff --git a/client/src/assets/get_started/create-layer.png b/client/src/assets/get_started/create-layer.png
new file mode 100644
index 0000000..3b0e1da
Binary files /dev/null and b/client/src/assets/get_started/create-layer.png differ
diff --git a/client/src/assets/get_started/distribution.png b/client/src/assets/get_started/distribution.png
new file mode 100644
index 0000000..25a0890
Binary files /dev/null and b/client/src/assets/get_started/distribution.png differ
diff --git a/client/src/assets/get_started/layer-created.png b/client/src/assets/get_started/layer-created.png
new file mode 100644
index 0000000..e98fcf0
Binary files /dev/null and b/client/src/assets/get_started/layer-created.png differ
diff --git a/client/src/assets/get_started/metrics-gradient.png b/client/src/assets/get_started/metrics-gradient.png
new file mode 100644
index 0000000..2d47780
Binary files /dev/null and b/client/src/assets/get_started/metrics-gradient.png differ
diff --git a/client/src/assets/get_started/metrics-popup.png b/client/src/assets/get_started/metrics-popup.png
new file mode 100644
index 0000000..3e0228f
Binary files /dev/null and b/client/src/assets/get_started/metrics-popup.png differ
diff --git a/client/src/assets/get_started/metrics.png b/client/src/assets/get_started/metrics.png
new file mode 100644
index 0000000..b94465b
Binary files /dev/null and b/client/src/assets/get_started/metrics.png differ
diff --git a/client/src/assets/get_started/pos-confusion.png b/client/src/assets/get_started/pos-confusion.png
new file mode 100644
index 0000000..71cf240
Binary files /dev/null and b/client/src/assets/get_started/pos-confusion.png differ
diff --git a/client/src/assets/get_started/upload-documents.png b/client/src/assets/get_started/upload-documents.png
new file mode 100644
index 0000000..5ec4df5
Binary files /dev/null and b/client/src/assets/get_started/upload-documents.png differ
diff --git a/client/src/assets/help/image1.png b/client/src/assets/help/image1.png
new file mode 100644
index 0000000..f24e1f4
Binary files /dev/null and b/client/src/assets/help/image1.png differ
diff --git a/client/src/assets/help/image10.png b/client/src/assets/help/image10.png
new file mode 100644
index 0000000..82d5669
Binary files /dev/null and b/client/src/assets/help/image10.png differ
diff --git a/client/src/assets/help/image11.png b/client/src/assets/help/image11.png
new file mode 100644
index 0000000..ec5b602
Binary files /dev/null and b/client/src/assets/help/image11.png differ
diff --git a/client/src/assets/help/image12.png b/client/src/assets/help/image12.png
new file mode 100644
index 0000000..1709b3d
Binary files /dev/null and b/client/src/assets/help/image12.png differ
diff --git a/client/src/assets/help/image13.png b/client/src/assets/help/image13.png
new file mode 100644
index 0000000..a212153
Binary files /dev/null and b/client/src/assets/help/image13.png differ
diff --git a/client/src/assets/help/image14.png b/client/src/assets/help/image14.png
new file mode 100644
index 0000000..c672ec2
Binary files /dev/null and b/client/src/assets/help/image14.png differ
diff --git a/client/src/assets/help/image2.png b/client/src/assets/help/image2.png
new file mode 100644
index 0000000..0980e6f
Binary files /dev/null and b/client/src/assets/help/image2.png differ
diff --git a/client/src/assets/help/image3.png b/client/src/assets/help/image3.png
new file mode 100644
index 0000000..fca775b
Binary files /dev/null and b/client/src/assets/help/image3.png differ
diff --git a/client/src/assets/help/image4.png b/client/src/assets/help/image4.png
new file mode 100644
index 0000000..374f08d
Binary files /dev/null and b/client/src/assets/help/image4.png differ
diff --git a/client/src/assets/help/image5.png b/client/src/assets/help/image5.png
new file mode 100644
index 0000000..5748f79
Binary files /dev/null and b/client/src/assets/help/image5.png differ
diff --git a/client/src/assets/help/image6.png b/client/src/assets/help/image6.png
new file mode 100644
index 0000000..85db587
Binary files /dev/null and b/client/src/assets/help/image6.png differ
diff --git a/client/src/assets/help/image7.png b/client/src/assets/help/image7.png
new file mode 100644
index 0000000..07c6726
Binary files /dev/null and b/client/src/assets/help/image7.png differ
diff --git a/client/src/assets/help/image8.png b/client/src/assets/help/image8.png
new file mode 100644
index 0000000..e9f04d0
Binary files /dev/null and b/client/src/assets/help/image8.png differ
diff --git a/client/src/assets/help/image9.png b/client/src/assets/help/image9.png
new file mode 100644
index 0000000..948c7d9
Binary files /dev/null and b/client/src/assets/help/image9.png differ
diff --git a/client/src/assets/ivdNt-logo-3regels.svg b/client/src/assets/ivdNt-logo-3regels.svg
new file mode 100644
index 0000000..f205f10
--- /dev/null
+++ b/client/src/assets/ivdNt-logo-3regels.svg
@@ -0,0 +1,12 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/AnnotateTab.vue b/client/src/components/AnnotateTab.vue
new file mode 100644
index 0000000..719e65c
--- /dev/null
+++ b/client/src/components/AnnotateTab.vue
@@ -0,0 +1,93 @@
+
+
+
+
+
+
+
+
+
+
+ No corpus has been selected.
+ Select a corpus
+
+
+
+
+
+ You have insufficient permissions to perform this action.
+ Select a different corpus
+
+
+
+
+
+
+
+
+
+ This corpus has no documents.
+ Upload documents to this corpus
+
+
+
+
+
+
+
+
+
+ None of the documents have annotations. Either:
+
+
+ Upload documents to this corpus that contain
+ source annotations
+
+
+ Start a tagger job to create annotations
+
+ Or wait for an existing job to finish
+
+
+
+
+
+
Oops!
+
+
+
+
diff --git a/client/src/components/EvaluationInfoBox.vue b/client/src/components/EvaluationInfoBox.vue
new file mode 100644
index 0000000..7f64eab
--- /dev/null
+++ b/client/src/components/EvaluationInfoBox.vue
@@ -0,0 +1,62 @@
+
+
+
+
+
+ Generated:
+ {{ new Date(eval.generated).toLocaleString() }}
+
+
+
+
+ Layer last modified (at time of generation):
+ {{ new Date(eval.lastModified).toLocaleString() }}
+
+
+
+
+ Hypothesis layer last modified (at time of generation):
+ {{ new Date(eval.hypothesisLastModified).toLocaleString() }}
+
+
+ Reference layer last modified (at time of generation):
+ {{ new Date(eval.referenceLastModified).toLocaleString() }}
+
+
+
+
+ Covered characters:
+ {{ (eval.coveredChars * 100 / eval.totalChars).toFixed(0) }}%
+
+
+ Covered alphabetic characters:
+ {{ (eval.coveredAlphabeticChars * 100 / eval.totalAlphabeticChars).toFixed(0) }}%
+
+
+
+
+
+
+
+
diff --git a/client/src/components/GCard.vue b/client/src/components/GCard.vue
new file mode 100644
index 0000000..20ffe97
--- /dev/null
+++ b/client/src/components/GCard.vue
@@ -0,0 +1,178 @@
+
+
+
+
+
+
+
+
+
+
+ {{ title }}
+
+
+ Someone forgot to put a title
+
+
+
+
+
+
+ {{ expand ? '×' : '?' }}
+
+
+
+
+
+
+
+ Someone forgot to put a help primer
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Somenone forgot to put content
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/GInfo.vue b/client/src/components/GInfo.vue
new file mode 100644
index 0000000..a68aa93
--- /dev/null
+++ b/client/src/components/GInfo.vue
@@ -0,0 +1,89 @@
+
+
+
+
+ Someone forgot to put the info.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/GSpinner.vue b/client/src/components/GSpinner.vue
new file mode 100644
index 0000000..f8f7fcc
--- /dev/null
+++ b/client/src/components/GSpinner.vue
@@ -0,0 +1,99 @@
+
+
+
+
+ ...
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/GTabs.vue b/client/src/components/GTabs.vue
new file mode 100644
index 0000000..3e15896
--- /dev/null
+++ b/client/src/components/GTabs.vue
@@ -0,0 +1,325 @@
+
+
+
+
+
+
+
+ { $router.push(x); induceCurrentTab() }" v-slot="{ Component }">
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/KeyValues.vue b/client/src/components/KeyValues.vue
new file mode 100644
index 0000000..3e32987
--- /dev/null
+++ b/client/src/components/KeyValues.vue
@@ -0,0 +1,103 @@
+
+
+
+
+ {{ key }}
+
+
+ {{format( key, data[key] )}}
+ here
+ {{ format( key, data[key]) }}
+ {{ format( key, data[key] ) }}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/help/BenchmarkSetsHelp.vue b/client/src/components/help/BenchmarkSetsHelp.vue
new file mode 100644
index 0000000..142501e
--- /dev/null
+++ b/client/src/components/help/BenchmarkSetsHelp.vue
@@ -0,0 +1,12 @@
+
+ Here you can see an overview of the test sets that can be used to benchmark taggers.
+ The complete benchmark sets, including train and dev sets can be found on GitHub:
+
+ galahad-corpus-data
+ .
+
+
+
diff --git a/client/src/components/help/CorpusFormHelp.vue b/client/src/components/help/CorpusFormHelp.vue
new file mode 100644
index 0000000..0ee2465
--- /dev/null
+++ b/client/src/components/help/CorpusFormHelp.vue
@@ -0,0 +1,6 @@
+
+ The corpus name is required. When the corpus is already tagged and
+ lemmatized, please add the name of the tagset.
+ You can add additional metadata on the time period covered by your corpus and the source of your corpus.
+ You can add other users with a CLARIN log-in (email) either as collaborators or as viewers.
+
diff --git a/client/src/components/help/CorpusHelp.vue b/client/src/components/help/CorpusHelp.vue
new file mode 100644
index 0000000..e4e5661
--- /dev/null
+++ b/client/src/components/help/CorpusHelp.vue
@@ -0,0 +1,20 @@
+
+
+
Step 1: Corpora
+
+ Press 'new' to create one or more corpora.
+ Select a corpus and press 'edit' to modify the corpus metadata
+ Select a corpus and press ‘delete’ to remove the selected corpus
+
+
Go to Annotate > Corpora
+
+
+
+
diff --git a/client/src/components/help/DataHelp.vue b/client/src/components/help/DataHelp.vue
new file mode 100644
index 0000000..6ba04e4
--- /dev/null
+++ b/client/src/components/help/DataHelp.vue
@@ -0,0 +1,42 @@
+
+
+
Step 1: Data
+ Create one or more corpora and upload documents to them.
+
+
+ Create a corpus
+
+ Enter a name and an
+ in the bottom line of the corpora list
+
+ Press 'create'
+
+ (Optional) Create additional corpora. Click on a corpus to select it for processing.
+
+ Go to Annotate > Corpora
+
+
+
+ Upload documents
+ The platform accepts plain text, tab-separated text, and TEI, NAF or FoLiA XML files,
+ see Help > Formats
+
+
+ Select files to upload in the upload field
+ Press the upload button
+
+ Keep the application open during the upload. If an upload failed (for example due to malformed
+ XML) an
+ error will display.
+ After uploading, your documents will show in Documents
+
+ Go to Annotate > Documents
+
+
+
+
+
+
diff --git a/client/src/components/help/DifferentTagsetsHelp.vue b/client/src/components/help/DifferentTagsetsHelp.vue
new file mode 100644
index 0000000..d8c470b
--- /dev/null
+++ b/client/src/components/help/DifferentTagsetsHelp.vue
@@ -0,0 +1,9 @@
+
+
+
+ * When the taggers use different tagsets, the information on the accuracy of the PoS tagging below is
+ meaningless. Please use the PoS confusion table to get more insight into the performance of the evaluated
+ tagger.
+
+
+
\ No newline at end of file
diff --git a/client/src/components/help/DocumentsHelp.vue b/client/src/components/help/DocumentsHelp.vue
new file mode 100644
index 0000000..24795ef
--- /dev/null
+++ b/client/src/components/help/DocumentsHelp.vue
@@ -0,0 +1,50 @@
+
+
+
Step 2: Documents
+ Upload documents to a corpus.
+
+ The platform accepts the following formats:
+
+
+ (Text-like formats) Plain text, Tab separated Values (TSV), CoNLL-U
+ (XML formats) TEI P5, TEI P5 Legacy, TEI P4, NLP Annotation Format (NAF), Format for Linguistic
+ Annotation (FoLiA)
+
+
+ Select files to upload in the upload field.
+ Press the upload button.
+
+ Keep the application open during the upload. If an upload failed (for example due
+ to malformed XML) an error will be shown.
+
+ After uploading, your documents are shown below.
+
+ Individual documents can be previewed, downloaded or deleted.
+
+ Go to Annotate > Documents
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/help/EvaluateHelp.vue b/client/src/components/help/EvaluateHelp.vue
new file mode 100644
index 0000000..07467b0
--- /dev/null
+++ b/client/src/components/help/EvaluateHelp.vue
@@ -0,0 +1,24 @@
+
+
+
Step 4: Evaluate
+
+ Inspect the tagged layers and evaluate the performance of the taggers by selecting a
+ hypothesis layer and a reference layer
+ .
+ As a reference layer, one can choose the source layer (uploaded annotations) or a version of your corpus
+ tagged
+ with a different tagger. Several aspects can be inspected: the distribution of the linguistic annotation,
+ accuracy metrics* and further information on the PoS confusion. You can also download these evaluations as
+ CSV
+ files.
+
+
+
+
+
+
diff --git a/client/src/components/help/ExportHelp.vue b/client/src/components/help/ExportHelp.vue
new file mode 100644
index 0000000..b284b39
--- /dev/null
+++ b/client/src/components/help/ExportHelp.vue
@@ -0,0 +1,23 @@
+
+
+
Step 5: Export
+
+ You can export your annotated corpus in various formats. The export contains the metadata of the selected
+ tagger. If the chosen export format is the same as the original file format, you can choose to merge the
+ linguistic annotation with the uploaded source files. If you do not choose to merge, your export will not
+ take the original encoding into account.
+
+
+ You can choose not to export the PoS features, for instance NOU-C instead of NOU-C(number=sg) .
+
+
+ When choosing TEI P5 as export format, the LAnCeLoT annotation tool is available to manually correct the
+ PoS-tagging and lemmatization of your corpus.
+
+
+
+
+
diff --git a/client/src/components/help/Intro.vue b/client/src/components/help/Intro.vue
new file mode 100644
index 0000000..a864404
--- /dev/null
+++ b/client/src/components/help/Intro.vue
@@ -0,0 +1,73 @@
+
+
+
Overview
+
+ Welcome to GaLAHaD: Generating Linguistic Annotations for Historical Dutch. This page provides an overview of the
+ GaLAHaD application.
+
+
+ GaLAHaD is a platform that enables (non-technical) users to add linguistic annotation to (primarily) historical
+ Dutch documents, which can then in turn be inspected, evaluated and exported. Here follows a short introduction of
+ what GaLAHaD entails and what users can expect from this platform. Thereafter follows a short step-by-step guide
+ that takes you through the platform.
+
+
Context of the application
+
+ Linguistic annotation of (historical) texts is invaluable for various kinds of research and, of course, a
+ consistent and apt annotation with the least amount of errors is necessary if one wants to research (words in)
+ large amounts of text. However, lack of a central place where researchers were able to look at the possibilities
+ of linguistically enriching historical Dutch texts made it hard to find a good fitting model without substantial
+ effort and technical knowledge. So instead, researchers might use (or develop) a model for their own documents
+ without looking at other models.
+
+
+ Although researchers use their own models, there was a wish to be able to look at all the possibilities of
+ linguistically enriching historical texts and also to compare the different models used and to be able to use the
+ one(s) most suited for their specific data. Therefore, in the context of
+ CLARIAH (Common Lab Research Infrastructure for the Arts and
+ Humanities), GaLAHaD has been created at INT to fulfil in that need.
+
+
Intended use
+
For linguistic users
+
+ GaLAHaD aims to be a central platform that allows users to do various things. The main thing for researchers is to
+ linguistically enrich (historical) data (corpora). GaLAHad enables a user to upload documents (in several formats)
+ to a corpus. This corpus can then be enriched with one or more taggers; this is also possible if a corpus has
+ already been enriched. Each of these taggers (and also enrichment that was added prior to uploading the documents)
+ can then be compared and evaluated.
+
+
+ With several (visually helpful) criteria a user should be able to decide which tagger is the best for their data
+ in a reasonable timespan. It is also possible to download all evaluation criteria in separate Excel-files.
+ Furthermore, a user is then able to export their corpus to
+ BlackLab , to add some manual corrections (CoBaLT ) or to download the enriched data (either as new documents or appended to the old documents). It should be noted
+ that individual steps take longer the more data are uploaded.
+
+
It also possible to make your corpus/corpora publicly available. TBD
+
For technical users
+
+ For technical users it is also possible to look at the performance of all taggers that exist in GaLAHaD.
+ Furthermore, it is also possible to add your own tagger to see how it compares to the other taggers. TBD
+
+
What can you expect of the Galahad?
+
+ GaLAHaD as a platform thus serves as a central hub for enriching historical Dutch. For instance, it allows users
+ to compare different taggers, to annotate data and to add your own material. Users can expect all of this to be
+ done rather easily and fast, though it should be noted that each operation takes more time, the bigger the
+ corpus/corpora (or documents) is/are. Besides ease of access, you can also expect good quality of linguistically
+ enriched data, though it of course depends how suited a tagger is for the specific data. Some taggers are more
+ suited (more trained) for older texts and some more for newer texts.
+
+
+ Even with the current state of the art, GaLAHaD can at best help to minimize the amount of manual corrections
+ needed. Depending on your use case, you should still expect that a significant post-processing step is required.
+
+
+
+
diff --git a/client/src/components/help/JobsHelp.vue b/client/src/components/help/JobsHelp.vue
new file mode 100644
index 0000000..909e51c
--- /dev/null
+++ b/client/src/components/help/JobsHelp.vue
@@ -0,0 +1,23 @@
+
+
+
Step 3: Jobs
+
+ You can use some or all of the available taggers to tag your documents. Each tagger will create a linguistic
+ annotation layer. Use the filters to find a tagger that works well for you or take a look at the
+ benchmark results .
+
+
+ To start a tagging job, select one of the taggers and click on 'View & Tag'.
+
+
Go to Annotate > Jobs
+
+
+
+
diff --git a/client/src/components/help/index.ts b/client/src/components/help/index.ts
new file mode 100644
index 0000000..cb45b85
--- /dev/null
+++ b/client/src/components/help/index.ts
@@ -0,0 +1,22 @@
+/**
+ * A collection of help components that can be looped over to display help for each section of the application.
+ */
+
+import intro from './Intro.vue'
+import corpusHelp from './CorpusHelp.vue'
+import documentsHelp from './DocumentsHelp.vue'
+import evaluateHelp from './EvaluateHelp.vue'
+import exportHelp from './ExportHelp.vue'
+import jobsHelp from './JobsHelp.vue'
+
+const helpObject = {
+ intro: intro,
+ corpora: corpusHelp,
+ documents: documentsHelp,
+ jobs: jobsHelp,
+ evaluate: evaluateHelp,
+ export: exportHelp,
+} as const
+
+export default helpObject;
+export const order: Array = ['intro', 'corpora', 'documents', 'jobs', 'evaluate', 'export'];
diff --git a/client/src/components/index.ts b/client/src/components/index.ts
new file mode 100644
index 0000000..567369b
--- /dev/null
+++ b/client/src/components/index.ts
@@ -0,0 +1,113 @@
+import GCard from "./GCard.vue"
+import GInfo from "./GInfo.vue"
+import GSpinner from "./GSpinner.vue"
+import GTabs from "./GTabs.vue"
+import KeyValues from "./KeyValues.vue"
+import AnnotateTab from "./AnnotateTab.vue"
+import EvaluationInfoBox from "./EvaluationInfoBox.vue"
+// Tables
+import GTable from "./tables/GTable.vue"
+import CorpusTable from "./tables/CorpusTable.vue"
+import DocumentsTable from "./tables/DocumentsTable.vue"
+import RightFloatCell from "./tables/RightFloatCell.vue"
+// Links
+import ContributeTaggerLink from "./links/ContributeTaggerLink.vue"
+import ExternalLink from "./links/ExternalLink.vue"
+import MailAddress from "./links/MailAddress.vue"
+import GNav from "./links/GNav.vue"
+import HelpLink from "./links/HelpLink.vue"
+import GlossaryLink from "./links/GlossaryLink.vue"
+// Inputs
+import JobSelect from "./input/JobSelect.vue"
+import FileFormatInput from "./input/FileFormatInput.vue"
+import GInput from "./input/GInput.vue"
+import GButton from "./input/GButton.vue"
+import DownloadButton from './input/DownloadButton.vue'
+import InspectButton from './input/InspectButton.vue'
+// Modals
+import GModal from "./modals/GModal.vue"
+import VariantsModal from "./modals/VariantsModal.vue"
+import DeleteModal from "./modals/DeleteModal.vue"
+import ComparisonModal from "./modals/ComparisonModal.vue"
+import CorpusForm from "./modals/corpus/CorpusForm.vue"
+import JobModal from "./modals/jobs/JobModal.vue"
+
+export {
+ GCard,
+ GInfo,
+ GSpinner,
+ GTabs,
+ KeyValues,
+ AnnotateTab,
+ EvaluationInfoBox,
+ // Tables
+ GTable,
+ DocumentsTable,
+ CorpusTable,
+ RightFloatCell,
+ // Links
+ ExternalLink,
+ GNav,
+ GlossaryLink,
+ HelpLink,
+ MailAddress,
+ ContributeTaggerLink,
+ // Inputs
+ GInput,
+ FileFormatInput,
+ GButton,
+ DownloadButton,
+ InspectButton,
+ JobSelect,
+ // Modals
+ GModal,
+ VariantsModal,
+ DeleteModal,
+ ComparisonModal,
+ CorpusForm,
+ JobModal,
+}
+
+// I do not recommend using global components like below, since there is no type completion
+// better to explicitly import components from the object above
+// However, nested components can be tricky
+// If you want type completion, just make sure you explicitly import the components
+
+export default {
+ install: (app, options) => {
+ // /* declare global component */
+ app.component("GCard", GCard)
+ app.component("GInfo", GInfo)
+ app.component("GSpinner", GSpinner)
+ app.component("GTabs", GTabs)
+ app.component("KeyValues", KeyValues)
+ app.component("AnnotateTab", AnnotateTab)
+ app.component("EvaluationInfoBox", EvaluationInfoBox)
+ // Tables
+ app.component("CorpusTable", CorpusTable)
+ app.component("DocumentsTable", DocumentsTable)
+ app.component("GTable", GTable)
+ app.component("RightFloatCell", RightFloatCell)
+ // Links
+ app.component("ContributeTaggerLink", ContributeTaggerLink)
+ app.component("ExternalLink", ExternalLink)
+ app.component("GlossaryLink", GlossaryLink)
+ app.component("GNav", GNav)
+ app.component("MailAddress", MailAddress)
+ app.component("HelpLink", HelpLink)
+ // Inputs
+ app.component("FileFormatInput", FileFormatInput)
+ app.component("GInput", GInput)
+ app.component("GButton", GButton)
+ app.component("DownloadButton", DownloadButton)
+ app.component("InspectButton", InspectButton)
+ app.component("JobSelect", JobSelect)
+ // Modals
+ app.component("GModal", GModal)
+ app.component("VariantsModal", VariantsModal)
+ app.component("DeleteModal", DeleteModal)
+ app.component("ComparisonModal", ComparisonModal)
+ app.component("CorpusForm", CorpusForm)
+ app.component("JobModal", JobModal)
+ }
+}
\ No newline at end of file
diff --git a/client/src/components/input/DownloadButton.vue b/client/src/components/input/DownloadButton.vue
new file mode 100644
index 0000000..7bd477c
--- /dev/null
+++ b/client/src/components/input/DownloadButton.vue
@@ -0,0 +1,23 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/input/FileFormatInput.vue b/client/src/components/input/FileFormatInput.vue
new file mode 100644
index 0000000..2f6d2d5
--- /dev/null
+++ b/client/src/components/input/FileFormatInput.vue
@@ -0,0 +1,32 @@
+
+
+
+
+
+
+
diff --git a/client/src/components/input/GButton.vue b/client/src/components/input/GButton.vue
new file mode 100644
index 0000000..3049d86
--- /dev/null
+++ b/client/src/components/input/GButton.vue
@@ -0,0 +1,129 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/input/GInput.vue b/client/src/components/input/GInput.vue
new file mode 100644
index 0000000..8185624
--- /dev/null
+++ b/client/src/components/input/GInput.vue
@@ -0,0 +1,344 @@
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/input/InspectButton.vue b/client/src/components/input/InspectButton.vue
new file mode 100644
index 0000000..66d9695
--- /dev/null
+++ b/client/src/components/input/InspectButton.vue
@@ -0,0 +1,5 @@
+
+
+
+
+
diff --git a/client/src/components/input/JobSelect.vue b/client/src/components/input/JobSelect.vue
new file mode 100644
index 0000000..9155553
--- /dev/null
+++ b/client/src/components/input/JobSelect.vue
@@ -0,0 +1,95 @@
+
+
+
+ Select the result of a tagger job as a hypothesis or reference layer.
+ It is also possible to select the source annotations.
+
+
+
+
+
+ Not all documents have been tagged yet. It is still possible to select this layer, but it will be
+ incomplete.
+ Alternatively, start a new tagger job or wait for the
+ current job to finish.
+
+
+ Some documents in this corpus have no source annotations. It is still possible to select this layer, but it
+ will be
+ incomplete.
+ Alternatively, go to documents and remove or add
+ documents.
+
+
+
+
+
+
+
diff --git a/client/src/components/input/UploadDocuments.vue b/client/src/components/input/UploadDocuments.vue
new file mode 100644
index 0000000..5ff21ea
--- /dev/null
+++ b/client/src/components/input/UploadDocuments.vue
@@ -0,0 +1,107 @@
+
+
+
+
+
+
+
+
+ Select file(s)
+
+
+
filesToUpload = Object.values(e.target.files as FileList)" />
+
+
+
+
+
+ {{ file.name }}
+
+
+ + {{ filesToUpload.length - 4 }} more {{ filesToUpload.length == 5 ? "file" : "files" }}
+
+
+
+
+
+
+ Upload
+
+
+ ✖ clear
+
+
+
+
+
+ You have selected some filetype(s) that are not supported in GaLAHaD:
+
+
+ Do you want to upload the text? You can:
+
+ copy-paste it to NotePad
+ save as .txt
+ upload it to GaLAHaD
+
+
+
+
+
+ Upload will continue in the background, please don't close the browser. Currently uploading:
+ {{ uploadBusyCount }}
+
+
+
+
+
+ {{ key }}: {{ value.message }}
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/links/ContributeTaggerLink.vue b/client/src/components/links/ContributeTaggerLink.vue
new file mode 100644
index 0000000..40fe1b9
--- /dev/null
+++ b/client/src/components/links/ContributeTaggerLink.vue
@@ -0,0 +1,14 @@
+
+
+
+ Feel free to add your own tagger
+ Contribute
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/links/ExternalLink.vue b/client/src/components/links/ExternalLink.vue
new file mode 100644
index 0000000..4b51f1d
--- /dev/null
+++ b/client/src/components/links/ExternalLink.vue
@@ -0,0 +1,20 @@
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/links/GNav.vue b/client/src/components/links/GNav.vue
new file mode 100644
index 0000000..e535451
--- /dev/null
+++ b/client/src/components/links/GNav.vue
@@ -0,0 +1,39 @@
+
+
+
+
+
+
+
diff --git a/client/src/components/links/GlossaryLink.vue b/client/src/components/links/GlossaryLink.vue
new file mode 100644
index 0000000..7d98e4c
--- /dev/null
+++ b/client/src/components/links/GlossaryLink.vue
@@ -0,0 +1,27 @@
+
+
+ {{ gloss }}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/links/HelpLink.vue b/client/src/components/links/HelpLink.vue
new file mode 100644
index 0000000..4181d21
--- /dev/null
+++ b/client/src/components/links/HelpLink.vue
@@ -0,0 +1,20 @@
+
+
+ read more on {{ subject }}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/links/MailAddress.vue b/client/src/components/links/MailAddress.vue
new file mode 100644
index 0000000..eb2678d
--- /dev/null
+++ b/client/src/components/links/MailAddress.vue
@@ -0,0 +1,11 @@
+
+
+ {{ address }}
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/modals/ComparisonModal.vue b/client/src/components/modals/ComparisonModal.vue
new file mode 100644
index 0000000..ebebad8
--- /dev/null
+++ b/client/src/components/modals/ComparisonModal.vue
@@ -0,0 +1,92 @@
+
+
+
+
+ Here you can see a sample of how a token was tagged by {{ hypothesisJob }} and {{ referenceJob
+ }} .
+ The samples are a random selection of all tokens in this category.
+
+
+
+ {{ data.field.label || data.field.key }}
+ {{ data.value }}
+
+
+
+ Download all samples for this category.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/modals/DeleteModal.vue b/client/src/components/modals/DeleteModal.vue
new file mode 100644
index 0000000..6924537
--- /dev/null
+++ b/client/src/components/modals/DeleteModal.vue
@@ -0,0 +1,22 @@
+
+
+
+ {{ displayname }} will be deleted from the Galahad server.
+ Make sure you have a copy of the original files somewhere.
+
+ You will delete {{ displayname }} . This can not be undone.
+
+ Confirm
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/modals/GModal.vue b/client/src/components/modals/GModal.vue
new file mode 100644
index 0000000..c8645ff
--- /dev/null
+++ b/client/src/components/modals/GModal.vue
@@ -0,0 +1,108 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Somenone forgot to put the content
+
+
+
+ Close
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/modals/VariantsModal.vue b/client/src/components/modals/VariantsModal.vue
new file mode 100644
index 0000000..f27fadd
--- /dev/null
+++ b/client/src/components/modals/VariantsModal.vue
@@ -0,0 +1,47 @@
+
+
+
+
+ Types of lemma {{ variantsToDisplay?.lemma }} and part-of-speech {{ variantsToDisplay?.pos
+ }}
+
+
+ This is an overview of all types belonging to the chosen lemma, part-of-speech pair.
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/modals/corpus/CorpusForm.vue b/client/src/components/modals/corpus/CorpusForm.vue
new file mode 100644
index 0000000..af7539c
--- /dev/null
+++ b/client/src/components/modals/corpus/CorpusForm.vue
@@ -0,0 +1,277 @@
+
+
+
+
+
+
+
+
+
+
+
+ Name: (Required)
+
+
+
+
+
+
+ Year from:
+
+
+
+
+
+
+ Year to:
+
+
+
+
+
+
+
+
+ Tagset :
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Benchmark set:
+
+ Benchmark
+
+
+
+
+
+
+
+
+
+
+
+ Source name:
+
+
+
+
+
+
+ Source url:
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ update ? 'Update' : 'Create' }}
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/modals/corpus/UserList.vue b/client/src/components/modals/corpus/UserList.vue
new file mode 100644
index 0000000..fbb1064
--- /dev/null
+++ b/client/src/components/modals/corpus/UserList.vue
@@ -0,0 +1,122 @@
+
+
+
+
+
+
+
+
+ {{ listName }}: {{ users.length }}
+
+
+
+
+ Add
+
+
+
+
+
+ {{ listName }}: {{ users.length }}
+
+
+
+
+
+
{{ userName }}
+
+ ✖ remove
+
+
+
+
+
+
+
+
+
+ remove access for user
+
+
+
+
+
+
diff --git a/client/src/components/modals/jobs/JobModal.vue b/client/src/components/modals/jobs/JobModal.vue
new file mode 100644
index 0000000..363075d
--- /dev/null
+++ b/client/src/components/modals/jobs/JobModal.vue
@@ -0,0 +1,237 @@
+
+
+
+ Here you can start a job to tag the documents in your corpus. This may take a while, depending on the corpus
+ size.
+ You can also stop and delete existing jobs. A preview of the resulting annotation layer is shown as
+ well.
+
+ The tagger status (pending, busy, error, finished) will be displayed in the status bar.
+ Tagging is carried out in the background. You do not need to keep the application open.
+ The total number of documents that are being processed is given so as to give an indication of how busy the
+ server is.
+
+
+
+
+
+
Connecting to tagger...
+
+
+
+
+
+
+
+ The tagger is currently unavailable. Please try again later.
+
+
+
+
+
+
+ GaLAHaD is currently processing {{ jobIndication }} {{ jobIndication == 1 ? 'document' :
+ 'documents' }}
+
+
+ Calculating current server load...
+
+
+
+
+
+
+
+
+
+
+
+
+ Start
+
+
+ Stop
+
+
+ Delete
+
+
+
+
+
+
+
+
+
+
+
+
+
+ The following
+ {{ job.progress.failed == 1 ? "document" : "documents" }} encountered errors:
+
+
+ {{ doc }} :
+ {{ message }}
+
+
+
+ ... and {{ job.progress.failed - 5 }} more errors are omitted.
+
+ None
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/modals/jobs/ProgressSegment.vue b/client/src/components/modals/jobs/ProgressSegment.vue
new file mode 100644
index 0000000..c1e37fc
--- /dev/null
+++ b/client/src/components/modals/jobs/ProgressSegment.vue
@@ -0,0 +1,34 @@
+
+
+ {{ value > 0 ? labelText : '' }}
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/tables/CorpusTable.vue b/client/src/components/tables/CorpusTable.vue
new file mode 100644
index 0000000..2414d56
--- /dev/null
+++ b/client/src/components/tables/CorpusTable.vue
@@ -0,0 +1,152 @@
+
+
+
+
+
+ {{ displayCorpora.length }} {{ type }} {{ displayCorpora.length == 1 ? ' corpus' : ' corpora' }}
+
+
+
+
+
+
+
+
+
+
+
+ First, create a new corpus.
+
+
+
+
+
+
+ New
+
+ Edit
+
+ Delete
+
+ Click on a row to select a
+ corpus.
+
+
+
+
+
+ {{ Utils.formatBytes(data.value) }}
+ ~ 0
+
+
+
+
+ {{ new Date(data.value).toLocaleString("nl", {
+ year: "2-digit",
+ month: "2-digit",
+ day: "numeric",
+ hour: "2-digit",
+ minute: "2-digit",
+ }) }}
+
+
+
+
+ {{ formatCollaborators(data.item) }}
+
+
+
+
+
+ {{ data.item.sourceName ? data.item.sourceName : data.item.sourceURL }}
+
+ {{ data.item.sourceName }}
+ Not declared
+
+
+
+
+
diff --git a/client/src/components/tables/DocumentsTable.vue b/client/src/components/tables/DocumentsTable.vue
new file mode 100644
index 0000000..58bf0b7
--- /dev/null
+++ b/client/src/components/tables/DocumentsTable.vue
@@ -0,0 +1,225 @@
+
+
+
+
+
+
+ No documents
+
+
+ {{ documentsStore.available.length }}
+ {{ documentsStore.available.length === 1 ? 'document' : 'documents' }}
+ in corpus {{ corpus.name }}
+
+
+
+
+
+
+
+
+
+
+ No corpus
+ selected.
+
+ This corpus is empty. Upload documents to the corpus.
+
+
+
+
+
+
+
+
+
+ {{ data.value ? '👍' : '🔥' }}
+
+
+
+
+ {{ data.value }}
+
+
+
+
+
+ {{ data.value }}
+
+
+
+ source annotations
+ (token / PoS / lemma)
+
+
+
+
+
+ {{ data.value.numWordForms }} /
+ {{ data.value.numPOS }} /
+ {{ data.value.numLemma }}
+
+
+
+
+
+
+
+
+
+ {{ data.value }}
+
+
+
+
+
+
+
+ {{ new Date(data.value).toLocaleString("nl", {
+ year: "2-digit",
+ month: "2-digit",
+ day: "numeric",
+ hour: "2-digit",
+ minute: "2-digit",
+ }) }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Source layer preview of document {{ previewDocument ? previewDocument.name : ""
+ }}
+
+ Here you can inspect a small part of the source layer of the document.
+
+
+ Loading...
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/tables/GTable.vue b/client/src/components/tables/GTable.vue
new file mode 100644
index 0000000..33ef4ec
--- /dev/null
+++ b/client/src/components/tables/GTable.vue
@@ -0,0 +1,455 @@
+
+
+
+ {{ title }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Here should be an instruction how to fill the content.
+
+
+
+
+
+
+
+
+
+ {{ field.label || field.key }}
+
+
+
+
+
+ ▲ | ▼
+
+
+ ▲
+ ▲
+ |
+ ▼
+ ▼
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ {{ item[field.key] }}
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/tables/LayerViewer.vue b/client/src/components/tables/LayerViewer.vue
new file mode 100644
index 0000000..4ae0038
--- /dev/null
+++ b/client/src/components/tables/LayerViewer.vue
@@ -0,0 +1,113 @@
+
+
+
+
+
+ Token
+ Lemma
+ PoS
+
+
+
+
+ {{ term.targets.map(x => x.literal).join('_') }}
+ {{ term.lemma }}
+ {{ term.pos }}
+
+
+
+
No layer found
+
+
+
+
+ {{ cursor + 1 }} - {{ cursor + pagesize }}
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/tables/MetricsFilter.vue b/client/src/components/tables/MetricsFilter.vue
new file mode 100644
index 0000000..f92832e
--- /dev/null
+++ b/client/src/components/tables/MetricsFilter.vue
@@ -0,0 +1,67 @@
+
+
+
+ Annotation:
+
+
+
+
+ Group by:
+
+
+
+
+ Single/multiple analysis:
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/components/tables/MetricsTable.vue b/client/src/components/tables/MetricsTable.vue
new file mode 100644
index 0000000..b65b029
--- /dev/null
+++ b/client/src/components/tables/MetricsTable.vue
@@ -0,0 +1,124 @@
+
+
+
+
+
+ PoS comparison is base on the full PoS, including features.
+ Click on a entry to view samples.
+
+
+
+
+
+ Select a reference layer and a hypothesis layer to generate metrics.
+
+
+
+ Generating metrics for large corpora may take a while...
+
+
+
+
+
+
+
+
+ {{ `${data.value ? parseFloat(data.value).toFixed(2) : 0}` }}
+
+
+
+
+
+
+ {{ (`${(data.value.count / data.item.count * 100).toFixed(1)}%`) }}
+ ({{ data.value.count.toString() }})
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/components/tables/RightFloatCell.vue b/client/src/components/tables/RightFloatCell.vue
new file mode 100644
index 0000000..1108291
--- /dev/null
+++ b/client/src/components/tables/RightFloatCell.vue
@@ -0,0 +1,22 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/main.ts b/client/src/main.ts
new file mode 100644
index 0000000..4a229a1
--- /dev/null
+++ b/client/src/main.ts
@@ -0,0 +1,49 @@
+import 'mutationobserver-shim'
+import gcomponents from '@/components'
+import App from './App.vue'
+import router from './router'
+import stores from './stores'
+import { createApp, watch } from 'vue/dist/vue.esm-bundler' // bug doesn't let use use 'vue' here
+import { createPinia } from 'pinia'
+import { setAxiosBaseUrl } from './api/api'
+
+setAxiosBaseUrl()
+
+const pinia = createPinia()
+const app = createApp(App)
+
+app.use(pinia)
+app.use(gcomponents)
+
+// Stores
+const jobSelection = stores.useJobSelection()
+const corporaStore = stores.useCorpora()
+
+// On pageload, retrieve values from query.
+watch(() => router.currentRoute.value.query, () => {
+ const q = router.currentRoute.value.query
+ // Only set if q values are not null.
+ if (q.corpus) corporaStore.activeUUID = router.currentRoute.value.query.corpus
+ if (q.hypothesis) jobSelection.hypothesisJobId = router.currentRoute.value.query.hypothesis
+ if (q.reference) jobSelection.referenceJobId = router.currentRoute.value.query.reference
+})
+
+// On change, update query.
+watch(() => corporaStore.activeUUID, updateQuery)
+watch(() => jobSelection.hypothesisJobId, updateQuery)
+watch(() => jobSelection.referenceJobId, updateQuery)
+
+function updateQuery() {
+ let newQuery = {}
+ // We do not want empty keys in the query, so we explicitly check each value before setting it
+ if (corporaStore.activeUUID) newQuery["corpus"] = corporaStore.activeUUID
+ if (jobSelection.hypothesisJobId) newQuery["hypothesis"] = jobSelection.hypothesisJobId
+ if (jobSelection.referenceJobId) newQuery["reference"] = jobSelection.referenceJobId
+ router.replace({
+ query: newQuery,
+ hash: router.currentRoute.value.hash // preserve the hash
+ })
+}
+
+app.use(router)
+app.mount('#app')
diff --git a/client/src/router/index.ts b/client/src/router/index.ts
new file mode 100644
index 0000000..d847dee
--- /dev/null
+++ b/client/src/router/index.ts
@@ -0,0 +1,184 @@
+import { createRouter, createWebHistory } from 'vue-router'
+
+import HomeView from '@/views/HomeView.vue'
+
+import AnnotateView from '@/views/annotate/AnnotateView.vue'
+
+import CorporaView from '@/views/annotate/subviews/CorporaView.vue'
+import DocumentsView from '@/views/annotate/subviews/DocumentsView.vue'
+import JobsView from '@/views/annotate/subviews/JobsView.vue'
+
+import EvaluateView from '@/views/annotate/subviews/evaluate/EvaluateView.vue'
+import DistributionView from '@/views/annotate/subviews/evaluate/subviews/DistributionView.vue'
+import GlobalMetricsView from '@/views/annotate/subviews/evaluate/subviews/GlobalMetricsView.vue'
+import GroupedMetricsView from '@/views/annotate/subviews/evaluate/subviews/GroupedMetricsView.vue'
+import ConfusionView from '@/views/annotate/subviews/evaluate/subviews/ConfusionView.vue'
+
+import ExportView from '@/views/annotate/subviews/ExportView.vue'
+
+import ApplicationView from '@/views/application/ApplicationView.vue'
+import AboutView from '@/views/application/subviews/AboutView.vue'
+
+import OverviewView from '@/views/overview/OverviewView.vue'
+import TaggersView from '@/views/overview/subviews/TaggersView.vue'
+import TagsetsView from '@/views/overview/subviews/TagsetsView.vue'
+import DatasetsView from '@/views/overview/subviews/DatasetsView.vue'
+import BenchmarksView from '@/views/overview/subviews/BenchmarksView.vue'
+
+import ContributeView from '@/views/contribute/ContributeView.vue'
+import ContributeTaggersView from '@/views/contribute/subviews/ContributeTaggersView.vue'
+import ContributeDatasetsView from '@/views/contribute/subviews/ContributeDatasetsView.vue'
+
+import HelpView from '@/views/help/HelpView.vue'
+import GeneralView from '@/views/help/subviews/GeneralView.vue'
+import DocumentFormatsView from '@/views/help/subviews/formats/DocumentFormatsView.vue'
+import GlossaryView from '@/views/help/subviews/GlossaryView.vue'
+
+import UserView from '@/views/UserView.vue'
+import PageNotFound from '@/views/PageNotFound.vue'
+
+export type RouterQuery = { corpus?: string, referenceJob?: string, hypothesisJob?: string }
+
+const routes = [
+ { path: '/:pathMatch(.*)*', component: PageNotFound },
+ {
+ path: '/',
+ redirect: '/home'
+ },
+ {
+ path: '/home',
+ name: 'Home',
+ component: HomeView
+ }, {
+ path: '/annotate',
+ name: 'Annotate',
+ redirect: '/annotate/corpora',
+ component: AnnotateView,
+ children: [
+ {
+ path: 'corpora',
+ name: 'Corpora',
+ component: CorporaView,
+ },
+ {
+ path: 'documents',
+ name: 'Documents',
+ component: DocumentsView,
+ },
+ {
+ path: 'jobs',
+ name: 'Jobs',
+ component: JobsView
+ },
+ {
+ path: 'evaluate',
+ name: 'Evaluate',
+ component: EvaluateView,
+ redirect: '/annotate/evaluate/distribution',
+ props: { basePath: "/annotate/evaluate" },
+ children: [
+ {
+ path: 'distribution',
+ component: DistributionView
+ },
+ {
+ path: 'global_metrics',
+ component: GlobalMetricsView,
+ },
+ {
+ path: 'grouped_metrics',
+ component: GroupedMetricsView,
+ },
+ {
+ path: 'confusion',
+ component: ConfusionView
+ }
+ ]
+ },
+ {
+ path: 'export',
+ name: 'Export',
+ component: ExportView
+ }
+ ]
+ }, {
+ path: '/application',
+ name: 'Application',
+ component: ApplicationView,
+ children: [
+ { path: 'about', component: AboutView },
+ ]
+ }, {
+ path: '/overview',
+ name: 'Overview',
+ redirect: '/overview/taggers',
+ component: OverviewView,
+ children: [
+ { path: 'taggers', component: TaggersView },
+ { path: 'tagsets', component: TagsetsView },
+ { path: 'datasets', component: DatasetsView },
+ { path: 'benchmarks', component: BenchmarksView },
+ {
+ path: 'evaluate', component: EvaluateView,
+ props: { basePath: "/overview/evaluate" },
+ redirect: '/overview/evaluate/distribution',
+ children: [
+ { path: 'distribution', component: DistributionView },
+ { path: 'global_metrics', component: GlobalMetricsView },
+ { path: 'grouped_metrics', component: GroupedMetricsView },
+ { path: 'confusion', component: ConfusionView }
+ ]
+ },
+ { path: 'contribute', component: ContributeTaggersView }
+ ]
+ }, {
+ path: '/contribute', component: ContributeView, children: [
+ { path: 'taggers', component: ContributeTaggersView },
+ { path: 'datasets', component: ContributeDatasetsView }
+ ]
+ }, {
+ path: '/help',
+ name: 'Help',
+ redirect: '/help/general',
+ component: HelpView,
+ children: [
+ {
+ path: 'general',
+ component: GeneralView
+ },
+ {
+ path: 'formats',
+ component: DocumentFormatsView
+ },
+ {
+ path: 'glossary',
+ component: GlossaryView
+ },
+ ]
+ }, {
+ path: '/user',
+ component: UserView
+ },
+]
+
+const router = createRouter({
+ history: createWebHistory('/galahad/'), //import.meta.env.BASE_URL ),
+ routes,
+ scrollBehavior(to, from, savedPosition) {
+ if (to && to.hash) {
+ return {
+ el: to.hash,
+ top: 200 // avoid the top bar
+ // behavior: 'smooth'
+ };
+ } else if (savedPosition) {
+ return savedPosition;
+ } else if (from.path === to.path) {
+ return // since we're on the same page, don't scroll to top
+ } else {
+ return { top: 0 };
+ }
+ }
+})
+
+export default router
diff --git a/client/src/shims-tsx.d.ts b/client/src/shims-tsx.d.ts
new file mode 100644
index 0000000..3eb3bc1
--- /dev/null
+++ b/client/src/shims-tsx.d.ts
@@ -0,0 +1,13 @@
+import Vue, { VNode } from 'vue'
+
+declare global {
+ namespace JSX {
+ // tslint:disable no-empty-interface
+ interface Element extends VNode {}
+ // tslint:disable no-empty-interface
+ interface ElementClass extends Vue {}
+ interface IntrinsicElements {
+ [elem: string]: any;
+ }
+ }
+}
\ No newline at end of file
diff --git a/client/src/shims-vue.d.ts b/client/src/shims-vue.d.ts
new file mode 100644
index 0000000..34a8f8e
--- /dev/null
+++ b/client/src/shims-vue.d.ts
@@ -0,0 +1,7 @@
+declare module '*.vue' {
+ import {defineComponent} from 'vue'
+ export default Vue
+}
+
+declare module 'vue-html2pdf'
+declare module '@/../version.yml'
\ No newline at end of file
diff --git a/client/src/stores/app.ts b/client/src/stores/app.ts
new file mode 100644
index 0000000..a3b05e9
--- /dev/null
+++ b/client/src/stores/app.ts
@@ -0,0 +1,65 @@
+// Libraries & stores
+import { ref } from "vue"
+import { defineStore } from "pinia"
+import yaml from 'js-yaml'
+import { AxiosError } from "axios"
+// Types & API
+import * as API from "@/api/application"
+
+// Custom types
+type ErrorMessage = {
+ statusCode: string
+ message: string
+}
+
+/**
+ * Mostly for global error handling.
+ */
+const app = defineStore('app', () => {
+ // Fields
+ const benchmarks = ref("" as string)
+ const errors = ref([] as string[])
+
+ // Methods
+ function addError(message: string) {
+ errors.value.push(message)
+ }
+
+ function resetErrors() {
+ errors.value = []
+ }
+
+ /**
+ * Display error in modal.
+ * @param intent Human readable explanation.
+ * @param error Axios error.
+ */
+ function handleServerError(intent: string, error: AxiosError) {
+ if (error.response) {
+ // The request was made and the server responded with a status code
+ // that falls out of the range of 2xx
+ addError("Server error: Failed to " + intent + " with the following error:\n" + error?.response?.data?.message)
+ } else if (error.request) {
+ // The request was made but no response was received
+ // `error.request` is an instance of XMLHttpRequest in the browser and an instance of
+ // http.ClientRequest in node.js
+ // this is a disconnect, it is handled by the user store
+ } else {
+ // Something happened in setting up the request that triggered an Error
+ addError("Request error: Failed to " + intent + " because something went wrong setting up the request:\n" + error.message)
+ }
+ }
+
+ function fetchBenchmarks() {
+ API.getBenchmarks()
+ .then(response => {
+ benchmarks.value = yaml.load(response.data) as string
+ })
+ .catch(error => handleServerError("fetch benchmarks", error))
+ }
+
+ // Exports
+ return { benchmarks, errors, resetErrors, fetchBenchmarks, handleServerError }
+})
+
+export default app
diff --git a/client/src/stores/assays.ts b/client/src/stores/assays.ts
new file mode 100644
index 0000000..4fb72a7
--- /dev/null
+++ b/client/src/stores/assays.ts
@@ -0,0 +1,41 @@
+// Libraries & stores
+import { computed, ref } from 'vue'
+import { defineStore } from 'pinia'
+import stores, { AppStore } from '@/stores'
+// Types & API
+import { Assays } from '@/types/assays'
+import * as API from '@/api/assays'
+
+/**
+ * Contains dataset assays.
+ */
+const useAssays = defineStore('assays', () => {
+ // Stores
+ const app = stores.useApp() as AppStore
+
+ // Fields
+ const loading = ref(false)
+ const assays = ref({} as Assays)
+
+ // Methods
+ /**
+ * Reload all assays.
+ */
+ function reload() {
+ loading.value = true
+ API.getAssays()
+ .then(response => assays.value = response.data)
+ .catch(error => app.handleServerError("fetch assays", error))
+ .finally(() => loading.value = false)
+ }
+
+ // Exports
+ return {
+ // Fields
+ assays, loading,
+ // Methods
+ reload,
+ }
+})
+
+export default useAssays
\ No newline at end of file
diff --git a/client/src/stores/corpora.ts b/client/src/stores/corpora.ts
new file mode 100644
index 0000000..a58ebc2
--- /dev/null
+++ b/client/src/stores/corpora.ts
@@ -0,0 +1,107 @@
+// Libraries & stores
+import { computed, ref } from 'vue'
+import { defineStore } from 'pinia'
+import stores, { AppStore, UserStore } from '@/stores'
+// Types & API
+import { CorpusMetadata, MutableCorpusMetadata, UUID } from '@/types/corpora'
+import * as API from '@/api/corpora'
+
+/**
+ * Contains all corpora for which the user has read access.
+ */
+const useCorpora = defineStore('corpora', () => {
+ // Stores
+ const userStore = stores.useUser() as UserStore
+ const app = stores.useApp() as AppStore
+
+ // Fields
+ const loading = ref(false)
+ const activeUUID = ref(null as unknown as UUID) // has to be null for to show its default value
+ const allCorpora = ref([] as CorpusMetadata[])
+ const datasetCorpora = computed((): CorpusMetadata[] => allCorpora.value.filter(i => i.dataset))
+ const publicCorpora = computed((): CorpusMetadata[] => allCorpora.value.filter(i => i.public))
+ const sharedCorpora = computed((): CorpusMetadata[] => allCorpora.value.filter(i => !i.public && i.owner != userStore.user.id))
+ const activeCorpus = computed((): CorpusMetadata | null => {
+ const candidates = allCorpora.value.filter(x => x.uuid == activeUUID.value)
+ return (candidates.length === 1 ? candidates[0] : null)
+ })
+ const hasDocs = computed((): boolean => {
+ return (activeCorpus.value?.numDocs ?? 0) > 0
+ })
+ const userIsCollaborator = computed((): boolean => {
+ return activeCorpus.value?.collaborators.includes(userStore.user.id) ?? false
+ })
+
+ // Methods
+ /**
+ * Only one corpus operation is allowed to run at a time
+ * Is a corpus operation running? If, not request to start a new operation
+ * It is the responsibility of the operation to call reload which will release loading.value after finishing
+ */
+ function corpusOperationLock(): boolean {
+ if (loading.value) { return true } else { loading.value = true; return false }
+ }
+
+ /**
+ * Fetch all corpora for which the user has read access.
+ */
+ function reload() {
+ loading.value = true // this will block any other operations
+ API.getCorpora()
+ .then(response => allCorpora.value = response.data || [])
+ .catch(error => { allCorpora.value = []; app.handleServerError("get corpora", error) })
+ .finally(() => loading.value = false)
+ }
+
+ /**
+ * Create a new corpus with the given metadata and set it as active.
+ * @param metadata Metadata of the new corpus.
+ */
+ function createCorpus(metadata: MutableCorpusMetadata) {
+ if (corpusOperationLock()) return
+ API.postCorpus(metadata)
+ // Automatically set the new corpus as active.
+ .then(response => activeUUID.value = response.data)
+ .catch(error => app.handleServerError("create corpus", error))
+ .finally(reload)
+ }
+
+ /**
+ * Delete and unselect corpus.
+ * @param metadata Corpus to delete.
+ */
+ function deleteCorpus(metadata: CorpusMetadata) {
+ if (corpusOperationLock()) return
+ API.deleteCorpus(metadata.uuid)
+ .then(() => {
+ // Deselect now deleted corpus
+ if (metadata.uuid === activeUUID.value) {
+ activeUUID.value = null as unknown as UUID
+ }
+ })
+ .catch(error => app.handleServerError("delete corpus", error))
+ .finally(reload)
+ }
+
+ /**
+ * Update metadata of existing corpus. Keeps it selected.
+ * @param uuid UUID of corpus to update.
+ * @param metadata Updated metadata.
+ */
+ function updateCorpus(uuid: UUID, metadata: MutableCorpusMetadata) {
+ if (corpusOperationLock()) return
+ API.patchCorpus(uuid, metadata)
+ .catch(error => app.handleServerError("update corpus", error))
+ .finally(reload)
+ }
+
+ // Exports
+ return {
+ // Fields
+ allCorpora, loading, datasetCorpora, publicCorpora, sharedCorpora, activeCorpus, hasDocs, activeUUID, userIsCollaborator,
+ // Methods
+ createCorpus, deleteCorpus, updateCorpus, reload,
+ }
+})
+
+export default useCorpora
diff --git a/client/src/stores/documents.ts b/client/src/stores/documents.ts
new file mode 100644
index 0000000..b67619d
--- /dev/null
+++ b/client/src/stores/documents.ts
@@ -0,0 +1,195 @@
+// Libraries & stores
+import { computed, ref, reactive } from 'vue'
+import { defineStore } from 'pinia'
+import stores, { AppStore, CorporaStore } from '@/stores'
+// Types & API
+import { DocumentMetadata, Format } from '@/types/documents'
+import { UUID } from '@/types/corpora'
+import * as API from '@/api/documents'
+import * as Utils from '@/api/utils'
+
+const MAX_FILE_SIZE = 10485760 // 10 MB
+
+// Custom types
+type FileStatus = {
+ status: 'busy' | 'success' | 'error',
+ message?: string
+}
+
+/**
+ * Contains the documents for the corporaStore.activeUUID
+ * as well as functionality related to the user's documents, like uploading.
+ */
+const documents = defineStore('documents', () => {
+ // Stores
+ const app = stores.useApp() as AppStore
+ const corporaStore = stores.useCorpora() as CorporaStore
+
+ // Fields
+ const loading = ref(false)
+ const available = ref([] as DocumentMetadata[])
+ const numSourceAnnotations = computed(() => available.value.filter(i => i.layerSummary?.numTerms > 0).length)
+ const totalSizeInChars = computed(() => available.value.reduce((x, y) => x + y.numChars, 0))
+ const uploading: Record = reactive({})
+ const uploadBusyCount = computed(() => Object.values(uploading).filter(i => i.status === "busy").length)
+ const uploadErrorCount = computed(() => Object.values(uploading).filter(i => i.status === "error").length)
+ const filesToUpload = ref([] as File[])
+ const illegalFiles = computed((): File[] => {
+ return filesToUpload.value.filter((x: any) => {
+ const ext = x.name.split('.').at(-1)
+ return !(['xml', 'tsv', 'txt', 'zip', 'conllu', 'naf'].includes(ext))
+ })
+ })
+ const tooLargeFiles = computed((): File[] => {
+ return filesToUpload.value.filter((x: any) => x.size > MAX_FILE_SIZE)
+ })
+
+ // Methods
+ /**
+ * Reloads documentsStore.available for a given corpus.
+ * @param corpus
+ */
+ function reloadDocumentsForCorpus(corpus: UUID) {
+ if (!corpus) return
+ // Reset
+ loading.value = true
+ // Fetch
+ API.getDocuments(corpus)
+ .then(response => {
+ // Only update if the response is for the active corpus.
+ if (response.request.responseURL.includes(corpus)) {
+ available.value = response.data
+ }
+ })
+ .catch(error => { app.handleServerError("fetch documents", error) })
+ .finally(() => loading.value = false)
+ }
+
+ /**
+ * Reload documents for the active user corpus and updates the corpora list.
+ * The latter is to update the document count which unlocks, e.g., the job tab.
+ */
+ function reloadForActiveUserCorpus() {
+ reloadDocumentsForCorpus(corporaStore.activeUUID)
+ corporaStore.reload()
+ }
+
+ /**
+ * Delete a document.
+ * @param documentName Document name.
+ */
+ function deleteDocument(documentName: string) {
+ API.deleteDocument(corporaStore.activeUUID, documentName)
+ .catch(error => app.handleServerError("delete document", error))
+ .finally(reloadForActiveUserCorpus)
+ }
+
+ /**
+ * Download original source document.
+ * @param documentName Document name.
+ */
+ function downloadRaw(documentName: string) {
+ API.getRawDocument(corporaStore.activeUUID, documentName)
+ .then(Utils.browserDownloadResponseFile)
+ .catch(res => Utils.handleBlobError(res, "download raw document", app))
+ }
+
+ /**
+ * Upload all files in filesToUpload.
+ * Creates timeouts to spread load.
+ */
+ function uploadAll() {
+ for (let i = 0; i < filesToUpload.value.length; i++) {
+ const formData = new FormData();
+ const file = filesToUpload.value[i];
+ // if( file.size > MAX_FILE_SIZE ) continue // skip too large files
+ formData.append('file', file)
+ uploading[file.name] = { status: "busy" }
+ // Spread the uploads a little
+ setTimeout(() => upload(formData), i / 10 * 100)
+ }
+ filesToUpload.value = []
+ }
+
+ /**
+ * Clear errors from not yet uploaded files.
+ */
+ function clearUploadErrors() {
+ Object.keys(uploading).forEach(key => {
+ if (uploading[key].status === "error")
+ delete uploading[key]
+ })
+ }
+
+ /**
+ * Add content type header.
+ * @param fd FormData with file to upload.
+ * @param contentType Content type header.
+ * @param exts File extensions to apply the content type header to.
+ */
+ function addContentTypeHeader(fd: FormData): Record | null {
+ let exts_and_headers = {
+ "tsv": "text/tab-separated-values",
+ "conllu": "text/tab-separated-values",
+ "naf": "text/xml",
+ }
+
+ let file = fd.get("file") as File
+ const extension = file?.name.split('.').at(-1)
+ let header = null
+
+ if (Object.keys(exts_and_headers).includes(extension)) {
+ const contentType = exts_and_headers[extension]
+ file = new File([file], file.name, { type: contentType })
+ header = { 'Content-Type': contentType }
+ fd.set("file", file)
+ }
+ return header
+ }
+
+ /**
+ * Upload a single file. Takes http content type header into account.
+ * @param formData FormData with file to upload.
+ */
+ function upload(formData: FormData) {
+ let file = formData.get("file") as File
+
+ // Some files need an explicit content type header.
+ let header = addContentTypeHeader(formData)
+
+ // Update status on upload, on success and on error.
+ uploading[(file?.name)] = { status: 'busy' }
+ API.postDocument(corporaStore.activeUUID, formData, header)
+ .then(() => {
+ uploading[file?.name] = { status: 'success' }
+ if (uploadBusyCount.value == 0)
+ reloadForActiveUserCorpus()
+ })
+ .catch(error => uploading[file.name] = { status: 'error', message: error.response.data.message })
+ }
+
+ /**
+ * Checks if the documentsStore.available contains at least one file of the given format.
+ */
+ function containsFormat(format: Format): boolean {
+ return available.value.some(i => {
+ // Overwrite the format for legacy formats.
+ let otherFormat = i.format
+ if (otherFormat == Format.Tei_p5_legacy) {
+ otherFormat = Format.Tei_p5
+ }
+ return otherFormat == format
+ })
+ }
+
+ // Exports
+ return {
+ // Fields
+ available, filesToUpload, illegalFiles, loading, tooLargeFiles, uploading,
+ uploadBusyCount, uploadErrorCount, totalSizeInChars, numSourceAnnotations,
+ // Methods
+ reloadDocumentsForCorpus, deleteDocument, downloadRaw, uploadAll, clearUploadErrors, containsFormat
+ }
+})
+
+export default documents
diff --git a/client/src/stores/evaluation.ts b/client/src/stores/evaluation.ts
new file mode 100644
index 0000000..c195090
--- /dev/null
+++ b/client/src/stores/evaluation.ts
@@ -0,0 +1,54 @@
+// Libraries & stores
+import { ref } from "vue"
+import { defineStore } from "pinia"
+import stores, { AppStore, CorporaStore, JobSelectionStore } from '@/stores'
+// API & types
+import * as API from "@/api/evaluation"
+import * as Utils from "@/api/utils"
+import { Term, TermComparison } from "@/types/evaluation"
+
+// For some reason the terms are undefined sometimes
+// We handle it here
+export function literalsForTerm(term: Term): string {
+ return term.targets.map(x => x.literal).join("..")
+}
+
+export function literalsForTermComparison(termComparison: TermComparison): string {
+ // the literals could be different for term1 and term2
+ if (literalsForTerm(termComparison.hypoTerm) == literalsForTerm(termComparison.refTerm)
+ || literalsForTerm(termComparison.refTerm) == ""
+ ) {
+ return literalsForTerm(termComparison.hypoTerm)
+ } else if (literalsForTerm(termComparison.hypoTerm) == "") {
+ return literalsForTerm(termComparison.refTerm)
+ } else {
+ return `MISMATCH: [${literalsForTerm(termComparison.hypoTerm)} — ${literalsForTerm(termComparison.refTerm)}]`
+ }
+}
+
+/**
+ * Used to download the evaluation CSV zip.
+ */
+const useEvaluation = defineStore('evaluation', () => {
+ // Stores
+ const app = stores.useApp() as AppStore
+ const corporaStore = stores.useCorpora() as CorporaStore
+ const jobSelection = stores.useJobSelection() as JobSelectionStore
+
+ // Fields
+ const loading = ref(false)
+
+ // Methods
+ function downloadCSV() {
+ loading.value = true
+ API.getDownloadEvaluation(corporaStore.activeUUID, jobSelection.hypothesisJobId, jobSelection.referenceJobId)
+ .then(Utils.browserDownloadResponseFile)
+ .catch(error => Utils.handleBlobError(error, "download evaluation", app))
+ .finally(() => loading.value = false)
+ }
+
+ // Exports
+ return { downloadCSV, loading }
+})
+
+export default useEvaluation
diff --git a/client/src/stores/evaluation/confusion.ts b/client/src/stores/evaluation/confusion.ts
new file mode 100644
index 0000000..41ed7e1
--- /dev/null
+++ b/client/src/stores/evaluation/confusion.ts
@@ -0,0 +1,118 @@
+// Libraries & stores
+import { ref } from 'vue'
+import { defineStore } from 'pinia'
+import stores from '@/stores'
+// API & types
+import { UUID } from '@/types/corpora'
+import { ConfusionWrapper } from '@/types/evaluation'
+import * as API from '@/api/evaluation'
+import * as Utils from '@/stores/evaluation/utils'
+
+// Allows for Object.keys(confusion.table), which dislikes null.
+const defaultConfusion = () => ({ table: {} } as ConfusionWrapper)
+
+export const MISC = "OTHER"
+const MISC_REGEX = /^[^A-Z]/
+
+/**
+ * Stores and fetches the confusion matrix.
+ */
+const confusion = defineStore('confusion', () => {
+ // Fields
+ const loading = ref(false)
+ const confusion = ref(defaultConfusion())
+
+ // Methods
+ /**
+ * Reset it when e.g. the hypothesis or reference is changed.
+ */
+ function reset() {
+ confusion.value = defaultConfusion()
+ }
+
+ /**
+ * Reloads the confusion matrix for the given corpus, hypothesis and reference.
+ * @param corpus The UUID of the corpus.
+ * @param hypothesis Tagger job name as hypothesis layer.
+ * @param reference Tagger job name as reference layer.
+ */
+ function reloadForUUIDHypothesisReference(corpus: UUID, hypothesis: string, reference: string) {
+ Utils.reloadEval(
+ API.getConfusion,
+ reset,
+ "fetch confusion",
+ loading,
+ confusion,
+ stores,
+ corpus,
+ hypothesis,
+ reference
+ ).then(() => {
+ if (Object.keys(confusion?.value?.table).length == 0) {
+ return
+ }
+ combineMisc()
+ })
+ }
+
+ /**
+ * Combine any pos that start with [^A-Z] into a misc category.
+ */
+ function combineMisc() {
+ // The confusion matrix has the structure:
+ // { AA: { AA: {...}, ADP: {...} } }
+ // So we need to merge on two levels, starting with the inner level
+
+ // Inner level
+ for (const pos in confusion.value.table) {
+ for (const pos2 in confusion.value.table[pos]) {
+ if (MISC_REGEX.test(pos2)) {
+ // Create MISC if not yet present: e.g. ADP: { MISC: {...} }
+ if (!confusion.value.table[pos][MISC]) {
+ confusion.value.table[pos][MISC] = { "count": 0, "samples": [] }
+ }
+ // Merge
+ const pos_to_pos = confusion.value.table[pos][pos2]
+ confusion.value.table[pos][MISC].count += pos_to_pos.count
+ confusion.value.table[pos][MISC].samples.push(...pos_to_pos.samples)
+ delete confusion.value.table[pos][pos2]
+ }
+ }
+ }
+
+
+ // Outer level
+ const misc: Record = { }
+
+ for (const pos in confusion.value.table) {
+ if (MISC_REGEX.test(pos)) {
+ for (const pos2 in confusion.value.table[pos]) {
+ // create misc[pos2] if not yet present
+ if (!misc[pos2]) {
+ misc[pos2] = { "count": 0, "samples": [] }
+ }
+ // Merge
+ const pos_to_pos = confusion.value.table[pos][pos2]
+ misc[pos2].count += pos_to_pos.count
+ misc[pos2].samples.push(...pos_to_pos.samples)
+ }
+ delete confusion.value.table[pos]
+ }
+ }
+
+ // Commit the matches if there were any
+ if (Object.keys(misc).length > 0) {
+ confusion.value.table[MISC] = misc
+ }
+ }
+
+ // Exports
+ return {
+ // Fields
+ confusion, loading,
+ // Methods
+ reloadForUUIDHypothesisReference, reset,
+ }
+})
+
+export default confusion
\ No newline at end of file
diff --git a/client/src/stores/evaluation/distribution.ts b/client/src/stores/evaluation/distribution.ts
new file mode 100644
index 0000000..813ebd9
--- /dev/null
+++ b/client/src/stores/evaluation/distribution.ts
@@ -0,0 +1,64 @@
+// Libraries & stores
+import { computed, ref } from 'vue'
+import { defineStore } from 'pinia'
+import stores from '@/stores'
+// API & types
+import { UUID } from '@/types/corpora'
+import { DistributionWrapper } from '@/types/evaluation'
+import * as API from '@/api/evaluation'
+import * as Utils from '@/stores/evaluation/utils'
+
+// Allows for Object.keys(distribution.table), which dislikes null.
+const defaultDistribution = () => ({ distribution: {} } as DistributionWrapper)
+
+/**
+ * Stores and fetches the term frequency distribution.
+ */
+const useDistribution = defineStore('distribution', () => {
+ // Fields
+ const distribution = ref(defaultDistribution())
+ const loading = ref(false)
+ const posses = computed(() => {
+ // A bit hacky using Object.entries, but .map throws on undefined.
+ return Object.entries(distribution.value?.distribution)?.map(x => x[1].pos)
+ .filter((val, ind, arr) => arr.indexOf(val) === ind) // unique values
+ .sort()
+ })
+
+ // Methods
+ /**
+ * Reset it when e.g. the hypothesis or reference is changed.
+ */
+ function reset() {
+ distribution.value = defaultDistribution()
+ }
+
+ /**
+ * Reloads the term frequency distribution for the given corpus, hypothesis and reference.
+ * @param corpus The UUID of the corpus.
+ * @param hypothesis The hypothesis job ID.
+ */
+ function reloadForUUIDHypothesis(corpus: UUID, hypothesis: string) {
+ Utils.reloadEval(
+ API.getDistribution,
+ reset,
+ "fetch distribution",
+ loading,
+ distribution,
+ stores,
+ corpus,
+ hypothesis,
+ ""
+ )
+ }
+
+ // Exports
+ return {
+ // Fields
+ loading, distribution, posses,
+ // Methods
+ reloadForUUIDHypothesis, reset,
+ }
+})
+
+export default useDistribution
\ No newline at end of file
diff --git a/client/src/stores/evaluation/metrics.ts b/client/src/stores/evaluation/metrics.ts
new file mode 100644
index 0000000..99af01d
--- /dev/null
+++ b/client/src/stores/evaluation/metrics.ts
@@ -0,0 +1,69 @@
+// Libraries & stores
+import { ref } from 'vue'
+import { defineStore } from 'pinia'
+import stores from '@/stores'
+// API & types
+import { UUID } from '@/types/corpora'
+import { Metrics, MetricsRow } from '@/types/evaluation'
+import * as API from '@/api/evaluation'
+import * as Utils from '@/stores/evaluation/utils'
+
+export const metricsPerPosColumns = [
+ { key: 'name', label: 'group', sortOn: (x: MetricsRow) => x.name },
+ // { key: 'accuracy', sortOn: (x: MetricsRow) => x.accuracy },
+ { key: 'precision', sortOn: (x: MetricsRow) => x.precision },
+ { key: 'recall', sortOn: (x: MetricsRow) => x.recall },
+ { key: 'f1', sortOn: (x: MetricsRow) => x.f1 },
+ { key: 'count', label: 'count', sortOn: (x: MetricsRow) => x.count },
+ { key: 'truePositive', label: 'true positive', sortOn: (x: MetricsRow) => x.truePositive.count / x.count },
+ { key: 'falsePositive', label: 'false positive', sortOn: (x: MetricsRow) => x.falsePositive.count / x.count },
+ { key: 'falseNegative', label: 'false negative', sortOn: (x: MetricsRow) => x.falseNegative.count / x.count },
+ { key: 'noMatch', label: "no match", sortOn: (x: MetricsRow) => x.noMatch.count / x.count },
+]
+
+/**
+ * Stores and fetches the Lemma & PoS accuracy metrics.
+ */
+const useMetrics = defineStore('metrics', () => {
+ // Fields
+ const loading = ref(false)
+ const metrics = ref({} as Metrics)
+
+ // Methods
+ /**
+ * Reset it when e.g. the hypothesis or reference is changed.
+ */
+ function reset() {
+ metrics.value = {} as Metrics
+ }
+
+ /**
+ * Reloads the metrics for the given corpus, hypothesis and reference.
+ * @param corpus The UUID of the corpus.
+ * @param hypothesis Tagger job name as hypothesis layer.
+ * @param reference Tagger job name as reference layer.
+ */
+ function reloadForUUIDHypothesisReference(corpus: UUID, hypothesis: string, reference: string) {
+ Utils.reloadEval(
+ API.getMetrics,
+ reset,
+ "fetch metrics",
+ loading,
+ metrics,
+ stores,
+ corpus,
+ hypothesis,
+ reference
+ )
+ }
+
+ // Exports
+ return {
+ // Fields
+ loading, metrics,
+ // Methods
+ reloadForUUIDHypothesisReference, reset,
+ }
+})
+
+export default useMetrics
\ No newline at end of file
diff --git a/client/src/stores/evaluation/utils.ts b/client/src/stores/evaluation/utils.ts
new file mode 100644
index 0000000..4af76d9
--- /dev/null
+++ b/client/src/stores/evaluation/utils.ts
@@ -0,0 +1,69 @@
+// Libraries & stores
+import { AxiosResponse } from "axios"
+import { Ref } from "vue"
+import { CorporaStore, JobSelectionStore } from "@/stores"
+// API & types
+import { UUID } from '@/types/corpora'
+
+/**
+ * Reloads the data for an evaluation store.
+ * @param ApiCall API to call for data.
+ * @param ResetCall Reset data call.
+ * @param intent Human readable intent, on error.
+ * @param loading Loading ref.
+ * @param data Data ref.
+ * @param stores Global stores object.
+ * @param corpus UUID of the corpus.
+ * @param hypothesis Tagger job name as hypothesis layer.
+ * @param reference Tagger job name as reference layer.
+ */
+export function reloadEval(
+ ApiCall: (corpus: UUID, hypothesis: string, reference: string) => Promise,
+ ResetCall: () => void,
+ intent: string,
+ loading: Ref,
+ data: Ref,
+ stores: any,
+ corpus: UUID,
+ hypothesis: string,
+ reference?: string,
+): any {
+ // Specifically check reference for null. We'll allow empty strings.
+ if (!corpus || !hypothesis || reference == null) {
+ ResetCall()
+ return
+ }
+ // Is already fetching
+ if (loading.value) return
+ // Else, start fetching
+ loading.value = true
+
+ return new Promise((resolve, reject) => {
+ ApiCall(corpus, hypothesis, reference)
+ .then(response => {
+ const corporaStore = stores.useCorpora() as CorporaStore
+ const jobSelection = stores.useJobSelection() as JobSelectionStore
+ // Retrieve latest selections
+ const currentCorpus = corporaStore.activeUUID
+ const currentHypothesis = jobSelection.hypothesisJobId
+ const currentReference = jobSelection.referenceJobId
+ // Only commit the response if it corresponds to the current corpus and layers
+ // This prevents late responses overwriting responses to newer requests
+ const url: string = response.request.responseURL
+ // Distribution does not need a reference and instead passes an empty string.
+ const includesRef = reference == "" ? true : url.includes(currentReference)
+
+ if (url.includes(currentCorpus) && url.includes(currentHypothesis)
+ && includesRef) {
+ // commit
+ data.value = response.data
+ resolve()
+ }
+ })
+ .catch(error => {
+ stores.useApp().handleServerError(intent, error)
+ reject()
+ })
+ .finally(() => loading.value = false)
+ })
+}
\ No newline at end of file
diff --git a/client/src/stores/exportstore.ts b/client/src/stores/exportstore.ts
new file mode 100644
index 0000000..540ea8c
--- /dev/null
+++ b/client/src/stores/exportstore.ts
@@ -0,0 +1,56 @@
+// Libraries & stores
+import { computed, ref } from 'vue'
+import { defineStore } from 'pinia'
+import stores, { AppStore, CorporaStore, JobSelectionStore } from '@/stores'
+// Types & API
+import * as API from '@/api/export'
+import { Format } from '@/types/documents'
+import * as Utils from '@/api/utils'
+
+/**
+ * Used to download exported corpora.
+ */
+const useExportStore = defineStore('exportStore', () => {
+ // Stores
+ const corporaStore = stores.useCorpora() as CorporaStore
+ const app = stores.useApp() as AppStore
+ const jobSelection = stores.useJobSelection() as JobSelectionStore
+
+ // Fields
+ const loading = ref(false)
+ const format = ref(null as any as Format) // can we use this both as the export format as the 'import-to-blacklab' format?
+ const linksAreValid = computed(() => {
+ return corporaStore.activeUUID !== null && jobSelection.hypothesisJobId !== null && format.value !== null
+ })
+
+ // Methods
+ function convert(shouldMerge: boolean, posHeadOnly: boolean) {
+ if (shouldMerge) {
+ merge(posHeadOnly)
+ return
+ }
+ loading.value = true
+ API.convertCorpus(corporaStore.activeUUID, jobSelection.hypothesisJobId, format.value, posHeadOnly)
+ .then(Utils.browserDownloadResponseFile)
+ .catch(res => Utils.handleBlobError(res, "convert corpus", app))
+ .finally(() => loading.value = false)
+ }
+
+ function merge(posHeadOnly: boolean) {
+ loading.value = true
+ API.mergeCorpus(corporaStore.activeUUID, jobSelection.hypothesisJobId, format.value, posHeadOnly)
+ .then(Utils.browserDownloadResponseFile)
+ .catch(res => Utils.handleBlobError(res, "merge corpus", app))
+ .finally(() => loading.value = false)
+ }
+ // Exports
+ return {
+ // Fields
+ // note: exporting the format seems necessary for reactivity
+ format, linksAreValid, loading,
+ // Methods
+ convert,
+ }
+})
+
+export default useExportStore
diff --git a/client/src/stores/index.ts b/client/src/stores/index.ts
new file mode 100644
index 0000000..7dba23f
--- /dev/null
+++ b/client/src/stores/index.ts
@@ -0,0 +1,53 @@
+import useApp from './app'
+import useUser from './user'
+
+import useCorpora from './corpora'
+
+import useDocuments from './documents'
+
+import useJobs from './jobs'
+import useJobSelection from './jobselection'
+
+import useTaggers from './taggers'
+import useTagsets from './tagsets'
+import useAssays from './assays'
+
+import useEvaluation from './evaluation'
+import useDistribution from './evaluation/distribution'
+import useMetrics from './evaluation/metrics'
+import useConfusion from './evaluation/confusion'
+
+import useExportStore from './exportstore'
+
+// Custom types
+export type AppStore = ReturnType
+export type UserStore = ReturnType
+export type CorporaStore = ReturnType
+export type DocumentsStore = ReturnType
+export type JobsStore = ReturnType
+export type JobSelectionStore = ReturnType
+export type TaggersStore = ReturnType
+export type TagsetsStore = ReturnType
+export type AssaysStore = ReturnType
+export type EvaluationStore = ReturnType
+export type DistributionStore = ReturnType
+export type MetricsStore = ReturnType
+export type ConfusionStore = ReturnType
+export type ExportStore = ReturnType
+
+export default {
+ useApp,
+ useAssays,
+ useConfusion,
+ useCorpora,
+ useDistribution,
+ useDocuments,
+ useExportStore,
+ useMetrics,
+ useJobs,
+ useJobSelection,
+ useUser,
+ useEvaluation,
+ useTaggers,
+ useTagsets,
+}
\ No newline at end of file
diff --git a/client/src/stores/jobs.ts b/client/src/stores/jobs.ts
new file mode 100644
index 0000000..ddd5f77
--- /dev/null
+++ b/client/src/stores/jobs.ts
@@ -0,0 +1,173 @@
+// Libraries & stores
+import { computed, ref } from 'vue'
+import { defineStore } from 'pinia'
+import stores, { AppStore, CorporaStore } from '@/stores'
+// API & Types
+import { Job, SOURCE_LAYER } from '@/types/jobs'
+import * as API from '@/api/jobs'
+import { getDocsAtTaggers } from '@/api/taggers'
+import { ProgressResponse } from '@/api/jobs'
+
+const POLL_INTERVAL = 5000
+
+/**
+ * Starts, stops and deletes jobs. Polls for job progress. Fetches available jobs.
+ */
+const jobs = defineStore('jobs', () => {
+ // Stores
+ const app = stores.useApp() as AppStore
+ const corporaStore = stores.useCorpora() as CorporaStore
+
+ // Fields
+ // Job statuses for the taggers.
+ const jobs = ref({} as Record)
+ const taggableJobs = computed((): Job[] => {
+ return Object.keys(jobs.value).map(x => jobs.value[x]).filter(x => x.tagger.id !== SOURCE_LAYER)
+ })
+ const loading = ref(false)
+ const posting = ref(false)
+ const pollers = {} as { [tagger: string]: number }
+ const numActiveDocs = ref(null as number | null)
+
+ // Methods
+ /**
+ * Fetch the progress for the given job. To be used within a poller.
+ * @param job Tagger job name.
+ */
+ function getProgress(job: string) {
+ API.getJobProgress(corporaStore.activeUUID, job)
+ .then(response => setProgress(job, response))
+ .catch(error => app.handleServerError("fetch job progress", error))
+ }
+
+ /**
+ * On poll promise resolve, set the progress for the given job.
+ */
+ const setProgress = (job: string, response: ProgressResponse) => {
+ if (response.request.responseURL.includes(corporaStore.activeUUID)) {
+ // Only commit the response if it corresponds to the correct corpus
+ // This prevents late responses overwriting responses to newer requests
+ jobs.value[job].progress = response.data
+ // Stop polling if the job is done.
+ if (!response.data.busy) {
+ stopPolling(job)
+ // Displaying the layer preview requires a reload.
+ reload()
+ }
+ } else {
+ // fizzle
+ }
+ }
+
+ /**
+ * Start a continuous progress poller for the given job
+ * @param job Tagger job name.
+ */
+ function startPolling(job: string) {
+ if (!(job in pollers)) {
+ pollers[job] = setInterval((job: string) => { getProgress(job) }, POLL_INTERVAL, job)
+ }
+ }
+
+ /**
+ * Stop polling progress for the given job.
+ * @param job Tagger job name.
+ */
+ function stopPolling(job: string) {
+ clearInterval(pollers[job])
+ delete pollers[job]
+ }
+
+ /**
+ * Empty the list of tagger job statuses.
+ */
+ function reset() {
+ jobs.value = {}
+ }
+
+ /**
+ * Fetch the list of tagger job statuses for the current corpus.
+ */
+ function reload() {
+ if (!corporaStore.activeUUID) {
+ reset()
+ return
+ }
+ Object.keys(pollers).forEach(x => stopPolling(x))
+ loading.value = true
+ // Reload jobs
+ API.getJobs(corporaStore.activeUUID, true)
+ .then(response => {
+ jobs.value = {} // reset the jobs value
+ response.data.forEach(job => {
+ jobs.value[job.tagger.id] = job
+ if (job.progress.busy) {
+ // Restart polling any running job
+ startPolling(job.tagger.id)
+ }
+ })
+ })
+ .catch(error => { jobs.value = {}; app.handleServerError("fetch jobs", error) })
+ .finally(() => loading.value = false)
+ }
+
+ function tag(job: string) {
+ posting.value = true
+ API.postJob(corporaStore.activeUUID, job)
+ .then(response => {
+ posting.value = false
+ // Fake it, because at this point all files will still be 'pending'.
+ // isBusy however depends on 'processing', so at this point it will still be false.
+ // A future poll will probably set it to true.
+ response.data.busy = true
+ setProgress(job, response)
+ startPolling(job) // TODO: this is a problem, because if the state doesn't change, the polling isn't stopped.
+ getDocsAtTagger()
+ })
+ .catch(error => app.handleServerError("post job", error))
+ }
+
+ function cancel(job: string) {
+ posting.value = true
+ API.cancelOrDeleteJob(corporaStore.activeUUID, job, false)
+ .then(response => {
+ posting.value = false
+ setProgress(job, response)
+ getDocsAtTagger()
+ })
+ .catch(error => app.handleServerError("cancel job", error))
+ }
+
+ // 'delete' is a reserved keyword
+ function deleteJob(job: string) {
+ posting.value = true
+ API.cancelOrDeleteJob(corporaStore.activeUUID, job, true)
+ .then(response => {
+ posting.value = false
+ setProgress(job, response)
+ getDocsAtTagger()
+ })
+ .catch(error => app.handleServerError("delete job", error))
+ }
+
+ /**
+ * Get the number of documents processing at all taggers.
+ */
+ function getDocsAtTagger() {
+ numActiveDocs.value = null
+ getDocsAtTaggers()
+ .then((response) => {
+ numActiveDocs.value = response.data
+ }).catch((error) => app.handleServerError("get number of active jobs", error))
+ }
+
+ // Exports
+ return {
+ // Fields
+ jobs, taggableJobs, loading, posting, numActiveDocs,
+ // Methods
+ tag, cancel, deleteJob, reload, reset, getDocsAtTagger
+ }
+})
+
+export default jobs
\ No newline at end of file
diff --git a/client/src/stores/jobselection.ts b/client/src/stores/jobselection.ts
new file mode 100644
index 0000000..e970921
--- /dev/null
+++ b/client/src/stores/jobselection.ts
@@ -0,0 +1,102 @@
+// Libraries & stores
+import { ref, watch, computed } from 'vue'
+import { defineStore } from 'pinia'
+import stores, { CorporaStore, JobsStore, DocumentsStore } from '@/stores'
+import { Job, SOURCE_LAYER } from '@/types/jobs'
+
+/**
+ * Stores the current job selection from the . Used in Evaluation & Export.
+ */
+const useJobSelection = defineStore('jobSelection', () => {
+ // Stores
+ const jobsStore = stores.useJobs() as JobsStore
+ const corporaStore = stores.useCorpora() as CorporaStore
+ const documentsStore = stores.useDocuments() as DocumentsStore
+
+ // Fields
+ const hypothesisJobId = ref(null as unknown as string)
+ const referenceJobId = ref(null as unknown as string)
+ // Set to true once we know the jobs exist in selectableJobs.
+ // (which requires waiting on jobs & docs to load)
+ const selectionsValid = ref(false)
+ const tagsetsMismatch = computed(() => {
+ return hypothesisJob.value?.tagger.tagset !== referenceJob.value?.tagger.tagset
+ })
+ // Selectable jobs are jobs that have at least one finished document,
+ // or have source annotations (i.e. sourceLayer).
+ const selectableJobs = computed((): { key: string, value: string, text: string }[] => {
+ const jobs = jobsStore.jobs
+ if (!jobs) return []
+ return Object.keys(jobs)?.filter(job => jobs[job].progress.finished > 0)
+ // Filter out sourceLayer if no documents have source annotations.
+ .filter((job: string) => !(job == SOURCE_LAYER && !documentsStore.numSourceAnnotations))
+ ?.map(job => {
+ return {
+ key: job,
+ value: job,
+ text: formatJobString(jobs[job]),
+ }
+ })
+ })
+
+ // Private Fields
+ const referenceJob = computed((): Job | null => {
+ return referenceJobId.value ? jobsStore.jobs[referenceJobId.value] : null
+ })
+ const hypothesisJob = computed((): Job | null => {
+ return hypothesisJobId.value ? jobsStore.jobs[hypothesisJobId.value] : null
+ })
+
+ // Watches
+ watch(() => corporaStore.activeUUID, (newValue, oldValue) => {
+ if (oldValue !== newValue && oldValue) {
+ hypothesisJobId.value = null as any
+ referenceJobId.value = null as any
+ }
+ })
+ /** Remove invalid job selections on loading jobs & loading docs (the latter for sourceLayer annotations).*/
+ watch([() => jobsStore.loading, () => documentsStore.loading], () => {
+ validateJobSelections()
+ })
+
+ // Methods
+ /** Remove any invalid job selections, either non-existing names or jobs that have no layer */
+ function validateJobSelections() {
+ const jobsExist: boolean = !jobsStore.loading && Object.keys(jobsStore.jobs).length > 0
+ const docsExist: boolean = !documentsStore.loading && documentsStore.available.length > 0
+ if (jobsExist && docsExist) {
+ if (!selectableJobs.value.map(job => job.key).includes(hypothesisJobId.value)) {
+ hypothesisJobId.value = null as any
+ }
+ if (!selectableJobs.value.map(job => job.key).includes(referenceJobId.value)) {
+ referenceJobId.value = null as any
+ }
+ selectionsValid.value = true
+ }
+ }
+ /** Format as displayed in the */
+ function formatJobString(job: Job) {
+ let finished = job.progress.finished
+ if (job.tagger.id == SOURCE_LAYER) {
+ finished = documentsStore.numSourceAnnotations
+ }
+ return `${job.tagger.id} (${job.tagger.description}) [${finished}/${job.progress.total} documents]`
+ }
+ function setHypothesisJobId(id: string) {
+ if (Object.keys(jobsStore.jobs)?.includes(id)) hypothesisJobId.value = id
+ }
+ function setReferenceJobId(id: string) {
+ if (Object.keys(jobsStore.jobs)?.includes(id)) referenceJobId.value = id
+ }
+
+ // Exports
+ return {
+ // Fields
+ tagsetsMismatch, hypothesisJobId, referenceJobId, selectableJobs, selectionsValid,
+ // Methods
+ setHypothesisJobId, setReferenceJobId,
+ }
+})
+
+export default useJobSelection
+
diff --git a/client/src/stores/taggers.ts b/client/src/stores/taggers.ts
new file mode 100644
index 0000000..cf327ae
--- /dev/null
+++ b/client/src/stores/taggers.ts
@@ -0,0 +1,47 @@
+// Libraries & stores
+import { ref } from 'vue'
+import { defineStore } from 'pinia'
+import stores, { AppStore } from '@/stores'
+// API & types
+import { Tagger } from '@/types/taggers'
+import * as API from '@/api/taggers'
+
+/**
+ * Sort the 'produces' field of the taggers. The order is stochastic when retrieved from the API.
+ * For the interface, we want the order to be fixed.
+ */
+export function sort_tagger_produces(types: string[]): string[] {
+ // By pure coincidence, reverse sorting makes the order TOK, POS, LEM, which is acceptable.
+ // But we might want a different order at some point.
+ return types.sort((a, b) => b.localeCompare(a))
+}
+
+/**
+ * Stores all available taggers. Mainly informational.
+ */
+const useTaggers = defineStore('taggers', () => {
+ // Stores
+ const app = stores.useApp() as AppStore
+
+ // Fields
+ const loading = ref(false)
+ const taggers = ref([] as Tagger[])
+
+ // Methods
+ function reload() {
+ loading.value = true
+ API.getTaggers()
+ .then(response => taggers.value = response.data)
+ .catch(error => { app.handleServerError("fetch taggers", error) })
+ .finally(() => loading.value = false)
+ }
+
+ reload() // load once
+
+ // Exports
+ return {
+ loading, taggers
+ }
+})
+
+export default useTaggers
diff --git a/client/src/stores/tagsets.ts b/client/src/stores/tagsets.ts
new file mode 100644
index 0000000..845f145
--- /dev/null
+++ b/client/src/stores/tagsets.ts
@@ -0,0 +1,37 @@
+// Libraries & stores
+import { ref } from 'vue'
+import { defineStore } from 'pinia'
+import stores, { AppStore } from '@/stores'
+// Types & API
+import { Tagset } from '@/types/tagset'
+import * as API from '@/api/tagset'
+
+/**
+ * Stores all available tagsets. Mainly informational.
+ */
+const useTagsets = defineStore('tagsets', () => {
+ // Stores
+ const app = stores.useApp() as AppStore
+
+ // Fields
+ const loading = ref(false)
+ const tagsets = ref([] as Tagset[])
+
+ // Methods
+ function reload() {
+ loading.value = true
+ API.getTagsets()
+ .then(response => tagsets.value = response.data)
+ .catch(error => app.handleServerError("fetch tagsets", error))
+ .finally(() => loading.value = false)
+ }
+
+ reload() // load once
+
+ // Exports
+ return {
+ loading, tagsets
+ }
+})
+
+export default useTagsets
\ No newline at end of file
diff --git a/client/src/stores/user.ts b/client/src/stores/user.ts
new file mode 100644
index 0000000..2eaf468
--- /dev/null
+++ b/client/src/stores/user.ts
@@ -0,0 +1,57 @@
+// Libraries & stores
+import { ref, computed } from 'vue'
+import { defineStore } from 'pinia'
+import stores, { CorporaStore } from '@/stores'
+// API & Types
+import { CorpusMetadata } from '@/types/corpora'
+import { User } from '@/types/user'
+import * as API from '@/api/application'
+
+/**
+ * User store & permissions checks.
+ */
+const useUser = defineStore('user', () => {
+ // Stores
+ const corporaStore = stores.useCorpora() as CorporaStore
+
+ // Fields
+ const user = ref({ id: 'NO USER', admin: false } as User)
+ const hasWriteAccess = computed((): boolean => {
+ return corporaStore.userIsCollaborator || user.value.admin || corporaStore.activeCorpus?.owner === user.value.id
+ })
+ const hasDeleteAccess = computed((): boolean => {
+ return canDelete(corporaStore.activeCorpus)
+ })
+
+ // Methods
+ /**
+ * Whether the user can delete the corpus. (Has to be either owner or admin)
+ * @param corpus Corpus metadata.
+ */
+ function canDelete(corpus: CorpusMetadata | null): boolean {
+ if (!corpus) return false
+ return corpus.owner === user.value.id || user.value.admin
+ }
+
+ /**
+ * Poll for the user account. On error, refresh page.
+ */
+ function fetchUser() {
+ API.getUser().then(response => {
+ user.value = response.data
+ }).catch(_ => {
+ // On error we wait 5 seconds then reload the page to force a new login
+ setTimeout(() => { console.log('Reloading page'); window.location.reload() }, 5000)
+ })
+ }
+
+ // Exports
+ return {
+ // Fields
+ user, hasWriteAccess, hasDeleteAccess,
+ // Methods
+ fetchUser, canDelete,
+ }
+})
+
+export default useUser
diff --git a/client/src/types/assays.ts b/client/src/types/assays.ts
new file mode 100644
index 0000000..de64eab
--- /dev/null
+++ b/client/src/types/assays.ts
@@ -0,0 +1,27 @@
+export type ClassificationMetrics = {
+ accuracy: number
+ precision: number
+ recall: number
+ f1: number
+}
+
+export type TaggerAssay = {
+ micro: ClassificationMetrics
+ macro: ClassificationMetrics
+}
+
+export type MetricTypeAssay = {
+ [taggerName: string]: TaggerAssay
+}
+
+export type DatasetAssay = {
+ [metricName: string]: MetricTypeAssay
+}
+
+export type Assays = {
+ [datasetName: string]: DatasetAssay
+}
+
+export type IndividualAssay = {
+ [metricName: string]: TaggerAssay
+}
\ No newline at end of file
diff --git a/client/src/types/corpora.ts b/client/src/types/corpora.ts
new file mode 100644
index 0000000..d95ca90
--- /dev/null
+++ b/client/src/types/corpora.ts
@@ -0,0 +1,25 @@
+export type UUID = string
+
+export type CorpusMetadata = MutableCorpusMetadata & {
+ "activeJobs": number
+ "dataset": boolean
+ "lastModified": number
+ "numDocs": number
+ "public": boolean
+ "sizeInBytes": number
+ "uuid": UUID
+}
+
+export type MutableCorpusMetadata = {
+ "name": string
+ "owner": string
+ "eraFrom": number
+ "eraTo": number
+ "tagset": string
+ "dataset": boolean
+ "public": boolean
+ "collaborators": string[]
+ "viewers": string[]
+ "sourceName": string
+ "sourceURL": string
+}
diff --git a/client/src/types/documents.ts b/client/src/types/documents.ts
new file mode 100644
index 0000000..8efde2c
--- /dev/null
+++ b/client/src/types/documents.ts
@@ -0,0 +1,29 @@
+export enum Format {
+ Tei_p4_legacy = "tei-p4-legacy",
+ Tei_p5_legacy = "tei-p5-legacy",
+ Tei_p5 = "tei-p5",
+ Naf = "naf",
+ Folia = "folia",
+ Tsv = "tsv",
+ Txt = "txt",
+ Conllu = "conllu",
+}
+
+export type LayerSummary = {
+ numLemma: number
+ numPOS: number
+ numTerms: number
+ numWordForms: number
+}
+
+export type DocumentMetadata = {
+ name: string
+ format: Format
+ valid: boolean
+ sizeInBytes: number
+ numChars: number
+ numAlphabeticChars: number
+ preview: string
+ layerSummary: LayerSummary
+ lastModified: number
+}
diff --git a/client/src/types/evaluation.ts b/client/src/types/evaluation.ts
new file mode 100644
index 0000000..b4732af
--- /dev/null
+++ b/client/src/types/evaluation.ts
@@ -0,0 +1,90 @@
+// Confusion
+export type ConfusionWrapper = {
+ matrix: {
+ [key: string]: {
+ count: number
+ samples: TermComparison[]
+ }
+ }
+ table: {
+ [key: string]: {}
+ }
+ generated: number
+ hypothesisLastModified: number
+ referenceLastModified: number
+}
+
+// Distribution
+export type DistributionWrapper = {
+ corpus_uuid: string
+ coveredAlphabeticChars: number
+ coveredChars: number
+ distribution: Distribution[]
+ generated: number
+ hypothesis: string
+ lastModified: number
+ totalAlphabeticChars: number
+ totalChars: number
+ trimmed: boolean
+}
+
+export type Distribution = {
+ lemma: string
+ pos: string
+ count: number
+ literals: { [literal: string]: number }
+}
+
+// Metrics
+export type Metrics = {
+ global: MetricsRow
+ perPOS: MetricsRow[]
+ generated: number
+ hypothesisLastModified: number
+ referenceLastModified: number
+}
+
+export type MetricsRow = {
+ name: string
+ count: number
+ bothAgree: MetricEntry
+ lemmaAgree: MetricEntry
+ lemmaDisagree: MetricEntry
+ posAgree: MetricEntry
+ posDisagree: MetricEntry
+ noMatch: MetricEntry
+}
+
+export type MetricEntry = {
+ count: number
+ samples: TermComparison[]
+}
+
+// Shared
+export type TermComparison = {
+ hypoTerm: Term
+ refTerm: Term
+ equalLemma: boolean
+ equalPOS: boolean
+ equalLabel: boolean
+ fullOverlap: boolean
+ partialOverlap: boolean
+}
+
+export type Term = {
+ lemma: string
+ pos: string
+ targets: WordForm[]
+}
+
+export type WordForm = {
+ literal: string
+ offset: number
+ length: number
+ id: null | string
+}
+
+export type EvaluationEntry = {
+ count: number
+ samples: TermComparison[]
+}
diff --git a/client/src/types/jobs.ts b/client/src/types/jobs.ts
new file mode 100644
index 0000000..bd30afd
--- /dev/null
+++ b/client/src/types/jobs.ts
@@ -0,0 +1,47 @@
+import { Tagset } from "@/types/tagset"
+import { Tagger } from "@/types/taggers"
+import { Term, WordForm } from "@/types/evaluation"
+
+export const SOURCE_LAYER: string = "sourceLayer"
+
+export type Job = {
+ tagger: Tagger,
+ progress: Progress,
+ preview: LayerPreview,
+ lastModified: number | null
+}
+
+export type JobDocumentResult = {
+ name: string,
+ tagset: Tagset,
+ preview: LayerPreview,
+ summary: LayerSummary,
+}
+
+export type Progress = {
+ pending: number,
+ processing: number,
+ failed: number,
+ finished: number,
+ total: number,
+ untagged: number,
+ busy: boolean,
+ hasError: boolean
+ errors: { [document: string]: string }
+}
+
+export type LayerPreview = {
+ wordforms: WordForm[]
+ terms: Term[]
+}
+
+export type LayerSummary = {
+ tagger: string
+ numDocuments: number
+ preview: LayerPreview
+ numWordForms: number
+ numTerms: number
+ numLemma: number
+ numPOS: number
+ lastModified: number
+}
diff --git a/client/src/types/table.ts b/client/src/types/table.ts
new file mode 100644
index 0000000..cbdf824
--- /dev/null
+++ b/client/src/types/table.ts
@@ -0,0 +1,27 @@
+export enum TableCorporaType {
+ Dataset = 'benchmark',
+ Public = 'benchmark',
+ User = 'user'
+}
+
+export enum TableDocumentsType {
+ Dataset = 'dataset',
+ Public = 'public',
+ User = 'user'
+}
+
+/* sortOn defines what to sort field values on */
+export type Field = {
+ key: string
+ label?: string
+ sortOn?: (value: any) => any
+ textAlign?: string
+ isPrimaryField?: boolean
+ hidden?: boolean
+}
+
+export type TableData = {
+ field: Field
+ item: T
+ value: any
+}
\ No newline at end of file
diff --git a/client/src/types/taggers.ts b/client/src/types/taggers.ts
new file mode 100644
index 0000000..6ee4a60
--- /dev/null
+++ b/client/src/types/taggers.ts
@@ -0,0 +1,29 @@
+export type Tagger = {
+ id: string
+ description: string
+ tagset: null | string
+ eraFrom: number
+ eraTo: number
+ produces: string[]
+ attributions: null | {
+ [key: string]: string
+ }
+ links: null | {
+ name: string
+ href: string
+ }[]
+}
+
+export enum TaggerStatus {
+ HEALTHY = 'HEALTHY',
+ ERROR = 'ERROR',
+ NOT_HEALTHY = 'NOT_HEALTHY',
+ UNKOWN = 'UNKOWN'
+}
+
+export type TaggerHealth = {
+ status: TaggerStatus
+ queueSizeAtTagger: number
+ processingSpeed: number
+ message: string
+}
diff --git a/client/src/types/tagset.ts b/client/src/types/tagset.ts
new file mode 100644
index 0000000..8f9e9c3
--- /dev/null
+++ b/client/src/types/tagset.ts
@@ -0,0 +1,5 @@
+export type Tagset = {
+ identifier: string
+ longName: string
+ punctuationTags: string[]
+}
diff --git a/client/src/types/user.ts b/client/src/types/user.ts
new file mode 100644
index 0000000..b37d802
--- /dev/null
+++ b/client/src/types/user.ts
@@ -0,0 +1,4 @@
+export type User = {
+ id: string
+ admin: boolean
+}
diff --git a/client/src/views/HomeView.vue b/client/src/views/HomeView.vue
new file mode 100644
index 0000000..307b644
--- /dev/null
+++ b/client/src/views/HomeView.vue
@@ -0,0 +1,107 @@
+
+
+
+
+ Generating Linguistic Annotations for Historical Dutch
+
+
+
+
+
+
+
Historical texts are essential source material for both linguistic and digital humanities research.
+ Adding linguistic annotation to historical text corpora helps to make the data more accessible.
+ Users need not be concerned with historical spelling variation, and can query or analyse the data
+ using higher-lever categories like part of speech and other grammatical properties.
+
This application serves two purposes. One is to make annotation and tool evaluation easily accessible
+ to researchers, the other to make it easy for developers to contribute their tools and models in the
+ platform, and thus compare them to other tools with gold standard material included in the platform.
+
+
GaLAHaD is designed to enable end users to choose the optimal path for their material. Apart from the
+ basic task of uploading and annotating corpus material, GaLAHaD provides options to inspect and
+ evaluate the result of the annotation process, in order to raise the awareness of typical errors and
+ biases in the tools. The functionality of comparing annotation layers enables users to assess the
+ accuracy of different tools on their data. It can be used both to evaluate a layer added by an
+ automatic tagger with respect to a gold standard reference layer, or to compare layers added by
+ different taggers. Disagreement between layers is not only represented by global statistics, but
+ also illustrated by examples which are immediately visible in the tool. The annotated material can
+ be automatically uploaded to the Autosearch corpus exploration environment and to the CoBaLT tool
+ for manual correction of linguistic annotation.
+
For tool developers, the docker-based application architecture ensures easy contribution of tools to
+ the platform. The application and taggers are hosted by the INT and accessible with any
+ CLARIN-account. There is also the option to self-host an instance using the publicly available
+ docker images from the
+ INT docker hub
+
+ or the open source code available on
+ GitHub .
+
+
+
+
+
+
+
+ “Ende seget datmen daer doe wan
+ Galaadde den goeden man,
+ Den besten riddre die wesen mochte,
+ Ende die ten inde brochte,
+ Nader historien tale,
+ Davonturen vanden grale”
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/views/PageNotFound.vue b/client/src/views/PageNotFound.vue
new file mode 100644
index 0000000..7806bce
--- /dev/null
+++ b/client/src/views/PageNotFound.vue
@@ -0,0 +1,18 @@
+
+
+
+ We could not find the page you were looking for.
+ Go back to the homepage
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/views/UserView.vue b/client/src/views/UserView.vue
new file mode 100644
index 0000000..4c48f31
--- /dev/null
+++ b/client/src/views/UserView.vue
@@ -0,0 +1,48 @@
+
+
+ Find information about your account here
+
+ Find information about your account here.
+
+ Username:
+ {{ user.user.id }}
+
+ You are an admin.
+
+
+
+
+
+
+
+
diff --git a/client/src/views/annotate/AnnotateView.vue b/client/src/views/annotate/AnnotateView.vue
new file mode 100644
index 0000000..da08965
--- /dev/null
+++ b/client/src/views/annotate/AnnotateView.vue
@@ -0,0 +1,31 @@
+
+
+
+
+
diff --git a/client/src/views/annotate/subviews/CorporaView.vue b/client/src/views/annotate/subviews/CorporaView.vue
new file mode 100644
index 0000000..3a48409
--- /dev/null
+++ b/client/src/views/annotate/subviews/CorporaView.vue
@@ -0,0 +1,100 @@
+
+
+
+ deleteCorpusData = corpus" @update="corpus => editMode(corpus)" selectable
+ @create="showNewCorpusModal = true">
+ Your corpora
+
+
+
+ deleteCorpusData = corpus" @update="corpus => editMode(corpus)" selectable
+ @create="showNewCorpusModal = true" sharedWithYou>
+ Shared with you
+
+ Here you can see the corpora that have been shared with you.
+ If a corpus has been shared with you as a
+ collaborator, you can make modifications.
+ If it has been shared with you as a viewer, you can only
+ inspect and evaluate it.
+
+
+
+
+ deleteCorpusData = corpus" @update="corpus => editMode(corpus)" selectable
+ @create="showNewCorpusModal = true">
+
+
+ You can inspect them in further detail on the
+ Evaluate tab .
+
+
+
+
+
+ Fill in the metadata and create a corpus.
+
+
+
+
+
+
+
+
+ Change the metadata of an existing corpus.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/views/annotate/subviews/DocumentsView.vue b/client/src/views/annotate/subviews/DocumentsView.vue
new file mode 100644
index 0000000..eba0acd
--- /dev/null
+++ b/client/src/views/annotate/subviews/DocumentsView.vue
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
diff --git a/client/src/views/annotate/subviews/ExportView.vue b/client/src/views/annotate/subviews/ExportView.vue
new file mode 100644
index 0000000..40479e7
--- /dev/null
+++ b/client/src/views/annotate/subviews/ExportView.vue
@@ -0,0 +1,113 @@
+
+
+
+
+
+
+
+
+ Download as format
+
+
+
+
+
+ Export part of speech without features
+ Merge
+
+
+
+
+
+ You have uploaded {{ formatToHumanReadable(exportStore.format) }} files to this
+ corpus
+ and
+ you are now exporting {{ formatToHumanReadable(exportStore.format) }} .
+ It is possible to insert the annotation layer into the original file. This will retain
+ the original encoding.
+
+
+ If you do not choose the merge option, your export will ignore the original encoding of
+ your uploaded document.
+
+
+
+
+ Special notice for TEI P5 legacy
+
+
+
+
+
+
+
+ Please wait while your export is being processed.
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/views/annotate/subviews/JobsView.vue b/client/src/views/annotate/subviews/JobsView.vue
new file mode 100644
index 0000000..cf9983c
--- /dev/null
+++ b/client/src/views/annotate/subviews/JobsView.vue
@@ -0,0 +1,320 @@
+
+
+
+
+
+
+
+
+ No results for current filter settings
+ No taggers showed up? Something went wrong! Please contact support.
+
+
+
+
+
+ {{ d.item.tagger.id }}
+
+
+ {{ d.item.tagger.id }}
+
+
+
+
+
+ Unknown
+ {{ d.item.tagger.tagset }}
+
+
+
+
+ {{ sort_tagger_produces(d.item.tagger.produces).join(", ") }}
+ None
+
+
+
+
+
+ {{ d.value.numWordForms }}
+
+
+
+
+ {{ d.item.tagger.eraFrom
+ }} {{ d.item.tagger.eraFrom }}
+ –
+ {{ d.item.tagger.eraTo }} {{
+ d.item.tagger.eraTo
+ }}
+
+
+
+
+
+ {{ unixToString(d.item.lastModified) }}
+
+
+
+
+
+
+ {{ d.item.progress.total === 0 ? '' : formatProgress(d.item.progress) }}
+ error !!
+
+
+
+
+
+
+
+ Upload documents to start job
+
+ View & Tag
+
+
+
+
+
+
+
+
+ Search tagger name:
+
+
+
+
+ Tagset, any of:
+
+ {{ tagset || 'Unknown' }}
+
+
+
+
+ Require type:
+
+ {{ type }}
+
+
+
+
+ Era range: {{ eraRange[0] }} – {{ eraRange[1] }}
+
+
+
+
+
+
+
+ Shown {{ displayJobs.length }} of {{ Object.keys(jobsStore.taggableJobs).length }}
+ taggers.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/views/annotate/subviews/evaluate/EvaluateView.vue b/client/src/views/annotate/subviews/evaluate/EvaluateView.vue
new file mode 100644
index 0000000..279884f
--- /dev/null
+++ b/client/src/views/annotate/subviews/evaluate/EvaluateView.vue
@@ -0,0 +1,132 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Select both layers first.
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/views/annotate/subviews/evaluate/subviews/ConfusionView.vue b/client/src/views/annotate/subviews/evaluate/subviews/ConfusionView.vue
new file mode 100644
index 0000000..53eb312
--- /dev/null
+++ b/client/src/views/annotate/subviews/evaluate/subviews/ConfusionView.vue
@@ -0,0 +1,218 @@
+
+
+
+
+
+
+ The table below presents an overview of the matches (in green) and mismatches per PoS when comparing
+ the tagging of the hypothesis layer with the reference layer. Click on any frequency below to show a
+ data sample.
+
+
+ The category "MULTIPLE" contains combined tags like "ADP+NOU" or "VRB+PD+PD". These are shown in one
+ cell, but this does not mean that the taggers agree on the exact tags. Click on the cell or look at
+ the Global Metrics for more details.
+
+
+
+
+ Select a reference layer and a hypothesis layer to generate a confusion
+ table.
+
+
+
+ part-of-speech
+ ({{ jobSelection.hypothesisJobId }}→)
+ ({{ jobSelection.referenceJobId }}↓)
+
+
+
+
+
+
+
+ {{ data.value }}
+
+
+
+
+ {{ `${(data.value ? data.value.count : 0).toString().padStart(3, ' ')}` }}
+
+
+
+
+
+
+
download(data)" :referenceJob="jobSelection.referenceJobId"
+ :hypothesisJob="jobSelection.hypothesisJobId" />
+
+
+
+
+
+
+
+
+
diff --git a/client/src/views/annotate/subviews/evaluate/subviews/DistributionView.vue b/client/src/views/annotate/subviews/evaluate/subviews/DistributionView.vue
new file mode 100644
index 0000000..27c7f06
--- /dev/null
+++ b/client/src/views/annotate/subviews/evaluate/subviews/DistributionView.vue
@@ -0,0 +1,218 @@
+
+
+
+
+
+
+ No results for current filter settings.
+ Select a hypothesis layer to generate a distribution.
+
+
+ The distribution shows what lemma, part-of-speech pairs have been assigned to which types. When there
+ are more than five types you can click on the inspect symbol to view all types of a lemma-PoS
+ combination.
+
+
+
+
+ Because of the large corpus size only the 1000 most frequent lemma,
+ part-of-speech pairs are shown.
+
+
+
+
+
+
+ {{ `${data.value}` }}
+
+
+
+
+ {{ `${Object.keys(data.item.literals.literals).length}` }}
+
+
+
+
+
+
+
+ {{ literal }} {{ `${data.item.literals.literals[literal]}` }} {{ index !=
+ Object.keys(data.item.literals.literals).length - 1 ? ', ' : '' }}
+
+
+
+
+
+
+ {{ literal }} {{ `${data.item.literals.literals[literal]}` }} ,
+
+ ... and {{ Object.keys(data.item.literals.literals).length - 5 }} more
+
+
+
+
+
+
+
+
+
+
+
+
+
+ Search lemma:
+
+
+
+
+ Search types:
+
+
+
+ Single/multiple PoS:
+
+
+
+
+
+ Include PoS:
+
+
+
+ {{ pos }}
+ None
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/views/annotate/subviews/evaluate/subviews/GlobalMetricsView.vue b/client/src/views/annotate/subviews/evaluate/subviews/GlobalMetricsView.vue
new file mode 100644
index 0000000..796c1ed
--- /dev/null
+++ b/client/src/views/annotate/subviews/evaluate/subviews/GlobalMetricsView.vue
@@ -0,0 +1,125 @@
+
+
+
download(data)" :downloading>
+
+ In Global Metrics an overview is given of the (dis)agreement between the two layers that have been
+ selected for lemma and PoS comparison. Click on the (dis)agreement value to show a data sample.
+
+
+
+
+
download(data)" :downloading />
+
+
+
+
+
+
diff --git a/client/src/views/annotate/subviews/evaluate/subviews/GroupedMetricsView.vue b/client/src/views/annotate/subviews/evaluate/subviews/GroupedMetricsView.vue
new file mode 100644
index 0000000..7bef85a
--- /dev/null
+++ b/client/src/views/annotate/subviews/evaluate/subviews/GroupedMetricsView.vue
@@ -0,0 +1,97 @@
+
+
+
download(data)"
+ :downloading>
+
+ In PoS Metrics an overview is given of the (dis)agreement for lemma and PoS per part-of-speech. Click
+ on
+ the (dis)agreement value to show a data sample.
+
+
+
+
+ Only the 100 most frequent groups are shown.
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/views/application/ApplicationView.vue b/client/src/views/application/ApplicationView.vue
new file mode 100644
index 0000000..cea3954
--- /dev/null
+++ b/client/src/views/application/ApplicationView.vue
@@ -0,0 +1,28 @@
+
+
+
+
+ API
+
+
+
+
+
+
+
+
diff --git a/client/src/views/application/subviews/AboutView.vue b/client/src/views/application/subviews/AboutView.vue
new file mode 100644
index 0000000..664d1e4
--- /dev/null
+++ b/client/src/views/application/subviews/AboutView.vue
@@ -0,0 +1,30 @@
+
+
+ Here you can find some information about the application and the exact version you are currently
+ running.
+
+
+
+
+
Galahad is created by the
+
+ Dutch Language Institute
+
+
+
Version
+
+
+
+
+
+
+
+
diff --git a/client/src/views/contribute/ContributeView.vue b/client/src/views/contribute/ContributeView.vue
new file mode 100644
index 0000000..46076ee
--- /dev/null
+++ b/client/src/views/contribute/ContributeView.vue
@@ -0,0 +1,17 @@
+
+
+
+
+
+
+
+
diff --git a/client/src/views/contribute/subviews/ContributeDatasetsView.vue b/client/src/views/contribute/subviews/ContributeDatasetsView.vue
new file mode 100644
index 0000000..283bc92
--- /dev/null
+++ b/client/src/views/contribute/subviews/ContributeDatasetsView.vue
@@ -0,0 +1,12 @@
+
+
+ Contribute a dataset
+ Creating linguistic infrastructure is a lot of work. Why not do it together?
+ This is a placeholder for a written instrcution on how to provide a dataset, which should include an e-mail
+ adress
+
+
+
+
diff --git a/client/src/views/contribute/subviews/ContributeTaggersView.vue b/client/src/views/contribute/subviews/ContributeTaggersView.vue
new file mode 100644
index 0000000..86393fe
--- /dev/null
+++ b/client/src/views/contribute/subviews/ContributeTaggersView.vue
@@ -0,0 +1,77 @@
+
+
+ Contribute a tagger
+ Creating linguistic infrastructure is a lot of work. Why not do it together?
+ If you have trained your own tagger, you can opt to make it available through Galahad. Read below how this works.
+
+
+ Docker
+
+
+ Galahad uses embedded Docker containers running the taggers. You can share your tagger as a Docker image with us
+ and
+ other researchers. The easiest way to create a Docker image that implements the basic Galahad API is to extend
+ from
+ the galahad-tagger-base image. You can get it from the docker hub.
+
+
+
+ Get
+ instituutnederlandsetaal/galahad-tagger-base from hub.docker.com (This docker repository
+ may not
+ be public during beta.)
+
+
+
+ Specifications
+
+ You need to provide some specifications of your tagger in a yaml file.
+ The file will be named TAGGER_ID.yml
.
+ So a tagger named 'my-tagger' needs to provide a my-tagger.yaml
file that looks like:
+
+
+
+
+# my-tagger.yaml
+description: "This is a short description of my tagger."
+tagset: TDN # Or any other tagset
+eraFrom: "0"
+eraTo: "2020"
+produces: # List of the output types of my-tagger
+- LEM
+- POS
+- TOK
+attributions: # List any number of attributions
+creator: galahad dev
+links: # List any number of links
+- name: website
+href: https://ivdnt.org
+
+
+ (subject to change during beta.)
+
+
+
+ Github
+
+ For more info and examples see our Github
+
+
+
+
+ INL/Galahad and
+ INL/galahad-taggers-dockerized
+ at github.com (Repository may not be public during beta.)
+
+
+ Base images for several frameworks already exist. To read how to submit a tagger created with such a framework, see
+ examples on GitHub.
+
+
+
+
+
+
diff --git a/client/src/views/help/HelpView.vue b/client/src/views/help/HelpView.vue
new file mode 100644
index 0000000..5552777
--- /dev/null
+++ b/client/src/views/help/HelpView.vue
@@ -0,0 +1,22 @@
+
+
+
+
+
+
+
+
diff --git a/client/src/views/help/subviews/GeneralView.vue b/client/src/views/help/subviews/GeneralView.vue
new file mode 100644
index 0000000..57efb95
--- /dev/null
+++ b/client/src/views/help/subviews/GeneralView.vue
@@ -0,0 +1,67 @@
+
+
+
+ Contents
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/views/help/subviews/GlossaryView.vue b/client/src/views/help/subviews/GlossaryView.vue
new file mode 100644
index 0000000..da1cf47
--- /dev/null
+++ b/client/src/views/help/subviews/GlossaryView.vue
@@ -0,0 +1,49 @@
+
+
+
+ Find a description of key terms in the glossary.
+
+ BlackLab
+ BlackLab allows fast, complex searches with accurate hit highlighting on large, tagged and annotated, bodies of
+ text.
+ TBD
+
+ CLARIAH
+ Common Lab Research Infrastructure for the Arts and Humanities
+ See clariah.nl and
+ CLARIAH at INT
+
+ CoBaLT
+ TBD
+
+ Era
+ An era indicates a span of time.
+ For corpora it spans the years during which the documents it contains were written.
+ For taggers, it indicates for which years the tagger was optimized.
+ The tagger may have been trained using data from the indicated era.
+
+ Hypothesis layer
+ The hypothesis layer is a candidate layer. After adding it to your documents, you can evaluate it.
+ You can compare the hypothesis layer to a reference layer.
+
+ INT
+ Instituut voor de Nederlandse taal, the Dutch Language Institute.
+ See ivdnt.org
+
+ Reference layer
+ The reference layer is used as the ground truth of your documents.
+ If your documents contain a source layer, you can use it as a reference. You can also choose a different layer.
+
+
+
+
+
+
+
+
diff --git a/client/src/views/help/subviews/formats/DocumentFormatsView.vue b/client/src/views/help/subviews/formats/DocumentFormatsView.vue
new file mode 100644
index 0000000..1efba13
--- /dev/null
+++ b/client/src/views/help/subviews/formats/DocumentFormatsView.vue
@@ -0,0 +1,240 @@
+
+
+
+ Document Formats
+
+
+ The following formats are supported by GaLAHaD. At the very least, a file of the respective format can be
+ uploaded. Most formats can also be used as an export format. This includes creating new files of that format to
+ download irrespective of the original document,
+ but also merging files of that format with the original document to preserve metadata and structure (only if it
+ is of
+ the same format of course).
+
+
+ Text-like formats
+
+
+ Plain text
+
+
+ Plain text files only contain text, no annotations.
+
+
+
+
+ Tab Separated Values (TSV)
+
+
+
+ TSV files may contain any number of columns in any order. However, a header is required.
+ GaLAHaD looks
+ for three columns:
+
+ A token column, called any of: word, token, literal, term, form
+ A lemma column called: lemma
+ A part of speech column called: pos
+
+
+
+
+ GaLAHaD exports TSV files with the columns word , lemma and pos ; in that order.
+
+
+word lemma pos
+hello hello INT
+world world NOU
+
+
+
+ When merging, the original columns are preserved.
+
+
+
+
+
+ CoNLL-U
+
+
+ CoNLL-U (Universal Dependencies)
+
+
+ CoNLL-U files are tab separated text files with a fixed column structure. They can also contain comments. See
+ the reference for details.
+
+
+
+ When importing, GaLAHaD reads the part of speech out of the XPOS column.
+
+
+ When exporting, GaLAHaD fills in the columns ID , FORM , LEMMA , and XPOS .
+
+
+ When merging, the original columns are preserved.
+
+
+
+
+ XML formats
+
+
+ TEI P5
+
+ P5 Guidelines
+
+ TEI P5 is the current version of the TEI Guidelines for Electronic Text Encoding and Interchange , which
+ define and document a markup language for representing the structural, renditional, and conceptual features of
+ texts.
+ Tokens are encoded as element <w>, punctuation as <pc>.
+
+
+
+ The lemma and part of speech are attributes
+ of <w>, namely @lemma and @pos . Note how @pos differs from the @type
+ attribute
+ in other TEI versions.
+
+<w lemma="word" pos="NOU" >
+ word
+</w>
+
+ When importing, intertwined styling tags like <hi> are supported. Unannotated and untokenized text is
+ also
+ supported.
+
+
+
+
+
+ TEI P5 Legacy
+
+
+
+
+ This format is similar to TEI P5, but with the part of speech encoded as in TEI P4, i.e. @type
+ instead
+ of @pos .
+
+<w lemma="word" type="VRB" >
+ word
+</w>
+
+
+
+
+
+
+
+
+
+ TEI P4
+
+ P4 Guidelines
+
+ Previous version of the TEI guidelines. Lemma is in the attribute @lemma , part of speech in attribute
+ @type .
+
+
+
+
+ NLP Annotation Format (NAF)
+
+ github.com/newsreader/NAF
+
+ NAF is a stand-off, multilayered annotation schema for representing linguistic annotations. Only the terms
+ layer
+ and
+ the token layer are taken into account.
+
+
+
+
+ Format for Linguistic Annotation (FoLiA)
+
+ FoLiA
+
+ FoLiA is an XML-based annotation format, suitable for the representation of linguistically annotated language
+ resources.
+
+
+
+ If a word (<w>) has multiple lemmas or part of speech annotations, only the last one is imported.
+
+<w>
+ <t>word</t>
+ <lemma class="word" set="example"/>
+ <lemma class="word" set="other_example"/>
+ <pos class="VRB" set="example"/>
+ <pos class="NOU" set="other_example"/>
+</w>
+ In <correction> tags, the <new> tag is imported.
+
+<w>
+ <t>word</t>
+ <correction>
+ <new>
+ <pos class="NOU" set="other_example"/>
+ </new>
+ <original>
+ <pos class="VRB" set="example"/>
+ </original>
+ </correction>
+</w>
+ Intertwined <t-style> tags are supported.
+ Lastly, we also support unannotated and untokenized text.
+
+
+
+ When merging, the lemma and part of speech annotations generated by GaLAHaD are added as the last annotations.
+
+<w>
+ <t>word</t>
+ <lemma class="word" set="example"/>
+ <lemma class="word" set="other_example"/>
+ <pos class="VRB" set="example"/>
+ <pos class="NOU" set="other_example"/>
+ <lemma class="word" set="GaLAHaD_example"/>
+ <pos class="NOU(num=sg)" set="GaLAHaD_example"/>
+</w>
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/views/help/subviews/formats/FormatCapabilities.vue b/client/src/views/help/subviews/formats/FormatCapabilities.vue
new file mode 100644
index 0000000..5918637
--- /dev/null
+++ b/client/src/views/help/subviews/formats/FormatCapabilities.vue
@@ -0,0 +1,42 @@
+
+
+
Supports:
+
+ Uploading
+
+
+ Exporting
+
+
+ Merging
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/views/help/subviews/formats/FormatModeExplanation.vue b/client/src/views/help/subviews/formats/FormatModeExplanation.vue
new file mode 100644
index 0000000..6bcfa1f
--- /dev/null
+++ b/client/src/views/help/subviews/formats/FormatModeExplanation.vue
@@ -0,0 +1,27 @@
+
+ Import
+
+
+
+
+ Export
+
+
+
+
+ Merge
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/views/help/subviews/formats/FormatReference.vue b/client/src/views/help/subviews/formats/FormatReference.vue
new file mode 100644
index 0000000..7cd74b6
--- /dev/null
+++ b/client/src/views/help/subviews/formats/FormatReference.vue
@@ -0,0 +1,17 @@
+
+
+
Reference:
+
+
+
+
+
\ No newline at end of file
diff --git a/client/src/views/help/subviews/formats/TeiP5LegacyWarning.vue b/client/src/views/help/subviews/formats/TeiP5LegacyWarning.vue
new file mode 100644
index 0000000..112cc2c
--- /dev/null
+++ b/client/src/views/help/subviews/formats/TeiP5LegacyWarning.vue
@@ -0,0 +1,8 @@
+
+ This format can be merged, but GaLAHaD removes the part of speech annotations in @type from the
+ original document and inserts the new ones into @pos ; in accordance with TEI P5.
+
+<w lemma="word" type="VRB" pos="NOU" >
+ word
+</w>
+
diff --git a/client/src/views/overview/OverviewView.vue b/client/src/views/overview/OverviewView.vue
new file mode 100644
index 0000000..2dc3ded
--- /dev/null
+++ b/client/src/views/overview/OverviewView.vue
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
diff --git a/client/src/views/overview/subviews/BenchmarksView.vue b/client/src/views/overview/subviews/BenchmarksView.vue
new file mode 100644
index 0000000..afe0fd0
--- /dev/null
+++ b/client/src/views/overview/subviews/BenchmarksView.vue
@@ -0,0 +1,158 @@
+
+
+
+
+ Benchmarks show the performance of taggers on the default datasets.
+ The accuracy scores are given for lemma, PoS, and both.
+
+ For more details on the datasets, see the
+
+ datasets overview
+
+
+ * : when taggers use a different tagset than the reference tagset, the score can be very low.
+
+
+
+
+
+ Select a dataset to view benchmarks.
+
+
+
+
+
+
+ {{ d.item.tagger }}
+
+
+ {{ d.item.tagger }}
+
+
+
+
+ {{ d.value ? d.value.toFixed(2) : "0.00" }}*
+
+
+
+
+ Details
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/client/src/views/overview/subviews/DatasetsView.vue b/client/src/views/overview/subviews/DatasetsView.vue
new file mode 100644
index 0000000..2f2c7c8
--- /dev/null
+++ b/client/src/views/overview/subviews/DatasetsView.vue
@@ -0,0 +1,42 @@
+
+
+
+
+
+
+
+
+
+ Here you can see a small preview of the documents within the selected benchmark set.
+
+
+
+
+
+
+
+
+
diff --git a/client/src/views/overview/subviews/TaggersView.vue b/client/src/views/overview/subviews/TaggersView.vue
new file mode 100644
index 0000000..63ed620
--- /dev/null
+++ b/client/src/views/overview/subviews/TaggersView.vue
@@ -0,0 +1,123 @@
+
+
+
+
+ Here you can see an overview of all available taggers within Galahad.
+ For more information on the taggers, please visit GitHub:
+
+ galahad-taggers-dockerized
+
+
+
+
+ No taggers appeared? That is not right! Please contact the INT at
+
+
+
+
+
+
+ {{ d.value }}
+
+
+
+
+
+ {{ d.value }}
+ Unknown
+
+
+
+
+ {{ d.item.eraFrom }} - {{ d.item.eraTo }}
+
+
+
+
+ {{ sort_tagger_produces(d.value).join(", ") }}
+
+
+
+
+
+
+
+
+
+ {{ key }}
+
+ {{ d.value[key] }}
+
+
+
+
+
+
+
+
+ {{ d.value.name }}
+
+
+
+
+ {{ d.value.name }}
+
+
+
+
+ {{ d.value.name }}
+
+
+
+
+
+
+
+
+
diff --git a/client/src/views/overview/subviews/TagsetsView.vue b/client/src/views/overview/subviews/TagsetsView.vue
new file mode 100644
index 0000000..13c16ab
--- /dev/null
+++ b/client/src/views/overview/subviews/TagsetsView.vue
@@ -0,0 +1,34 @@
+
+
+
+
+ Here you can see an overview of all tagsets used by the benchmark corpora.
+
+
+
+ No tagsets appeared? That is not right! Please contact the INT at
+
+
+
+
+ {{ d.value.join(", ") }}
+
+
+
+
+
+
diff --git a/client/tsconfig.json b/client/tsconfig.json
new file mode 100644
index 0000000..a7a8dd5
--- /dev/null
+++ b/client/tsconfig.json
@@ -0,0 +1,42 @@
+{
+ "compilerOptions": {
+ "allowJs": true,
+ "resolveJsonModule": true,
+ "target": "esnext",
+ "module": "esnext",
+ "strict": true,
+ "noImplicitThis": true,
+ "jsx": "preserve",
+ "importHelpers": true,
+ "moduleResolution": "node",
+ "esModuleInterop": true,
+ "allowSyntheticDefaultImports": true,
+ "sourceMap": true,
+ "baseUrl": ".",
+ "types": [
+ "webpack-env",
+ "jest"
+ ],
+ "paths": {
+ "@/*": [
+ "src/*"
+ ]
+ },
+ "lib": [
+ "esnext",
+ "dom",
+ "dom.iterable",
+ "scripthost"
+ ]
+ },
+ "include": [
+ "src/**/*.ts",
+ "src/**/*.tsx",
+ "src/**/*.vue",
+ "tests/**/*.ts",
+ "tests/**/*.tsx"
+, "vendor.d.ts" ],
+ "exclude": [
+ "node_modules"
+ ]
+}
diff --git a/client/vendor.d.ts b/client/vendor.d.ts
new file mode 100644
index 0000000..6664a1f
--- /dev/null
+++ b/client/vendor.d.ts
@@ -0,0 +1 @@
+declare module 'vue-slider-component';
\ No newline at end of file
diff --git a/client/version.yml b/client/version.yml
new file mode 100644
index 0000000..afda223
--- /dev/null
+++ b/client/version.yml
@@ -0,0 +1,8 @@
+GITHUB_REPOSITORY: "{{GITHUB_REPOSITORY}}"
+VERSION: "{{VERSION_LABEL}}"
+GITHUB_REF_TYPE: "{{GITHUB_REF_TYPE}}"
+GITHUB_REF_NAME: "{{GITHUB_REF_NAME}}"
+GITHUB_SHA: "{{GITHUB_SHA}}"
+GITHUB_WORKFLOW: "{{GITHUB_WORKFLOW}}"
+GITHUB_RUN_NUMBER: "{{GITHUB_RUN_NUMBER}}"
+BUILD_DATE: "{{BUILD_DATE}}"
\ No newline at end of file
diff --git a/client/vite.config.mjs b/client/vite.config.mjs
new file mode 100644
index 0000000..cd9202f
--- /dev/null
+++ b/client/vite.config.mjs
@@ -0,0 +1,20 @@
+// vite.config.js
+import { defineConfig } from 'vite'
+import vue from '@vitejs/plugin-vue'
+import yaml from '@rollup/plugin-yaml'
+import path from 'path'
+
+// https://vitejs.dev/config/
+export default defineConfig({
+ plugins: [vue(), yaml()],
+ server: {
+ watch: {
+ usePolling: true
+ }
+ },
+ resolve: {
+ alias: {
+ "@": path.resolve(__dirname, "./src"),
+ },
+ },
+})
\ No newline at end of file
diff --git a/client/vue-compat.d.ts b/client/vue-compat.d.ts
new file mode 100644
index 0000000..af4a69a
--- /dev/null
+++ b/client/vue-compat.d.ts
@@ -0,0 +1,9 @@
+declare module 'vue' {
+ import { CompatVue } from '@vue/runtime-dom'
+ const Vue: CompatVue
+ export default Vue
+ export * from '@vue/runtime-dom'
+ const { configureCompat } = Vue
+ export { configureCompat }
+ }
+
\ No newline at end of file
diff --git a/codemeta.json b/codemeta.json
new file mode 100644
index 0000000..807c127
--- /dev/null
+++ b/codemeta.json
@@ -0,0 +1,392 @@
+{
+ "@context": "https://w3id.org/codemeta/3.0",
+ "@type": "SoftwareSourceCode",
+ "version": "0.9.0",
+ "dateModified": "2024-05-31",
+ "dateCreated": "2024-05-31",
+ "datePublished": "2024-05-31",
+ "applicationCategory": [
+ "https://w3id.org/nwo-research-fields#Linguistics",
+ "https://w3id.org/nwo-research-fields#ComputationalLinguisticsAndPhilology",
+ "https://w3id.org/nwo-research-fields#ArtificialIntelligenceExpertSystems",
+ "https://w3id.org/nwo-research-fields#SoftwareForTheHumanities",
+ "https://w3id.org/nwo-research-fields#TextualAndLinguisticCorpora",
+ "https://vocabs.dariah.eu/tadirah/en/page/enriching",
+ "https://vocabs.dariah.eu/tadirah/en/page/posTagging",
+ "https://vocabs.dariah.eu/tadirah/en/page/tagging",
+ "https://vocabs.dariah.eu/tadirah/en/page/annotating",
+ "https://vocabs.dariah.eu/tadirah/en/page/merging",
+ "https://vocabs.dariah.eu/tadirah/en/page/converting",
+ "https://vocabs.dariah.eu/tadirah/en/page/lemmatizing",
+ "https://vocabs.dariah.eu/tadirah/en/page/machineLearning",
+ "https://vocabs.dariah.eu/tadirah/en/page/comparing",
+ "https://vocabs.dariah.eu/tadirah/en/page/analyzing"
+ ],
+ "author": [
+ {
+ "@type": "Person",
+ "@id": "http://orcid.org/0009-0006-9941-9582",
+ "type": "Person",
+ "email": "vincent.prins@ivdnt.org",
+ "familyName": "Prins",
+ "givenName": "Vincent"
+ },
+ {
+ "@type": "Person",
+ "familyName": "Brouwer",
+ "givenName": "Tim"
+ }
+ ],
+ "contributor": [
+ "http://orcid.org/0009-0006-9941-9582",
+ {
+ "@type": "Person",
+ "familyName": "Brouwer",
+ "givenName": "Tim"
+ }
+ ],
+ "maintainer": "http://orcid.org/0009-0006-9941-9582",
+ "codeRepository": "git+https://github.com/INL/galahad.git",
+ "description": "GaLAHaD (Generating Linguistic Annotations for Historical Dutch) allows linguists to compare taggers, tag their own corpora, evaluate the results and export their tagged documents.",
+ "downloadUrl": "https://github.com/INL/galahad",
+ "license": "http://spdx.org/licenses/Apache-2.0",
+ "name": "GaLAHaD",
+ "identifier": "galahad",
+ "funding": [
+ {
+ "@type": "Grant",
+ "name": "CLARIAH-PLUS (NWO grant 184.034.023)",
+ "funder": {
+ "@type": "Organization",
+ "name": "NWO",
+ "url": "https://www.nwo.nl"
+ }
+ }
+ ],
+ "operatingSystem": "Linux",
+ "programmingLanguage": [
+ "Kotlin",
+ "Javascript",
+ "Typescript"
+ ],
+ "runtimePlatform": [
+ "JVM",
+ "Node"
+ ],
+ "codemeta:contIntegration": {
+ "id": "https://github.com/INL/galahad/actions"
+ },
+ "continuousIntegration": "https://github.com/INL/galahad/actions",
+ "developmentStatus": [
+ "https://www.repostatus.org/#active",
+ "https://w3id.org/research-technology-readiness-levels#Level6LatePrototype"
+ ],
+ "producer": {
+ "@type": "Organization",
+ "name": "Instituut voor de Nederlandse taal",
+ "@id": "https://www.ivdnt.org",
+ "url": "https://www.ivdnt.org"
+ },
+ "issueTracker": "https://github.com/INL/galahad/issues",
+ "readme": "https://github.com/INL/Galahad/blob/development/readme.md",
+ "releaseNotes": "https://github.com/INL/Galahad/releases",
+ "softwareHelp": [
+ {
+ "@id": "https://portal.clarin.ivdnt.org/galahad/help",
+ "@type": "WebSite",
+ "name": "GaLAHad Help",
+ "url": "https://portal.clarin.ivdnt.org/galahad/help"
+ }
+ ],
+ "targetProduct": [
+ {
+ "@type": "WebApplication",
+ "name": "GaLAHaD",
+ "provider": "https://www.ivdnt.org",
+ "url": "https://portal.clarin.ivdnt.org/galahad"
+ },
+ {
+ "@type": "WebAPI",
+ "name": "GaLAHaD API",
+ "provider": "https://www.ivdnt.org",
+ "endpointUrl": {
+ "@type": "EntryPoint",
+ "url": "https://portal.clarin.ivdnt.org/galahad/api",
+ "contentType": "application/json"
+ },
+ "endpointDescription": {
+ "@type": "CreativeWork",
+ "encodingFormat": "application/json",
+ "url": "https://portal.clarin.ivdnt.org/galahad/api/v3/api-docs"
+ },
+ "documentation": "https://portal.clarin.ivdnt.org/galahad/api/swagger-ui/index.html",
+ "version": "1.0.0",
+ "consumesData": [
+ {
+ "@type": "TextDigitalDocument",
+ "encodingFormat": "text/plain"
+ },
+ {
+ "@type": "TextDigitalDocument",
+ "encodingFormat": "application/folia+xml"
+ },
+ {
+ "@type": "TextDigitalDocument",
+ "encodingFormat": "application/tei+xml"
+ },
+ {
+ "@type": "TextDigitalDocument",
+ "encodingFormat": "text/tab-separated-values"
+ },
+ {
+ "@type": "TextDigitalDocument",
+ "encodingFormat": "https://github.com/newsreader/NAF"
+ },
+ {
+ "@type": "TextDigitalDocument",
+ "encodingFormat": "https://universaldependencies.org/format.html"
+ }
+ ],
+ "producesData": [
+ {
+ "@type": "CreativeWork",
+ "encodingFormat": "application/zip"
+ }
+ ]
+ },
+ {
+ "@type": "ServerApplication",
+ "name": "GaLAHaD proxy"
+ },
+ {
+ "@type": "SoftwareImage",
+ "name": "GaLAHaD client Docker image"
+ },
+ {
+ "@type": "SoftwareImage",
+ "name": "GaLAHaD server Docker image"
+ },
+ {
+ "@type": "SoftwareImage",
+ "name": "GaLAHaD proxy Docker image"
+ }
+ ],
+ "softwareRequirements": [
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "@rollup/plugin-yaml",
+ "name": "@rollup/plugin-yaml",
+ "version": "^4.0.1"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "@types/jest",
+ "name": "@types/jest",
+ "version": "^28.1.6"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "@types/js-yaml",
+ "name": "@types/js-yaml",
+ "version": "^4.0.5"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "@types/uuid",
+ "name": "@types/uuid",
+ "version": "^8.3.4"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "@typescript-eslint/eslint-plugin",
+ "name": "@typescript-eslint/eslint-plugin",
+ "version": "^5.30.7"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "@typescript-eslint/parser",
+ "name": "@typescript-eslint/parser",
+ "version": "^5.30.7"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "@vitejs/plugin-vue",
+ "name": "@vitejs/plugin-vue",
+ "version": "^5.0.4"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "@vue/eslint-config-typescript",
+ "name": "@vue/eslint-config-typescript",
+ "version": "^11.0.0"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "axios",
+ "name": "axios",
+ "version": "^1.6.2"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "buffer",
+ "name": "buffer",
+ "version": "^6.0.3"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "content-disposition",
+ "name": "content-disposition",
+ "version": "^0.5.4"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "eslint",
+ "name": "eslint",
+ "version": "^8.20.0"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "eslint-plugin-vue",
+ "name": "eslint-plugin-vue",
+ "version": "^9.2.0"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "js-yaml",
+ "name": "js-yaml",
+ "version": "^4.1.0"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "json-loader",
+ "name": "json-loader",
+ "version": "^0.5.7"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "mutationobserver-shim",
+ "name": "mutationobserver-shim",
+ "version": "^0.3.7"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "node-sass",
+ "name": "node-sass",
+ "version": "^9.0.0"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "pinia",
+ "name": "pinia",
+ "version": "^2.0.16"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "safe-buffer",
+ "name": "safe-buffer",
+ "version": "^5.2.1"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "sass",
+ "name": "sass",
+ "version": "^1.53.0"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "typescript",
+ "name": "typescript",
+ "version": "^4.7.4"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "uuid",
+ "name": "uuid",
+ "version": "^8.3.2"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "vite",
+ "name": "vite",
+ "version": "^5.1.7"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "vue",
+ "name": "vue",
+ "version": "^3.2.37"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "vue-router",
+ "name": "vue-router",
+ "version": "^4.1.2"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "vue-slider-component",
+ "name": "vue-slider-component",
+ "version": "^4.1.0-beta.1"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "com.beust.klaxon",
+ "name": "klaxon",
+ "version": "5.6"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "org.jetbrains.kotlin.kotlin-reflect",
+ "name": "kotlin-reflect",
+ "version": "1.9.22"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "org.jetbrains.kotlin.kotlin-stdlib",
+ "name": "kotlin-stdlib",
+ "version": "1.9.22"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "org.jetbrains.kotlinx.kotlinx-coroutines-core-jvm",
+ "name": "kotlinx-coroutines-core-jvm",
+ "version": "1.7.3"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "org.jetbrains.kotlinx.kotlinx-serialization-json-jvm",
+ "name": "kotlinx-serialization-json-jvm",
+ "version": "1.6.3"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "org.apache.logging.log4j.log4j-api-kotlin",
+ "name": "log4j-api-kotlin",
+ "version": "1.2.0"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "org.yaml.snakeyaml",
+ "name": "snakeyaml",
+ "version": "2.2"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "org.springframework.boot.spring-boot-devtools",
+ "name": "spring-boot-devtools",
+ "version": "3.2.3"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "org.springframework.boot.spring-boot-starter-web",
+ "name": "spring-boot-starter-web",
+ "version": "3.2.4"
+ },
+ {
+ "@type": "SoftwareApplication",
+ "identifier": "org.springdoc.springdoc-openapi-starter-webmvc-ui",
+ "name": "springdoc-openapi-starter-webmvc-ui",
+ "version": "2.5.0"
+ }
+ ]
+}
\ No newline at end of file
diff --git a/docker-compose.override.yml b/docker-compose.override.yml
new file mode 100644
index 0000000..d6164ff
--- /dev/null
+++ b/docker-compose.override.yml
@@ -0,0 +1,7 @@
+# Docker compose override file for galahad client, server and proxy
+
+version: '3'
+services:
+ server:
+ ports:
+ - 8010:8010 # used for the reference test, which directly calls the server
diff --git a/docker-compose.yml b/docker-compose.yml
new file mode 100644
index 0000000..c019d0e
--- /dev/null
+++ b/docker-compose.yml
@@ -0,0 +1,76 @@
+# Docker compose file for galahad client, server and proxy
+# Note: outside of swarm mode, the deploy key requires use of the --compatibility flag
+
+version: '3'
+services:
+ client:
+ image: instituutnederlandsetaal/galahad-client:${APP_VERSION}
+ restart: unless-stopped
+ networks:
+ - proxy_network
+ deploy:
+ resources:
+ limits:
+ cpus: '0.50'
+ memory: 512M
+ reservations:
+ cpus: '0.25'
+ memory: 128M
+ server:
+ image: instituutnederlandsetaal/galahad-server:${APP_VERSION}
+ restart: unless-stopped
+ volumes:
+ - user-corpora-volume:/data/corpora/custom
+ - preset-corpora-volume:/data/corpora/presets
+ - formats-volume:/data/formats
+ - taggers-volume:/data/taggers
+ - tagsets-volume:/data/tagsets
+ - admins-volume:/data/admins
+ networks:
+ - proxy_network
+ - taggers
+ deploy:
+ resources:
+ limits:
+ cpus: '2.00'
+ memory: 8G # should be higher that the java heap size
+ reservations:
+ cpus: '1.00'
+ memory: 2G
+ environment: # Otherwise container doesn't restart on OutOfMemory
+ - "JAVA_OPTS=-XX:+ExitOnOutOfMemoryError"
+ # Simple nginx reverse proxy to combine front- and backend
+ proxy:
+ image: instituutnederlandsetaal/galahad-proxy:${APP_VERSION}
+ depends_on:
+ - client
+ - server
+ ports:
+ - "80:80"
+ restart: unless-stopped
+ networks:
+ - proxy_network
+ deploy:
+ resources:
+ limits:
+ cpus: '0.50'
+ memory: 256M
+ reservations:
+ cpus: '0.25'
+ memory: 128M
+
+networks:
+ front:
+ driver: bridge
+ proxy_network:
+ driver: bridge
+ taggers:
+ external: true
+ name: $taggers_network
+volumes:
+ user-corpora-volume:
+ preset-corpora-volume:
+ formats-volume:
+ taggers-volume:
+ tagsets-volume:
+ admins-volume:
diff --git a/proxy/Dockerfile b/proxy/Dockerfile
new file mode 100644
index 0000000..b217c77
--- /dev/null
+++ b/proxy/Dockerfile
@@ -0,0 +1,12 @@
+# nginx reverse proxy to combine frontend and backend
+#============================
+
+FROM nginx:alpine
+
+# FIX CVE-2023-52425
+RUN apk --no-cache add --upgrade expat
+
+# Config file
+COPY nginx.conf /etc/nginx/conf.d/default.conf
+
+EXPOSE 80
\ No newline at end of file
diff --git a/proxy/nginx.conf b/proxy/nginx.conf
new file mode 100644
index 0000000..06c99a4
--- /dev/null
+++ b/proxy/nginx.conf
@@ -0,0 +1,25 @@
+server {
+ listen 80;
+ listen [::]:80 default ipv6only=on;
+
+ root /usr/share/nginx/html;
+ index index.html;
+
+ # all hostnames
+ server_name _;
+
+ # Reverse proxies for the backend
+ location /galahad/api/ {
+ proxy_pass http://server:8010/;
+ proxy_set_header X-Forwarded-Host localhost;
+ proxy_set_header X-Forwarded-Proto http;
+ }
+
+ # Reverse proxy for the frontend
+ location / {
+ proxy_pass http://client:8080/;
+ }
+
+ # file size
+ client_max_body_size 150M;
+}
\ No newline at end of file
diff --git a/push.sh b/push.sh
new file mode 100644
index 0000000..6622418
--- /dev/null
+++ b/push.sh
@@ -0,0 +1,3 @@
+docker push instituutnederlandsetaal/galahad-client:dev
+docker push instituutnederlandsetaal/galahad-server:dev
+docker push instituutnederlandsetaal/galahad-proxy:dev
diff --git a/readme.md b/readme.md
new file mode 100644
index 0000000..a4f77c3
--- /dev/null
+++ b/readme.md
@@ -0,0 +1,126 @@
+# GaLAHaD (0.9.0)
+Generating Linguistic Annotations for Historical Dutch
+
+[![Version, build and push to Docker Hub](https://github.com/INL/Galahad/actions/workflows/publish-dev.yml/badge.svg)](https://github.com/INL/Galahad/actions/workflows/publish-dev.yml)
+[![Tests](https://github.com/INL/Galahad/actions/workflows/tests.yml/badge.svg?branch=development&event=push)](https://github.com/INL/Galahad/actions/workflows/tests.yml)
+
+### GaLAHaD-related Repositories
+- [galahad](https://github.com/INL/galahad) [you are here]
+- [galahad-train-battery](https://github.com/INL/galahad-train-battery)
+- [galahad-taggers-dockerized](https://github.com/INL/galahad-taggers-dockerized) [to be released]
+- [galahad-corpus-data](https://github.com/INL/galahad-corpus-data/)
+- [int-pie](https://github.com/INL/int-pie)
+- [int-huggingface-tagger](https://github.com/INL/huggingface-tagger) [to be released]
+
+## Goal
+Galahad is developed as part of the CLARIAH "Improved Infrastructure for Historical Dutch" project. The goal is an application that:
+
+- enables linguïsts:
+ - to check which taggers are suitable for tagging their corpus.
+ - to have their corpus tagged
+- enables computational linguists:
+ - to provide their models through a unified interface
+ - to have their model evaluated
+
+This is provided through a platform that offers:
+- statistics for submitted models on existing corpora
+- a corpus annotation service
+- instructions on how to submit a model
+
+Note that this infrastructure can also be of interest for other languages and eras.
+
+## Team
+
+### Principal engineer
+
+- vincent.prins@ivdnt.org
+
+### Scientific advisors
+
+- Jesse de Does
+- Katrien Depuydt
+
+## Quick start
+
+Do you have docker and docker-compose? Do you have access to the public Docker Hub [instituutnederlandsetaal](https://hub.docker.com/repositories/instituutnederlandsetaal)? Then you can clone this repository and run
+
+```
+docker compose up
+```
+This requires an external taggers network to exists. You can use the `docker-compose.yml` from `https://github.com/INL/galahad-taggers-dockerized` to start a taggers network.
+
+To run Galahad locally. The webclient is available on port 8080.
+
+# Setup for development
+
+Clone the code.
+
+`git clone https://github.com/INL/Galahad.git`
+
+## The client
+
+Start the client.
+
+`cd galahad/client`
+
+`npm install`
+
+`npm run dev`
+
+Go to `http://localhost:5173/` in the browser to check the client development server is running.
+
+## The server
+Go to your favourite IDE and open the Gradle project in `galahad/server`.
+
+For development, add `spring.profiles.active=dev` to the environment variables. If you are using IntelliJ, simply use `server/.run/GalahadApplication.run.xml`. This is needed to differentiate whether we are in a docker container (production) or on the localhost (development), which in turn changes how we must communicate with the taggers (via a docker network or via the localhost).
+
+Run `galahad/server/src/main/kotlin/org/ivdnt/galahad/app/GalahadApplication.kt` from your IDE. Check `http://localhost:8010` to see whether see server is running.
+
+Go back to the client in the browser and try to create a corpus and upload some documents.
+
+## The taggers
+
+In development the application will talk to the taggers through a port-forward. The port-forwards are defined in `docker-compose.yml` from `https://github.com/INL/galahad-taggers-dockerized`. The port-forwards should be defined accordingly as `devport` in the taggers specifications at `server/data/taggers/*.yaml` to enable communication.
+
+## Adding a new tagger
+
+*Asssuming you have already wrapped your tagger in a Docker image.*
+
+First, launch your tagger. See `https://github.com/INL/galahad-taggers-dockerized`.
+
+Now make Galahad aware of the new tagger by creating a tagger metadata yaml file. See `server/data/taggers/` in this repo for examples.
+
+Make the specification yaml available to Galahad:
+- If you are running Galahad server from a docker container, the specification yaml should be placed on the docker volume at `data/taggers/`.
+- If you are running Galahad server otherwise e.g. from your IDE, you can add the specifications yaml directly to `server/data/taggers/`
+
+Refresh the browser to load the new tagger.
+
+## Adding admins
+
+You can configure the admins account through a file `admins.txt`. Add the desired admin users one per line. To update the file (create it if it does not exists):
+```
+docker compose exec server sh
+cd data
+vi admins.txt # make your edits
+```
+
+App should autoreload and update to the new status, but refresh client just to be sure.
+
+
+## Supported file formats
+Plain text, TSV, CoNLL-U, TEI, NAF, FoLia.
+For more details, see the help screen on formats on the GaLAHaD website.
+
+## Technical notes
+
+### Swagger UI
+
+Once you have launched the application, you can explore the public API at
+
+`http://localhost:8010/swagger-ui.html`
+
+### application BasePath
+
+The INT runs the application behind a portal on a path `/galahad`. Therefore this is set as the default path for the application. Changing this basePath requires to at least rebuild the client application with a different `vite build --base=/galahad/` set.
+
diff --git a/scripts/benchmarking/1doc_100char/100ch.txt b/scripts/benchmarking/1doc_100char/100ch.txt
new file mode 100644
index 0000000..cc1069a
--- /dev/null
+++ b/scripts/benchmarking/1doc_100char/100ch.txt
@@ -0,0 +1 @@
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
diff --git a/scripts/benchmarking/1doc_10kchar/10kch.txt b/scripts/benchmarking/1doc_10kchar/10kch.txt
new file mode 100644
index 0000000..fca5455
--- /dev/null
+++ b/scripts/benchmarking/1doc_10kchar/10kch.txt
@@ -0,0 +1,100 @@
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
+tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars tienchars
diff --git a/scripts/benchmarking/run-benchmarks.sh b/scripts/benchmarking/run-benchmarks.sh
new file mode 100644
index 0000000..ad67ffc
--- /dev/null
+++ b/scripts/benchmarking/run-benchmarks.sh
@@ -0,0 +1,390 @@
+#!/bin/bash
+# Run benchmarks and store the result in the galahad-server
+# This script is intended to be a progressive load test of the server
+# Things might go wrong and/or your server may run out of capacity.
+# So make sure you know what you are doing!
+# It is recommended to run this script once only, after deployment and before allowing users access to the server
+
+case $PWD/ in
+ */scripts/benchmarking/) echo "I will run in scripts/benchmarking";;
+ *) echo "I refuse to run outside of scripts/benchmarking" && exit 1;;
+esac
+
+API_URL="http://localhost:8010"
+SERVER_CONTAINER="galahad_server_1" # can also be "galahad-server-1"
+SOURCE_LAYER="sourceLayer"
+FORMATS=("naf" "tei-p5" "tsv") # "folia"
+TAGGERS=("dev-naive") # "frog-nld" "pie-mixed")
+start=`date +%s`
+
+# exit when any command fails
+set -e
+
+ENV=''
+if [ "$1" = "prod" ]
+then
+ ENV='prod'
+ if [ -n "$2" ]
+ then
+ SERVER_CONTAINER="$2"
+ fi
+elif [ "$1" = "dev" ]
+then
+ ENV='dev'
+else
+ echo "Usage $0 prod|dev [galahad_server_container_name]"
+ exit 1
+fi
+
+# keep track of the last executed command
+trap 'last_command=$current_command; current_command=$BASH_COMMAND' DEBUG
+# echo an error message before exiting
+trap 'exit_code=$?; final_command=$last_command;
+if [ $exit_code -ne 0 ]; then
+ echo "\"${final_command}\" or a subsequent command failed with exit code $exit_code. If this is an eror, please fix it and rerun the benchmarks."
+ shareFailure
+fi' EXIT
+
+echo ""
+echo "--------------------------------------------------------------------------"
+echo "$(date) $(realpath $0)"
+echo ""
+echo "Run benchmarks and store the result in the galahad-server"
+echo "This script will upload, tag, evaluate and export increasingly larger corpora."
+echo "This may take very long, and use a lot of resources. You are encouraged to interrupt the process at any time if it takes to long"
+echo "The state at time of interruption is consider the result or score of the benchmark."
+
+while true; do
+ read -p "Do you know what you are doing? " yn
+ case $yn in
+ [Yy]* ) break;;
+ [Nn]* ) echo "You could start by reading $0 or visit https://github.com/INL/Galahad"; exit;;
+ * ) echo "Please answer yes or no.";;
+ esac
+done
+while true; do
+ read -p "Are you sure you want to run this PROGRESSIVE LOAD TEST? " yn
+ case $yn in
+ [Yy]* ) break;;
+ [Nn]* ) echo "Ok then, you can run this script at a later point."; exit;;
+ * ) echo "Please answer yes or no.";;
+ esac
+done
+
+echo "Very well, the benchmarking will now begin"
+echo ""
+
+rm -f benchmarks.yml
+
+INDENT=0
+
+indent_more () {
+ INDENT=$(($INDENT+1))
+}
+
+indent_less () {
+ INDENT=$(($INDENT-1))
+}
+
+output () {
+ if [ "$INDENT" -gt "0" ]
+ then
+ for i in $(seq 1 $INDENT); do echo " "; done
+ for i in $(seq 1 $INDENT); do printf " " >> benchmarks.yml; done
+ fi
+ echo $1
+ printf "$1\n" >> benchmarks.yml
+}
+
+beginReport () {
+ output "# generated by benchmarking/run-benchmarks.sh"
+ output "# results may vary base on the setup used at runtime"
+ nodename=`uname --nodename`
+ head=`echo $nodename | head -c 2`
+ tail=`echo $nodename | tail -c 3`
+ safename="$head..$tail"
+ output "benchmark-server: $safename"
+ output "api_url: $API_URL"
+}
+
+shareFailure () {
+ timestamp=`date +%s%N`
+ output "FAILURE: $timestamp"
+ shareReport
+}
+
+shareReport () {
+ if [ "$ENV" = "prod" ]
+ then
+ docker cp benchmarks.yml $SERVER_CONTAINER:benchmarks.yml
+ elif [ "$ENV" = "dev" ]
+ then
+ cp benchmarks.yml ../../server/benchmarks.yml
+ fi
+}
+
+begin_benchmark () {
+ benchmark_start=`date +%s%N`
+ BENCHMARK_NAME=$1
+ output "$1:"
+ indent_more
+ output "start: $benchmark_start"
+ shareReport
+}
+
+begin_step () {
+ step_start=`date +%s%N`
+ output "$1:"
+ indent_more
+ output "start: $step_start"
+ shareReport
+}
+
+begin_tagger () {
+ tagger_start=`date +%s%N`
+ output "$1:"
+ indent_more
+ output "start: $tagger_start"
+ shareReport
+}
+
+begin_evaluation_step () {
+ step_start=`date +%s%N`
+ output "$1:"
+ indent_more
+ output "start: $step_start"
+ shareReport
+}
+
+end_evaluation_step () {
+ step_end=`date +%s%N`
+ output "end: $step_end"
+ indent_less
+ shareReport
+}
+
+end_tagger () {
+ tagger_end=`date +%s%N`
+ duration=$(($tagger_end-$tagger_start))
+ output "end: $tagger_end"
+ output "duration: $duration"
+ indent_less
+ shareReport
+}
+
+end_step () {
+ step_end=`date +%s%N`
+ duration=$(($step_end-$step_start))
+ output "end: $step_end"
+ output "duration: $duration"
+ indent_less
+ shareReport
+}
+
+end_benchmark () {
+ benchmark_end=`date +%s%N`
+ duration=$(($benchmark_end-$benchmark_start))
+ output "end: $benchmark_end"
+ output "duration: $duration"
+ indent_less
+ shareReport
+}
+
+do_benchmark () {
+ begin_benchmark $1
+ begin_step "data"
+
+ echo "--- creating a corpus --- "
+ eval CORPUS_UUID=$(curl -X POST --header 'Content-Type: application/json' \
+ --header 'Accept: application/json' \
+ -d "{ \"name\": \"$BENCHMARK_NAME\", \"eraFrom\": 0, \"eraTo\": 0, \"collaborators\": [ \"Mark Bench\" ], \"sourceName\": \"source name\", \"sourceURL\": \"http://string\" }" \
+ "$API_URL/corpora")
+ echo "Corpus uuid is <$CORPUS_UUID>"
+ echo ""
+ output "corpus UUID: '$CORPUS_UUID'"
+
+ for FILE in $BENCHMARK_NAME/*
+ do
+ echo " --- uploading file --- "
+ curl_result=$(curl -F "file=@$FILE" --write-out 'HTTP status: %{http_code}' "$API_URL/corpora/$CORPUS_UUID/documents")
+ if [[ $? -ne 0 ]] || [[ ${curl_result} != *'HTTP status: 200' ]]; then
+ echo "Uploading failed. The output of curl was $curl_result. You will fail this test."
+ # premature_fail
+ exit 1
+ fi
+ echo $curl_result
+ echo " --- uploading finished --- "
+ echo ""
+
+ done # for FILE in $BENCHMARK_NAME/*
+
+ end_step "data"
+ begin_step "annotate"
+
+ for TAGGER in "${TAGGERS[@]}"; do
+ begin_tagger $TAGGER
+
+ echo " --- tagging started ---"
+ echo "using url: $API_URL/corpora/$CORPUS_UUID/jobs/$TAGGER"
+ curl_result=$(curl -X POST "$API_URL/corpora/$CORPUS_UUID/jobs/$TAGGER" -H "accept: */*" -d "")
+ if [[ $? -ne 0 ]] ; then
+ echo "Failed to start job $TAGGER. The output of curl was $curl_result. You will fail this test."
+ # premature_fail
+ exit 1
+ fi
+ echo $curl_result
+ echo ""
+
+ polling_max=10000000000
+ polling=0
+ timed_out=true # true unless otherwise
+ while (($polling < $polling_max)); do
+ sleep 1
+ echo "[$BENCHMARK_NAME] polling tagger $polling"
+ isBusy=$(curl -X GET --silent --header 'Accept: application/json' "$API_URL/corpora/$CORPUS_UUID/jobs/$TAGGER/isBusy")
+ if [[ $? -ne 0 ]]; then
+ echo "polling $TAGGER failed. The output of curl was $isBusy. You will fail this test."
+ # premature_fail
+ exit 1
+ fi
+ echo "[$BENCHMARK_NAME] isBusy: $isBusy"
+ polling=$((polling+1))
+ if [[ $isBusy == false ]]; then
+ echo " --- tagging finished. --- "
+ # We can speed ahead
+ polling=$polling_max
+ timed_out=false
+ fi
+ done # While polling < polling_max
+ if [[ $timed_out == true ]]; then
+ echo "tagger $TAGGER timed out. You will fail this test." # TODO consider we even want a timeout for this test
+ # premature_fail
+ exit 1
+ fi
+
+ jobSummary=$(curl -X GET --header 'Accept: application/json' "$API_URL/corpora/$CORPUS_UUID/jobs/$TAGGER")
+ # output "job_summary: '$jobSummary'"
+
+ hasError=$(curl -X GET --silent --header 'Accept: application/json' "$API_URL/corpora/$CORPUS_UUID/jobs/$TAGGER/hasError")
+ if [[ $hasError == true ]]; then
+ echo " --- tagging finished, but has errors, benchmark will end here --- "
+ output "ERROR: taggers has errors, please inspect"
+ output "link: /galahad/annotate/annotations?corpus=$CORPUS_UUID"
+ exit 1
+ fi
+
+ end_tagger $TAGGER
+ done # for TAGGER in "${TAGGERS[@]}"
+
+ end_step "annotate"
+ begin_step "evaluate"
+
+ for TAGGER in "${TAGGERS[@]}"; do
+ begin_tagger $TAGGER
+
+ begin_evaluation_step "distribution"
+ curl -X GET --header 'Accept: application/json' "$API_URL/corpora/$CORPUS_UUID/jobs/$TAGGER/evaluation/distribution"
+ end_evaluation_step "distribution"
+ begin_evaluation_step "metrics"
+ echo "Note: metrics only make sense if a source layer is present on the documents."
+ output "note: without sourcelayer in the documents, this step is not a good benchmark"
+ curl -X GET --header 'Accept: application/json' "$API_URL/corpora/$CORPUS_UUID/jobs/$TAGGER/evaluation/metrics?reference=$SOURCE_LAYER"
+ end_evaluation_step "metrics"
+ begin_evaluation_step "confusion"
+ echo "Note: confusion only make sense if a source layer is present on the documents."
+ output "note: without sourcelayer in the documents, this step is not a good benchmark"
+ curl -X GET --header 'Accept: application/json' "$API_URL/corpora/$CORPUS_UUID/jobs/$TAGGER/evaluation/confusion?reference=$SOURCE_LAYER"
+ end_evaluation_step "confusion"
+
+ end_tagger $TAGGER
+ done # for TAGGER in "${TAGGERS[@]}"
+ end_step "evaluate"
+
+ begin_step "export"
+ echo "Export merge is skipped because it is not a very usefull test for text-only files."
+ output "note: Export merge is skipped because it is not a very usefull test for text-only files."
+ for FORMAT in "${FORMATS[@]}"; do
+ begin_step $FORMAT
+ for TAGGER in "${TAGGERS[@]}"; do
+ begin_tagger $TAGGER
+
+ curl -X GET --header 'Accept: text/plain' -OJ "$API_URL/corpora/$CORPUS_UUID/jobs/$TAGGER/export/convert?format=$FORMAT"
+ # We dont care about the corpus, remove it
+ rm *.zip
+
+ end_tagger $TAGGER
+ done # for TAGGER in "${TAGGERS[@]}"
+ end_step $FORMAT
+ done # for FORMAT in "${FORMATS[@]}"
+ end_step "export"
+ end_benchmark
+
+}
+
+beginReport
+
+do_benchmark "1doc_100char"
+do_benchmark "1doc_10kchar"
+
+# generate our own data, to keep it out of git
+rm -rf 1doc_100kchar # remove to clean up previous failure
+mkdir 1doc_100kchar
+for i in $(seq 1 10); do cat 1doc_10kchar/10kch.txt >> 1doc_100kchar/100kch.txt; done
+do_benchmark "1doc_100kchar"
+rm -r 1doc_100kchar
+
+# generate our own data, to keep it out of git
+rm -rf 1doc_1mchar # remove to clean up previous failure
+mkdir 1doc_1mchar
+for i in $(seq 1 100); do cat 1doc_10kchar/10kch.txt >> 1doc_1mchar/1mch.txt; done
+do_benchmark "1doc_1mchar"
+rm -r 1doc_1mchar
+
+# generate our own data, to keep it out of git
+rm -rf 1doc_10mchar # remove to clean up previous failure
+mkdir 1doc_10mchar
+for i in $(seq 1 1000); do cat 1doc_10kchar/10kch.txt >> 1doc_10mchar/10mch.txt; done
+do_benchmark "1doc_10mchar"
+rm -r 1doc_10mchar
+
+# generate our own data, to keep it out of git
+rm -rf 10doc_10kchar # remove to clean up previous failure
+mkdir 10doc_10kchar
+for i in $(seq 1 10); do cp 1doc_10kchar/10kch.txt 10doc_10kchar/10kch$i.txt; done
+do_benchmark "10doc_10kchar"
+rm -r 10doc_10kchar
+
+# generate our own data, to keep it out of git
+rm -rf 100doc_10kchar # remove to clean up previous failure
+mkdir 100doc_10kchar
+for i in $(seq 1 100); do cp 1doc_10kchar/10kch.txt 100doc_10kchar/10kch$i.txt; done
+do_benchmark "100doc_10kchar"
+rm -r 100doc_10kchar
+
+# generate our own data, to keep it out of git
+rm -rf 1kdoc_10kchar # remove to clean up previous failure
+mkdir 1kdoc_10kchar
+for i in $(seq 1 1000); do cp 1doc_10kchar/10kch.txt 1kdoc_10kchar/10kch$i.txt; done
+do_benchmark "1kdoc_10kchar"
+rm -r 1kdoc_10kchar
+
+# generate our own data, to keep it out of git
+rm -rf 10kdoc_10kchar # remove to clean up previous failure
+mkdir 10kdoc_10kchar
+for i in $(seq 1 10000); do cp 1doc_10kchar/10kch.txt 10kdoc_10kchar/10kch$i.txt; done
+do_benchmark "10kdoc_10kchar"
+rm -r 10kdoc_10kchar
+
+
+cat benchmarks.yml
+shareReport
+
+rm benchmarks.yml
+
+echo "$(date) benchmarks finished."
+echo "Visit https://github.com/INL/Galahad for more, or view the benchmarks result at app_url/benchmarks"
+end=`date +%s`
+runtime=$((end-start))
+echo "Runtime $runtime seconds"
+echo "--------------------------------------------------------------------------"
+echo ""
diff --git a/scripts/build-all.sh b/scripts/build-all.sh
new file mode 100644
index 0000000..07c5cb0
--- /dev/null
+++ b/scripts/build-all.sh
@@ -0,0 +1,8 @@
+# Set the default label
+: ${VERSION_LABEL:=dev}
+
+echo "Will build galahad images with version <$VERSION_LABEL>. Set VERSION_LABEL to override this."
+
+docker build -t instituutnederlandsetaal/galahad-proxy:$VERSION_LABEL proxy/ || { echo 'failed to build proxy, I refuse to continue the build' ; exit 1; }
+docker build -t instituutnederlandsetaal/galahad-server:$VERSION_LABEL server/ || { echo 'failed to build server, I refuse to continue the build' ; exit 1; }
+docker build -t instituutnederlandsetaal/galahad-client:$VERSION_LABEL client/ || { echo 'failed to build client, I refuse to continue the build' ; exit 1; }
diff --git a/scripts/deploy.sh b/scripts/deploy.sh
new file mode 100644
index 0000000..9a1a102
--- /dev/null
+++ b/scripts/deploy.sh
@@ -0,0 +1,86 @@
+#!/bin/bash
+# Automatically redeploy the application
+# This script will change the state of the server
+# So make sure you know what you are doing!
+
+# Optionally you can run a cronjob for this deployment
+# to enable continuous deployment.
+# Open crontab configuration with: crontab -e
+# and add a line like: */5 * * * * cd /path/to/dir && yes | ./scripts/deploy.sh >> /path/to/logdir/deployment.log 2>&1
+# THIS WILL OVERRIDE PREVIOUS DEPLOYMENTS, MAKE SURE YOU KNOW WHAT YOU ARE DOING!
+# I doubt you want this for a production server
+
+start=`date +%s`
+
+# exit when any command fails
+set -e
+
+# keep track of the last executed command
+trap 'last_command=$current_command; current_command=$BASH_COMMAND' DEBUG
+# echo an error message before exiting
+trap 'exit_code=$?; final_command=$last_command;
+if [ $exit_code -ne 0 ]; then
+ echo "\"${final_command}\" command failed with exit code $exit_code. Please fix the error and rerun the deployment. Current deployment maybe in an incomplete state."
+fi' EXIT
+
+echo ""
+echo "--------------------------------------------------------------------------"
+echo "$(date) $(realpath $0)"
+echo ""
+echo "Update and redeploy application, this will execute:"
+echo " - git pull"
+echo " - docker compose pull"
+echo " - docker compose up"
+echo " - docker image prune"
+echo ""
+echo "Optionally:"
+echo " - reset server data"
+echo ""
+while true; do
+ read -p "Do you know what you are doing? " yn
+ case $yn in
+ [Yy]* ) break;;
+ [Nn]* ) echo "You could start by reading $0 or visit https://github.com/INL/Galahad"; exit;;
+ * ) echo "Please answer yes or no.";;
+ esac
+done
+while true; do
+ read -p "Are you sure? " yn
+ case $yn in
+ [Yy]* ) break;;
+ [Nn]* ) echo "Ok then, keep your secrets"; exit;;
+ * ) echo "Please answer yes or no.";;
+ esac
+done
+
+echo "Very well, deploy will now begin"
+echo ""
+
+git pull | while IFS= read -r line; do printf '[%s git pull] %s\n' "$(date '+%H:%M:%S')" "$line"; done
+echo ""
+docker compose pull | while IFS= read -r line; do printf '[%s docker compose pull] %s\n' "$(date '+%H:%M:%S')" "$line"; done
+echo ""
+docker compose --compatibility up -d | while IFS= read -r line; do printf '[%s docker compose up] %s\n' "$(date '+%H:%M:%S')" "$line"; done
+echo ""
+yes | docker image prune | while IFS= read -r line; do printf '[%s docker image prune] %s\n' "$(date '+%H:%M:%S')" "$line"; done
+echo ""
+
+# reset the data
+while true; do
+ read -p "Do you want to rest server data (formats, taggers, tagsets)?
+ [Note: to avoid accidentaly removing user data, private corpora always need to be reset manually.]
+" yn
+ case $yn in
+ [Yy]* ) echo "I will reset the data"; ./scripts/reset-data.sh; break;;
+ [Nn]* ) echo "I will not reset the data"; break;;
+ * ) echo "Please answer yes or no.";;
+ esac
+done
+
+echo "$(date) deployment finished."
+echo "Visit https://github.com/INL/Galahad for more"
+end=`date +%s`
+runtime=$((end-start))
+echo "Runtime $runtime seconds"
+echo "--------------------------------------------------------------------------"
+echo ""
diff --git a/scripts/reset-data.sh b/scripts/reset-data.sh
new file mode 100644
index 0000000..a1f6b05
--- /dev/null
+++ b/scripts/reset-data.sh
@@ -0,0 +1,32 @@
+#!/bin/bash
+# This script resets/updates the data in the docker volumes to the data in the repo
+#
+# Note: we require jq. Feel free to write an implementation without jq.
+
+echo "docker volume ls:"
+docker volume ls
+
+echo ""
+echo "resetting formats ..."
+FORMATS_DIR=$(docker inspect galahad_formats-volume | jq -r '.[0].Mountpoint')
+# Somehow the rm fails. TODO fix it.
+rm -v --interactive=never "$FORMATS_DIR/*"
+cp -v ./server/data/formats/* $FORMATS_DIR
+echo "Available formats are:"
+ls $FORMATS_DIR
+
+echo ""
+echo "resetting taggers ..."
+TAGGERS_DIR=$(docker inspect galahad_taggers-volume | jq -r '.[0].Mountpoint')
+rm -v --interactive=never "$TAGGERS_DIR/*"
+cp -v ./server/data/taggers/* $TAGGERS_DIR
+echo "Available taggers are:"
+ls $TAGGERS_DIR
+
+echo ""
+echo "resetting tagsets ..."
+TAGSETS_DIR=$(docker inspect galahad_tagsets-volume | jq -r '.[0].Mountpoint')
+rm -v --interactive=never "$TAGSETS_DIR/*"
+cp -v ./server/data/tagsets/* $TAGSETS_DIR
+echo "Available tagsets are:"
+ls $TAGSETS_DIR
diff --git a/scripts/tag-matching/CLVN-hug-2023-11-20.txt b/scripts/tag-matching/CLVN-hug-2023-11-20.txt
new file mode 100644
index 0000000..6ef9e19
--- /dev/null
+++ b/scripts/tag-matching/CLVN-hug-2023-11-20.txt
@@ -0,0 +1,930 @@
+01_Archief_Delfland_1531gDelfland.tei.xml -- 6da1b4cd-cc83-45a1-9c4a-f81441271689
+[SUCCESS] Found all tags with advanced matching.
+
+
+01_Archief_Delfland_1532jDelfland.tei.xml -- e01f38ff-bc1c-4afe-97fb-c7fd9d71cc3c
+[SUCCESS] Found all tags with advanced matching.
+
+
+02_Archief_Elburg_1580aElburg.tei.xml -- 080ab7de-3f9a-4f79-9a0b-7fd0ab4a6274
+[ERROR] Could not resolve mismatch:
+ Truth: H..t
+ Pred: H
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_ALKMAAR_10-4-1573_TS.tei.xml -- 0abb6244-876f-459c-8b27-013a57b61df8
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Alkmaar_10-6-1575_TS.tei.xml -- 6e6de06f-df3e-4e97-a6b1-6d641ab8de6b
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t.
+ Pred: s
+[ERROR] Could not resolve mismatch:
+ Truth: Ordre
+ Pred: L
+[ERROR] Could not resolve mismatch:
+ Truth: van
+ Pred: .
+[ERROR] Could not resolve mismatch:
+ Truth: Senoy
+ Pred: Coren
+[ERROR] Could not resolve mismatch:
+ Truth: gouverneur
+ Pred: s
+[FAIL] Advanced matching misses 5 tags.
+
+
+03_ArchiefAlkmaar_Alkmaar_16-2-1574_TS.tei.xml -- 00176f3c-9ac2-443c-a0ef-c4d55e77a8ad
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t.
+ Pred: s
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Alkmaar_8-2-1574_TS.tei.xml -- 5a582f5c-ac0b-4531-8148-9207123a9b06
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t
+ Pred: s
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Delft_18-6-1573_TS.tei.xml -- d9a75dc7-1f29-4b8c-b1f5-c2b46e96d762
+[ERROR] Could not resolve mismatch:
+ Truth: [v]rienden
+ Pred: [
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Delft_24-3-1573_TS.tei.xml -- 966eb985-2519-4005-ae04-5ca873d07598
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t
+ Pred: s
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Den_Haag_15-9-1572_TS.tei.xml -- a5fb4752-9bf7-4a53-bf1f-15193b986ec0
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Den_Haag_2-10-1586_TS.tei.xml -- 3d82bbca-c0c8-487d-aed4-890366549878
+[ERROR] Could not resolve mismatch:
+ Truth: d'onkosten
+ Pred: d
+[ERROR] Could not resolve mismatch:
+ Truth: d'oncosten
+ Pred: d
+[FAIL] Advanced matching misses 2 tags.
+
+
+03_ArchiefAlkmaar_Diemerdijk_7-6-1573_TS.tei.xml -- f6fb158a-7a8e-40bb-a389-354cf6c073b1
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Dordrecht_23-8-1573_TS.tei.xml -- 1db709e8-40d7-436e-b2dc-d119797b01e1
+[ERROR] Could not resolve mismatch:
+ Truth: u[lieden]
+ Pred: u
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t
+ Pred: s
+[ERROR] Could not resolve mismatch:
+ Truth: [sc]hepenen
+ Pred: [
+[FAIL] Advanced matching misses 3 tags.
+
+
+03_ArchiefAlkmaar_Edam_15-9-1573_TS.tei.xml -- af78dcc3-6273-45c2-ad6b-3a0489f2717a
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Enkhuizen_5-4-1574_TS_b.tei.xml -- 61ee3744-69d2-4d5b-ab31-411742f049e9
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Enkhuizen__27-10-1572_TS.tei.xml -- ae4722c1-82fc-4dad-8301-40c57180196c
+[ERROR] Could not resolve mismatch:
+ Truth: [ci]to
+ Pred: [
+[ERROR] Could not resolve mismatch:
+ Truth: [ci]to
+ Pred: ci
+[FAIL] Advanced matching misses 2 tags.
+
+
+03_ArchiefAlkmaar_Hoorn_17-8-1573_TS.tei.xml -- 63d6d61f-0322-40d3-8a17-c11cfb95f12c
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t
+ Pred: s
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Hoorn_8-3-1573_TS.tei.xml -- b23d1e66-d8e5-4039-9e20-3cde43430b93
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Leiden_13-7-1573_TS_b.tei.xml -- a40a7b23-1bf3-46f7-bb1c-f671e90c1657
+[ERROR] Could not resolve mismatch:
+ Truth: N.Brunynck
+ Pred: N
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Leiden_16-6-1573_TS.tei.xml -- b1bb7282-6b16-49d8-8d9c-e0793a0163a2
+[ERROR] Could not resolve mismatch:
+ Truth: N.Brunynck
+ Pred: N
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t
+ Pred: .
+[FAIL] Advanced matching misses 2 tags.
+
+
+03_ArchiefAlkmaar_Middelburg_15-3-1574_TS.tei.xml -- cf1b73d5-cb24-4473-90d6-b603e216123f
+[ERROR] Could not resolve mismatch:
+ Truth: [c]ito
+ Pred: .
+[ERROR] Could not resolve mismatch:
+ Truth: [c]ito
+ Pred: [
+[ERROR] Could not resolve mismatch:
+ Truth: [m]ijne
+ Pred: [
+[FAIL] Advanced matching misses 3 tags.
+
+
+03_ArchiefAlkmaar_Purmerend_2-5-1574_TS.tei.xml -- ffb5c177-8589-4632-af14-a9bb8f695eb8
+[ERROR] Could not resolve mismatch:
+ Truth: Gunsty[ge]
+ Pred: Gunsty
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Purmerend_4-6-1574_TS.tei.xml -- c6611cf7-368e-4cf5-a643-94d1ecb4f22b
+[SUCCESS] Found all tags with advanced matching.
+
+
+04_ArchiefEnschede_1596eEnschede.tei.xml -- a436f3d0-1203-4140-bf96-b155c6850ed5
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_12maarta.tei.xml -- ba0db231-1e82-474b-8a9a-6b9132dda26e
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_12maartb.tei.xml -- 78eafa2f-c636-44f7-8b46-520dee8ce338
+[ERROR] Could not resolve mismatch:
+ Truth: J.M.W
+ Pred: J
+[FAIL] Advanced matching misses 1 tags.
+
+
+05_ArchiefZutphen_1552Zua.tei.xml -- 106757d0-fd5e-434b-b163-ac190fababf4
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_1571le.tei.xml -- 213e8a4e-527e-4d1c-ae61-64ac85db0b34
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_1571zur.tei.xml -- 6aaa3c61-403d-4c60-b5fd-ee5409c735c8
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_1572eZutphen.tei.xml -- 93730ee4-0c6c-48d2-abc9-e168d9045090
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_1572iiZutphen.tei.xml -- 4b2956a0-c8ad-4894-af69-fd00ba08cd17
+[ERROR] Could not resolve mismatch:
+ Truth: capiteyn-generael
+ Pred: capiteyn
+[FAIL] Advanced matching misses 1 tags.
+
+
+05_ArchiefZutphen_1572uZutphen.tei.xml -- 34fef94c-57c1-48d6-9551-6df00a3c0162
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_1575fZutphen.tei.xml -- 43811525-9e0e-4111-9956-b6c2d562cee2
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_20juli.tei.xml -- d45a0a0a-0181-4254-8145-9529691b8a29
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_23novembera.tei.xml -- d7b617cc-5947-40b5-9175-390c92d7b212
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_28juli.tei.xml -- 19f6e25f-537e-4a4d-9d47-d32662dc1d81
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_29decembera.tei.xml -- 493122fe-4b98-4241-9033-844e7dab295e
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_apr25a.tei.xml -- 69b16e9f-b987-4004-9eec-484834a2744d
+[SUCCESS] Found all tags with advanced matching.
+
+
+06_DiT-Corpus_1594ara.tei.xml -- eeffd88f-6175-49af-8f5b-6dc93b8a8bb6
+[SUCCESS] Found all tags with advanced matching.
+
+
+06_DiT-Corpus_1594are.tei.xml -- 78f35131-937b-4c79-86e9-8c84ceaddaf7
+[SUCCESS] Found all tags with advanced matching.
+
+
+06_DiT-Corpus_1594arf.tei.xml -- 594e3f82-b6c2-473d-9a3f-464dd0b5224d
+[ERROR] Could not resolve mismatch:
+ Truth: B+B+S+R
+ Pred: B
+[FAIL] Advanced matching misses 1 tags.
+
+
+06_DiT-Corpus_1595ar.tei.xml -- 0ff13fe1-0ccc-4c5f-b1a5-2446e771629b
+[ERROR] Could not resolve mismatch:
+ Truth: zynddat
+ Pred: zynd
+[FAIL] Advanced matching misses 1 tags.
+
+
+06_DiT-Corpus_1596ara.tei.xml -- ac2bfaee-1008-4eee-bf36-81313482d4ba
+[SUCCESS] Found all tags with advanced matching.
+
+
+06_DiT-Corpus_1598ara.tei.xml -- e1b9b51e-1161-4c86-8e67-8bc9ffe51b66
+[ERROR] Could not resolve mismatch:
+ Truth: B.Cannoye
+ Pred: B
+[FAIL] Advanced matching misses 1 tags.
+
+
+06_DiT-Corpus_1598arb.tei.xml -- 6763b79b-60e3-4856-b22a-f31510e25551
+[SUCCESS] Found all tags with advanced matching.
+
+
+06_DiT-Corpus_1599ar.tei.xml -- 8c55df51-86c3-4cb0-8d1e-ad8dafbdb918
+[ERROR] Could not resolve mismatch:
+ Truth: B+B
+ Pred: B
+[FAIL] Advanced matching misses 1 tags.
+
+
+08_HeemkundekringGemert_1501.tei.xml -- 4afdbeea-e2fb-4b28-832e-ae529129efb7
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1509.tei.xml -- f0c0943a-4e45-4db1-85bf-1cd900b4b9dc
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1513.tei.xml -- 4fa404a2-9e2b-4537-a5bc-c70fbbae7ef6
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1541.tei.xml -- 0e2c4d9d-a168-4d9c-b8ed-fd60a5e449bf
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1543.tei.xml -- 1c7d1939-0d3b-403b-aea7-19b631012563
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1547sep.tei.xml -- d381fe9a-7103-4404-aee2-38fc3f1534b2
+[ERROR] Could not resolve mismatch:
+ Truth: u[no]l[atere]
+ Pred: u
+[ERROR] Could not resolve mismatch:
+ Truth: a[lio]
+ Pred: no
+[ERROR] Could not resolve mismatch:
+ Truth: l[atere]
+ Pred: ]
+[ERROR] Could not resolve mismatch:
+ Truth: Jennekens
+ Pred: l
+[ERROR] Could not resolve mismatch:
+ Truth: voerscreven
+ Pred: [
+[ERROR] Could not resolve mismatch:
+ Truth: hebbende
+ Pred: atere
+[ERROR] Could not resolve mismatch:
+ Truth: aenden
+ Pred: ]
+[ERROR] Could not resolve mismatch:
+ Truth: pat
+ Pred: ,
+[ERROR] Could not resolve mismatch:
+ Truth: in
+ Pred: a
+[ERROR] Could not resolve mismatch:
+ Truth: breyde
+ Pred: [
+[ERROR] Could not resolve mismatch:
+ Truth: ander
+ Pred: lio
+[ERROR] Could not resolve mismatch:
+ Truth: halff
+ Pred: ]
+[ERROR] Could not resolve mismatch:
+ Truth: roey
+ Pred: l
+[ERROR] Could not resolve mismatch:
+ Truth: ende
+ Pred: [
+[ERROR] Could not resolve mismatch:
+ Truth: vijftalven
+ Pred: atere
+[ERROR] Could not resolve mismatch:
+ Truth: voet
+ Pred: ]
+[ERROR] Could not resolve mismatch:
+ Truth: ende
+ Pred: Jennekens
+[ERROR] Could not resolve mismatch:
+ Truth: aende
+ Pred: voerscreven
+[ERROR] Could not resolve mismatch:
+ Truth: Alde
+ Pred: hebbende
+[ERROR] Could not resolve mismatch:
+ Truth: Straet
+ Pred: aenden
+[ERROR] Could not resolve mismatch:
+ Truth: derdalff
+ Pred: pat
+[FAIL] Advanced matching misses 21 tags.
+
+
+08_HeemkundekringGemert_1555.tei.xml -- 7a132d35-1df3-49e2-bb8f-d10efc9b2efc
+[ERROR] Could not resolve mismatch:
+ Truth: d'andere
+ Pred: d
+[FAIL] Advanced matching misses 1 tags.
+
+
+12_Seyger-metmetadata_1521Oldenzaalregio.tei.xml -- 12e08234-7728-47fc-9064-9a3e56016038
+[ERROR] Could not resolve mismatch:
+ Truth: Vrouwen-gijlde
+ Pred: Vrouwen
+[ERROR] Could not resolve mismatch:
+ Truth: Vrouwen-gijlde
+ Pred: Vrouwen
+[FAIL] Advanced matching misses 2 tags.
+
+
+12_Seyger-metmetadata_1532bOldenzaalregio.tei.xml -- 15dac08a-e58c-44ed-977e-6a416b4020a7
+[ERROR] Could not resolve mismatch:
+ Truth: Annen-altaer
+ Pred: Annen
+[ERROR] Could not resolve mismatch:
+ Truth: Vrouwen-gijlde
+ Pred: Vrouwen
+[FAIL] Advanced matching misses 2 tags.
+
+
+12_Seyger-metmetadata_1545bOldenzaalregio.tei.xml -- 87ef0b91-014b-48a2-9742-f516cf2f7ac6
+[ERROR] Could not resolve mismatch:
+ Truth: Vrouwen-gylde
+ Pred: Vrouwen
+[ERROR] Could not resolve mismatch:
+ Truth: Vrouwen-gylde
+ Pred: Vrouwen
+[ERROR] Could not resolve mismatch:
+ Truth: Vrouwen-register
+ Pred: Vrouwen
+[ERROR] Could not resolve mismatch:
+ Truth: bewij[singhe]
+ Pred: bewij
+[FAIL] Advanced matching misses 4 tags.
+
+
+12_Seyger-metmetadata_1548aOldenzaalregio.tei.xml -- 371894d5-7cbc-4ff4-ba05-d14303e98d42
+[ERROR] Could not resolve mismatch:
+ Truth: Vrouwen-gijlde
+ Pred: Vrouwen
+[FAIL] Advanced matching misses 1 tags.
+
+
+12_Seyger-metmetadata_1549Oldenzaalregio.tei.xml -- 59d21350-cd7c-401f-bf49-eedc116dcc88
+[SUCCESS] Found all tags with advanced matching.
+
+
+12_Seyger-metmetadata_1550bOldenzaalregio.tei.xml -- 2961107f-44a2-4e5a-9fed-27220e3f8bfd
+[SUCCESS] Found all tags with advanced matching.
+
+
+BRA-BRE-152_25.tei.xml -- c8c734b9-c9c9-4061-84c3-abecbaafd445
+[ERROR] Could not resolve mismatch:
+ Truth: XVC.XXVII
+ Pred: XVC
+[FAIL] Advanced matching misses 1 tags.
+
+
+BRA-BRE-158_30.tei.xml -- 36c08d5e-ae82-4f77-8d09-4282c7e83581
+[ERROR] Could not resolve mismatch:
+ Truth: cnoeckhouders-ambachte
+ Pred: cnoeckhouders
+[FAIL] Advanced matching misses 1 tags.
+
+
+BRA-OIR-153_257.tei.xml -- 84ac1032-1066-4f62-8d92-acc4a2ad70f7
+[ERROR] Could not resolve mismatch:
+ Truth: t's
+ Pred: t
+[ERROR] Could not resolve mismatch:
+ Truth: t'samen
+ Pred: t
+[ERROR] Could not resolve mismatch:
+ Truth: of't
+ Pred: of
+[ERROR] Could not resolve mismatch:
+ Truth: t's
+ Pred: t
+[ERROR] Could not resolve mismatch:
+ Truth: t's
+ Pred: t
+[FAIL] Advanced matching misses 5 tags.
+
+
+FRL-LEE-151_332.tei.xml -- 2cd57d36-e571-4791-ab1d-0f2d98de8b44
+[ERROR] Could not resolve mismatch:
+ Truth: observanten-cloester
+ Pred: observanten
+[ERROR] Could not resolve mismatch:
+ Truth: =Kollum
+ Pred: =
+[ERROR] Could not resolve mismatch:
+ Truth: L-nrs
+ Pred: L
+[FAIL] Advanced matching misses 3 tags.
+
+
+FRL-ZVW-150_354.tei.xml -- ddc22d82-2f0f-4cb1-bf48-06cf12135079
+[ERROR] Could not resolve mismatch:
+ Truth: eest
+ Pred: ee
+[ERROR] Could not resolve mismatch:
+ Truth: =Kolderwolde
+ Pred: =
+[ERROR] Could not resolve mismatch:
+ Truth: [al]s
+ Pred: [
+[FAIL] Advanced matching misses 3 tags.
+
+
+FRL-ZVW-151_355.tei.xml -- c8529065-e4a0-4314-a6a3-09203af5b10a
+[ERROR] Could not resolve mismatch:
+ Truth: pleec[h
+ Pred: pleec
+[FAIL] Advanced matching misses 1 tags.
+
+
+FRL-ZVW-152_356.tei.xml -- e30da0a2-0744-4a90-bc76-6d1b6ee53371
+[SUCCESS] Found all tags with advanced matching.
+
+
+FRL-ZVW-153_357.tei.xml -- cec70de9-1642-438d-b4d0-ccff382f234f
+[SUCCESS] Found all tags with advanced matching.
+
+
+OSL-BRV-154_139.tei.xml -- f8a43337-ba8c-45d5-99e3-6dfa1c1784e8
+[ERROR] Could not resolve mismatch:
+ Truth: dijens[t]
+ Pred: dijens
+[ERROR] Could not resolve mismatch:
+ Truth: hij[er
+ Pred: hij
+[ERROR] Could not resolve mismatch:
+ Truth: t]ho
+ Pred: [
+[FAIL] Advanced matching misses 3 tags.
+
+
+OSL-RLT-158_465.tei.xml -- 96b58a7d-1d96-4522-ad10-4fe730df0ac1
+[ERROR] Could not resolve mismatch:
+ Truth: ge‹nseriert
+ Pred: ge
+[FAIL] Advanced matching misses 1 tags.
+
+
+OVL-BKH-156_441.tei.xml -- c4318a7c-ed79-474c-b1ae-b3e167178deb
+[SUCCESS] Found all tags with advanced matching.
+
+
+OVL-DNM-156_444.tei.xml -- 47b0381d-09a4-4764-85bb-6d6b43ff12b3
+[ERROR] Could not resolve mismatch:
+ Truth: info-file
+ Pred: info
+[FAIL] Advanced matching misses 1 tags.
+
+
+OVL-GNT-150_451.tei.xml -- 324180e8-f1e6-48b7-9490-f306145f8298
+[ERROR] Could not resolve mismatch:
+ Truth: f°65v-67r
+ Pred: f°65v
+[ERROR] Could not resolve mismatch:
+ Truth: N.S
+ Pred: N
+[FAIL] Advanced matching misses 2 tags.
+
+
+OVL-GRB-156_454.tei.xml -- e87aee0a-f060-41a5-99ab-ab19bfb0b553
+[ERROR] Could not resolve mismatch:
+ Truth: luitenant-baljuw
+ Pred: luitenant
+[ERROR] Could not resolve mismatch:
+ Truth: Sint-Maria-Lierde
+ Pred: Sint
+[FAIL] Advanced matching misses 2 tags.
+
+
+OVL-GRB-157_455.tei.xml -- 7237b809-c945-4c93-b020-ce554fd95930
+[ERROR] Could not resolve mismatch:
+ Truth: Mercy-Argenteau
+ Pred: Mercy
+[FAIL] Advanced matching misses 1 tags.
+
+
+OVL-OUD-156_457.tei.xml -- 84c9fd24-8399-4f95-b03d-0794abdc578a
+[ERROR] Could not resolve mismatch:
+ Truth: info-file
+ Pred: info
+[FAIL] Advanced matching misses 1 tags.
+
+
+OVL-RNS-156_458.tei.xml -- 104b7cfc-539f-4adf-9d25-6b7e2ee4db27
+[SUCCESS] Found all tags with advanced matching.
+
+
+VBR-ERP-157_484.tei.xml -- 27efeb21-3915-4b85-baaa-81583aa1b7ea
+[ERROR] Could not resolve mismatch:
+ Truth: Ouder-hove
+ Pred: Ouder
+[ERROR] Could not resolve mismatch:
+ Truth: som-meren
+ Pred: som
+[ERROR] Could not resolve mismatch:
+ Truth: d'aenleggere
+ Pred: d
+[ERROR] Could not resolve mismatch:
+ Truth: hoff-weerdich
+ Pred: hoff
+[ERROR] Could not resolve mismatch:
+ Truth: d'ons
+ Pred: d
+[ERROR] Could not resolve mismatch:
+ Truth: stuy-vers
+ Pred: stuy
+[ERROR] Could not resolve mismatch:
+ Truth: Diede-gem
+ Pred: Diede
+[ERROR] Could not resolve mismatch:
+ Truth: haer-lieden
+ Pred: haer
+[ERROR] Could not resolve mismatch:
+ Truth: ont-erven
+ Pred: ont
+[ERROR] Could not resolve mismatch:
+ Truth: hooft-bancke
+ Pred: hooft
+[ERROR] Could not resolve mismatch:
+ Truth: over-hooren
+ Pred: over
+[ERROR] Could not resolve mismatch:
+ Truth: naer-beschreven
+ Pred: naer
+[ERROR] Could not resolve mismatch:
+ Truth: belee-ninghe
+ Pred: belee
+[ERROR] Could not resolve mismatch:
+ Truth: stadt-houdere
+ Pred: stadt
+[ERROR] Could not resolve mismatch:
+ Truth: t'ontgoeden
+ Pred: t
+[ERROR] Could not resolve mismatch:
+ Truth: t'onder-houden
+ Pred: t
+[ERROR] Could not resolve mismatch:
+ Truth: t'aboleren
+ Pred: t
+[ERROR] Could not resolve mismatch:
+ Truth: t'sedert
+ Pred: t
+[ERROR] Could not resolve mismatch:
+ Truth: stadt-houdere
+ Pred: stadt
+[ERROR] Could not resolve mismatch:
+ Truth: goe-dinghe
+ Pred: goe
+[ERROR] Could not resolve mismatch:
+ Truth: stadt-houdere
+ Pred: stadt
+[ERROR] Could not resolve mismatch:
+ Truth: heerend'erf-laeten
+ Pred: heerend
+[ERROR] Could not resolve mismatch:
+ Truth: thol-kamere
+ Pred: thol
+[ERROR] Could not resolve mismatch:
+ Truth: thol-kamere
+ Pred: thol
+[ERROR] Could not resolve mismatch:
+ Truth: endethol-kamere
+ Pred: endethol
+[ERROR] Could not resolve mismatch:
+ Truth: sche-penen
+ Pred: sche
+[ERROR] Could not resolve mismatch:
+ Truth: hooft-banck
+ Pred: hooft
+[ERROR] Could not resolve mismatch:
+ Truth: wet-houderen
+ Pred: wet
+[ERROR] Could not resolve mismatch:
+ Truth: per-sonele
+ Pred: per
+[ERROR] Could not resolve mismatch:
+ Truth: erf-goeden
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: uyt-ghedinght
+ Pred: uyt
+[ERROR] Could not resolve mismatch:
+ Truth: uyt-ghedaeght
+ Pred: uyt
+[ERROR] Could not resolve mismatch:
+ Truth: vangront-chynsen
+ Pred: vangront
+[ERROR] Could not resolve mismatch:
+ Truth: landt-charteren
+ Pred: landt
+[ERROR] Could not resolve mismatch:
+ Truth: blyde-incompsten
+ Pred: charteren
+[ERROR] Could not resolve mismatch:
+ Truth: erf-laten
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: d'erf-laeten
+ Pred: d
+[ERROR] Could not resolve mismatch:
+ Truth: voor-schreven
+ Pred: voor
+[ERROR] Could not resolve mismatch:
+ Truth: erf-laeten
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: laet-hove
+ Pred: laet
+[ERROR] Could not resolve mismatch:
+ Truth: vanhoef-goedinge
+ Pred: vanhoef
+[ERROR] Could not resolve mismatch:
+ Truth: over-heer
+ Pred: over
+[ERROR] Could not resolve mismatch:
+ Truth: erf-laeten
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: erf-goe-den
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: appel-latien
+ Pred: appel
+[ERROR] Could not resolve mismatch:
+ Truth: erf-goeden
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: hooft-banck
+ Pred: hooft
+[ERROR] Could not resolve mismatch:
+ Truth: d'welck
+ Pred: d
+[FAIL] Advanced matching misses 48 tags.
+
+
+VBR-OVY-157_495.tei.xml -- 9b86423b-4de5-4e5b-b1c2-015f003518a8
+[ERROR] Could not resolve mismatch:
+ Truth: D'ISQUE
+ Pred: D
+[ERROR] Could not resolve mismatch:
+ Truth: onder-teeckent
+ Pred: onder
+[ERROR] Could not resolve mismatch:
+ Truth: lest-leden
+ Pred: lest
+[ERROR] Could not resolve mismatch:
+ Truth: voor-gaenden
+ Pred: voor
+[ERROR] Could not resolve mismatch:
+ Truth: clock-slagh
+ Pred: -
+[ERROR] Could not resolve mismatch:
+ Truth: die-men
+ Pred: die
+[ERROR] Could not resolve mismatch:
+ Truth: al-hier
+ Pred: -
+[ERROR] Could not resolve mismatch:
+ Truth: imme-diaet
+ Pred: imme
+[ERROR] Could not resolve mismatch:
+ Truth: schepen-dom
+ Pred: schepen
+[ERROR] Could not resolve mismatch:
+ Truth: laet-hoven
+ Pred: laet
+[ERROR] Could not resolve mismatch:
+ Truth: voor-schreven
+ Pred: voor
+[ERROR] Could not resolve mismatch:
+ Truth: hun-lieden
+ Pred: hun
+[ERROR] Could not resolve mismatch:
+ Truth: banck-recht
+ Pred: banck
+[ERROR] Could not resolve mismatch:
+ Truth: daer-en-boven
+ Pred: daer
+[ERROR] Could not resolve mismatch:
+ Truth: d'administratie
+ Pred: d
+[ERROR] Could not resolve mismatch:
+ Truth: leen-hof
+ Pred: leen
+[ERROR] Could not resolve mismatch:
+ Truth: erf-goeden
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: twin-tichsten
+ Pred: twin
+[ERROR] Could not resolve mismatch:
+ Truth: pont-ghelt
+ Pred: pont
+[ERROR] Could not resolve mismatch:
+ Truth: dray-boomsgelegen
+ Pred: dray
+[ERROR] Could not resolve mismatch:
+ Truth: dray-booms
+ Pred: dray
+[ERROR] Could not resolve mismatch:
+ Truth: handt-teecken
+ Pred: handt
+[FAIL] Advanced matching misses 22 tags.
+
+
+VBR-SOZ-157_497.tei.xml -- 82c088f8-a7c7-49d2-9672-88c8ea902370
+[ERROR] Could not resolve mismatch:
+ Truth: cance-lier
+ Pred: cance
+[ERROR] Could not resolve mismatch:
+ Truth: in't
+ Pred: i
+[ERROR] Could not resolve mismatch:
+ Truth: d'inhouden
+ Pred: d
+[ERROR] Could not resolve mismatch:
+ Truth: hooft-banck
+ Pred: hooft
+[ERROR] Could not resolve mismatch:
+ Truth: erf-goeden
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: compe-terende
+ Pred: compe
+[ERROR] Could not resolve mismatch:
+ Truth: erf-goeden
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: erf-goeden
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: uyt-gewonnen
+ Pred: uyt
+[ERROR] Could not resolve mismatch:
+ Truth: gront-chynsen
+ Pred: gront
+[ERROR] Could not resolve mismatch:
+ Truth: evinceert-men
+ Pred: -
+[ERROR] Could not resolve mismatch:
+ Truth: mets'dinghs
+ Pred: evinceert
+[ERROR] Could not resolve mismatch:
+ Truth: voor-schreven
+ Pred: voor
+[ERROR] Could not resolve mismatch:
+ Truth: erf-goeden
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: belee-ninghe
+ Pred: belee
+[ERROR] Could not resolve mismatch:
+ Truth: thol-camere
+ Pred: thol
+[ERROR] Could not resolve mismatch:
+ Truth: erf-laeten
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: thol-camere
+ Pred: thol
+[ERROR] Could not resolve mismatch:
+ Truth: ghena-dichs
+ Pred: ghena
+[ERROR] Could not resolve mismatch:
+ Truth: over-midts
+ Pred: over
+[ERROR] Could not resolve mismatch:
+ Truth: lant-charteren
+ Pred: lant
+[ERROR] Could not resolve mismatch:
+ Truth: plac-caerte
+ Pred: plac
+[ERROR] Could not resolve mismatch:
+ Truth: gheswo-ren
+ Pred: gheswo
+[ERROR] Could not resolve mismatch:
+ Truth: ende't
+ Pred: ende
+[ERROR] Could not resolve mismatch:
+ Truth: ghe-daenhebbe
+ Pred: ghe
+[FAIL] Advanced matching misses 25 tags.
+
+
+WVL-BRG-153_178.tei.xml -- 55d589f7-b9b9-4c5a-a82e-f5c6ed248fe9
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-KRT-156_518.tei.xml -- 4de000d7-2f0b-4bb4-9c1a-4b8da0fc0a17
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-MEN-156_522.tei.xml -- 9ea0bee6-48dd-46ab-9f24-30949a89a76d
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-NWP-156_524.tei.xml -- 203455c8-e545-4f50-aebd-97178a954b09
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-OST-156_532.tei.xml -- 2a6dc163-9292-450a-92f6-5cedba4a6513
+[ERROR] Could not resolve mismatch:
+ Truth: info-file
+ Pred: info
+[FAIL] Advanced matching misses 1 tags.
+
+
+WVL-VDH-157_534.tei.xml -- 06525725-1796-4e46-976b-ce378df2c547
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-VRN-156_535.tei.xml -- 3a5afaf6-cb17-440a-9f49-9cb98eb54c64
+[ERROR] Could not resolve mismatch:
+ Truth: VEURNE-AMBACHT
+ Pred: VEURNE
+[ERROR] Could not resolve mismatch:
+ Truth: VEURNE-AMBACHT
+ Pred: VEURNE
+[ERROR] Could not resolve mismatch:
+ Truth: Veurne-Ambacht
+ Pred: Veurne
+[ERROR] Could not resolve mismatch:
+ Truth: info-file
+ Pred: info
+[ERROR] Could not resolve mismatch:
+ Truth: info-file
+ Pred: info
+[FAIL] Advanced matching misses 5 tags.
+
+
+WVL-WVK-156_536.tei.xml -- 890b851f-0ce7-4e8c-a5cb-3fcc4946e767
+[ERROR] Could not resolve mismatch:
+ Truth: Mercy-Argenteau
+ Pred: Mercy
+[ERROR] Could not resolve mismatch:
+ Truth: f°188r-188v
+ Pred: f°188r
+[FAIL] Advanced matching misses 2 tags.
+
+
+ZHL-LEI-156_386.tei.xml -- 58ae1cf8-ea29-497b-bab3-a8e97fc89ca5
+[SUCCESS] Found all tags with advanced matching.
+
+
+ZHL-SDM-158_561.tei.xml -- 03fef532-1276-4a1a-8c26-6c32b7fb8861
+[SUCCESS] Found all tags with advanced matching.
+
+
+Summary:
+ Simple matches: 19
+ Advanced matches: 44
+ Total files: 89
diff --git a/scripts/tag-matching/CLVN-hug-2024-1-19.txt b/scripts/tag-matching/CLVN-hug-2024-1-19.txt
new file mode 100644
index 0000000..1471d7f
--- /dev/null
+++ b/scripts/tag-matching/CLVN-hug-2024-1-19.txt
@@ -0,0 +1,930 @@
+01_Archief_Delfland_1531gDelfland.tei.xml -- 6da1b4cd-cc83-45a1-9c4a-f81441271689
+[SUCCESS] Found all tags with advanced matching.
+
+
+01_Archief_Delfland_1532jDelfland.tei.xml -- e01f38ff-bc1c-4afe-97fb-c7fd9d71cc3c
+[SUCCESS] Found all tags with advanced matching.
+
+
+02_Archief_Elburg_1580aElburg.tei.xml -- 080ab7de-3f9a-4f79-9a0b-7fd0ab4a6274
+[ERROR] Could not resolve mismatch:
+ Truth: H..t
+ Pred: H
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_ALKMAAR_10-4-1573_TS.tei.xml -- 0abb6244-876f-459c-8b27-013a57b61df8
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Alkmaar_10-6-1575_TS.tei.xml -- 6e6de06f-df3e-4e97-a6b1-6d641ab8de6b
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t.
+ Pred: s
+[ERROR] Could not resolve mismatch:
+ Truth: Ordre
+ Pred: L
+[ERROR] Could not resolve mismatch:
+ Truth: van
+ Pred: .
+[ERROR] Could not resolve mismatch:
+ Truth: Senoy
+ Pred: Coren
+[ERROR] Could not resolve mismatch:
+ Truth: gouverneur
+ Pred: s
+[FAIL] Advanced matching misses 5 tags.
+
+
+03_ArchiefAlkmaar_Alkmaar_16-2-1574_TS.tei.xml -- 00176f3c-9ac2-443c-a0ef-c4d55e77a8ad
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t.
+ Pred: s
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Alkmaar_8-2-1574_TS.tei.xml -- 5a582f5c-ac0b-4531-8148-9207123a9b06
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t
+ Pred: s
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Delft_18-6-1573_TS.tei.xml -- d9a75dc7-1f29-4b8c-b1f5-c2b46e96d762
+[ERROR] Could not resolve mismatch:
+ Truth: [v]rienden
+ Pred: [
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Delft_24-3-1573_TS.tei.xml -- 966eb985-2519-4005-ae04-5ca873d07598
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t
+ Pred: s
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Den_Haag_15-9-1572_TS.tei.xml -- a5fb4752-9bf7-4a53-bf1f-15193b986ec0
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Den_Haag_2-10-1586_TS.tei.xml -- 3d82bbca-c0c8-487d-aed4-890366549878
+[ERROR] Could not resolve mismatch:
+ Truth: d'onkosten
+ Pred: d
+[ERROR] Could not resolve mismatch:
+ Truth: d'oncosten
+ Pred: d
+[FAIL] Advanced matching misses 2 tags.
+
+
+03_ArchiefAlkmaar_Diemerdijk_7-6-1573_TS.tei.xml -- f6fb158a-7a8e-40bb-a389-354cf6c073b1
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Dordrecht_23-8-1573_TS.tei.xml -- 1db709e8-40d7-436e-b2dc-d119797b01e1
+[ERROR] Could not resolve mismatch:
+ Truth: u[lieden]
+ Pred: u
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t
+ Pred: s
+[ERROR] Could not resolve mismatch:
+ Truth: [sc]hepenen
+ Pred: [
+[FAIL] Advanced matching misses 3 tags.
+
+
+03_ArchiefAlkmaar_Edam_15-9-1573_TS.tei.xml -- af78dcc3-6273-45c2-ad6b-3a0489f2717a
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Enkhuizen_5-4-1574_TS_b.tei.xml -- 61ee3744-69d2-4d5b-ab31-411742f049e9
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Enkhuizen__27-10-1572_TS.tei.xml -- ae4722c1-82fc-4dad-8301-40c57180196c
+[ERROR] Could not resolve mismatch:
+ Truth: [ci]to
+ Pred: [
+[ERROR] Could not resolve mismatch:
+ Truth: [ci]to
+ Pred: ci
+[FAIL] Advanced matching misses 2 tags.
+
+
+03_ArchiefAlkmaar_Hoorn_17-8-1573_TS.tei.xml -- 63d6d61f-0322-40d3-8a17-c11cfb95f12c
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t
+ Pred: s
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Hoorn_8-3-1573_TS.tei.xml -- b23d1e66-d8e5-4039-9e20-3cde43430b93
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Leiden_13-7-1573_TS_b.tei.xml -- a40a7b23-1bf3-46f7-bb1c-f671e90c1657
+[ERROR] Could not resolve mismatch:
+ Truth: N.Brunynck
+ Pred: N
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Leiden_16-6-1573_TS.tei.xml -- b1bb7282-6b16-49d8-8d9c-e0793a0163a2
+[ERROR] Could not resolve mismatch:
+ Truth: N.Brunynck
+ Pred: N
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t
+ Pred: .
+[FAIL] Advanced matching misses 2 tags.
+
+
+03_ArchiefAlkmaar_Middelburg_15-3-1574_TS.tei.xml -- cf1b73d5-cb24-4473-90d6-b603e216123f
+[ERROR] Could not resolve mismatch:
+ Truth: [c]ito
+ Pred: .
+[ERROR] Could not resolve mismatch:
+ Truth: [c]ito
+ Pred: [
+[ERROR] Could not resolve mismatch:
+ Truth: [m]ijne
+ Pred: [
+[FAIL] Advanced matching misses 3 tags.
+
+
+03_ArchiefAlkmaar_Purmerend_2-5-1574_TS.tei.xml -- ffb5c177-8589-4632-af14-a9bb8f695eb8
+[ERROR] Could not resolve mismatch:
+ Truth: Gunsty[ge]
+ Pred: Gunsty
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Purmerend_4-6-1574_TS.tei.xml -- c6611cf7-368e-4cf5-a643-94d1ecb4f22b
+[SUCCESS] Found all tags with advanced matching.
+
+
+04_ArchiefEnschede_1596eEnschede.tei.xml -- a436f3d0-1203-4140-bf96-b155c6850ed5
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_12maarta.tei.xml -- ba0db231-1e82-474b-8a9a-6b9132dda26e
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_12maartb.tei.xml -- 78eafa2f-c636-44f7-8b46-520dee8ce338
+[ERROR] Could not resolve mismatch:
+ Truth: J.M.W
+ Pred: J
+[FAIL] Advanced matching misses 1 tags.
+
+
+05_ArchiefZutphen_1552Zua.tei.xml -- 106757d0-fd5e-434b-b163-ac190fababf4
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_1571le.tei.xml -- 213e8a4e-527e-4d1c-ae61-64ac85db0b34
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_1571zur.tei.xml -- 6aaa3c61-403d-4c60-b5fd-ee5409c735c8
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_1572eZutphen.tei.xml -- 93730ee4-0c6c-48d2-abc9-e168d9045090
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_1572iiZutphen.tei.xml -- 4b2956a0-c8ad-4894-af69-fd00ba08cd17
+[ERROR] Could not resolve mismatch:
+ Truth: capiteyn-generael
+ Pred: capiteyn
+[FAIL] Advanced matching misses 1 tags.
+
+
+05_ArchiefZutphen_1572uZutphen.tei.xml -- 34fef94c-57c1-48d6-9551-6df00a3c0162
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_1575fZutphen.tei.xml -- 43811525-9e0e-4111-9956-b6c2d562cee2
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_20juli.tei.xml -- d45a0a0a-0181-4254-8145-9529691b8a29
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_23novembera.tei.xml -- d7b617cc-5947-40b5-9175-390c92d7b212
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_28juli.tei.xml -- 19f6e25f-537e-4a4d-9d47-d32662dc1d81
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_29decembera.tei.xml -- 493122fe-4b98-4241-9033-844e7dab295e
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_apr25a.tei.xml -- 69b16e9f-b987-4004-9eec-484834a2744d
+[SUCCESS] Found all tags with advanced matching.
+
+
+06_DiT-Corpus_1594ara.tei.xml -- eeffd88f-6175-49af-8f5b-6dc93b8a8bb6
+[SUCCESS] Found all tags with advanced matching.
+
+
+06_DiT-Corpus_1594are.tei.xml -- 78f35131-937b-4c79-86e9-8c84ceaddaf7
+[SUCCESS] Found all tags with advanced matching.
+
+
+06_DiT-Corpus_1594arf.tei.xml -- 594e3f82-b6c2-473d-9a3f-464dd0b5224d
+[ERROR] Could not resolve mismatch:
+ Truth: B+B+S+R
+ Pred: B
+[FAIL] Advanced matching misses 1 tags.
+
+
+06_DiT-Corpus_1595ar.tei.xml -- 0ff13fe1-0ccc-4c5f-b1a5-2446e771629b
+[ERROR] Could not resolve mismatch:
+ Truth: zynddat
+ Pred: zynd
+[FAIL] Advanced matching misses 1 tags.
+
+
+06_DiT-Corpus_1596ara.tei.xml -- ac2bfaee-1008-4eee-bf36-81313482d4ba
+[SUCCESS] Found all tags with advanced matching.
+
+
+06_DiT-Corpus_1598ara.tei.xml -- e1b9b51e-1161-4c86-8e67-8bc9ffe51b66
+[ERROR] Could not resolve mismatch:
+ Truth: B.Cannoye
+ Pred: B
+[FAIL] Advanced matching misses 1 tags.
+
+
+06_DiT-Corpus_1598arb.tei.xml -- 6763b79b-60e3-4856-b22a-f31510e25551
+[SUCCESS] Found all tags with advanced matching.
+
+
+06_DiT-Corpus_1599ar.tei.xml -- 8c55df51-86c3-4cb0-8d1e-ad8dafbdb918
+[ERROR] Could not resolve mismatch:
+ Truth: B+B
+ Pred: B
+[FAIL] Advanced matching misses 1 tags.
+
+
+08_HeemkundekringGemert_1501.tei.xml -- 4afdbeea-e2fb-4b28-832e-ae529129efb7
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1509.tei.xml -- f0c0943a-4e45-4db1-85bf-1cd900b4b9dc
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1513.tei.xml -- 4fa404a2-9e2b-4537-a5bc-c70fbbae7ef6
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1541.tei.xml -- 0e2c4d9d-a168-4d9c-b8ed-fd60a5e449bf
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1543.tei.xml -- 1c7d1939-0d3b-403b-aea7-19b631012563
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1547sep.tei.xml -- d381fe9a-7103-4404-aee2-38fc3f1534b2
+[ERROR] Could not resolve mismatch:
+ Truth: u[no]l[atere]
+ Pred: u
+[ERROR] Could not resolve mismatch:
+ Truth: a[lio]
+ Pred: no
+[ERROR] Could not resolve mismatch:
+ Truth: l[atere]
+ Pred: ]
+[ERROR] Could not resolve mismatch:
+ Truth: Jennekens
+ Pred: l
+[ERROR] Could not resolve mismatch:
+ Truth: voerscreven
+ Pred: [
+[ERROR] Could not resolve mismatch:
+ Truth: hebbende
+ Pred: atere
+[ERROR] Could not resolve mismatch:
+ Truth: aenden
+ Pred: ]
+[ERROR] Could not resolve mismatch:
+ Truth: pat
+ Pred: ,
+[ERROR] Could not resolve mismatch:
+ Truth: in
+ Pred: a
+[ERROR] Could not resolve mismatch:
+ Truth: breyde
+ Pred: [
+[ERROR] Could not resolve mismatch:
+ Truth: ander
+ Pred: lio
+[ERROR] Could not resolve mismatch:
+ Truth: halff
+ Pred: ]
+[ERROR] Could not resolve mismatch:
+ Truth: roey
+ Pred: l
+[ERROR] Could not resolve mismatch:
+ Truth: ende
+ Pred: [
+[ERROR] Could not resolve mismatch:
+ Truth: vijftalven
+ Pred: atere
+[ERROR] Could not resolve mismatch:
+ Truth: voet
+ Pred: ]
+[ERROR] Could not resolve mismatch:
+ Truth: ende
+ Pred: Jennekens
+[ERROR] Could not resolve mismatch:
+ Truth: aende
+ Pred: voerscreven
+[ERROR] Could not resolve mismatch:
+ Truth: Alde
+ Pred: hebbende
+[ERROR] Could not resolve mismatch:
+ Truth: Straet
+ Pred: aenden
+[ERROR] Could not resolve mismatch:
+ Truth: derdalff
+ Pred: pat
+[FAIL] Advanced matching misses 21 tags.
+
+
+08_HeemkundekringGemert_1555.tei.xml -- 7a132d35-1df3-49e2-bb8f-d10efc9b2efc
+[ERROR] Could not resolve mismatch:
+ Truth: d'andere
+ Pred: d
+[FAIL] Advanced matching misses 1 tags.
+
+
+12_Seyger-metmetadata_1521Oldenzaalregio.tei.xml -- 12e08234-7728-47fc-9064-9a3e56016038
+[ERROR] Could not resolve mismatch:
+ Truth: Vrouwen-gijlde
+ Pred: Vrouwen
+[ERROR] Could not resolve mismatch:
+ Truth: Vrouwen-gijlde
+ Pred: Vrouwen
+[FAIL] Advanced matching misses 2 tags.
+
+
+12_Seyger-metmetadata_1532bOldenzaalregio.tei.xml -- 15dac08a-e58c-44ed-977e-6a416b4020a7
+[ERROR] Could not resolve mismatch:
+ Truth: Annen-altaer
+ Pred: Annen
+[ERROR] Could not resolve mismatch:
+ Truth: Vrouwen-gijlde
+ Pred: Vrouwen
+[FAIL] Advanced matching misses 2 tags.
+
+
+12_Seyger-metmetadata_1545bOldenzaalregio.tei.xml -- 87ef0b91-014b-48a2-9742-f516cf2f7ac6
+[ERROR] Could not resolve mismatch:
+ Truth: Vrouwen-gylde
+ Pred: Vrouwen
+[ERROR] Could not resolve mismatch:
+ Truth: Vrouwen-gylde
+ Pred: Vrouwen
+[ERROR] Could not resolve mismatch:
+ Truth: Vrouwen-register
+ Pred: Vrouwen
+[ERROR] Could not resolve mismatch:
+ Truth: bewij[singhe]
+ Pred: bewij
+[FAIL] Advanced matching misses 4 tags.
+
+
+12_Seyger-metmetadata_1548aOldenzaalregio.tei.xml -- 371894d5-7cbc-4ff4-ba05-d14303e98d42
+[ERROR] Could not resolve mismatch:
+ Truth: Vrouwen-gijlde
+ Pred: Vrouwen
+[FAIL] Advanced matching misses 1 tags.
+
+
+12_Seyger-metmetadata_1549Oldenzaalregio.tei.xml -- 59d21350-cd7c-401f-bf49-eedc116dcc88
+[SUCCESS] Found all tags with advanced matching.
+
+
+12_Seyger-metmetadata_1550bOldenzaalregio.tei.xml -- 2961107f-44a2-4e5a-9fed-27220e3f8bfd
+[SUCCESS] Found all tags with advanced matching.
+
+
+BRA-BRE-152_25.tei.xml -- c8c734b9-c9c9-4061-84c3-abecbaafd445
+[ERROR] Could not resolve mismatch:
+ Truth: XVC.XXVII
+ Pred: XVC
+[FAIL] Advanced matching misses 1 tags.
+
+
+BRA-BRE-158_30.tei.xml -- 36c08d5e-ae82-4f77-8d09-4282c7e83581
+[ERROR] Could not resolve mismatch:
+ Truth: cnoeckhouders-ambachte
+ Pred: cnoeckhouders
+[FAIL] Advanced matching misses 1 tags.
+
+
+BRA-OIR-153_257.tei.xml -- 84ac1032-1066-4f62-8d92-acc4a2ad70f7
+[ERROR] Could not resolve mismatch:
+ Truth: t's
+ Pred: t
+[ERROR] Could not resolve mismatch:
+ Truth: t'samen
+ Pred: t
+[ERROR] Could not resolve mismatch:
+ Truth: of't
+ Pred: of
+[ERROR] Could not resolve mismatch:
+ Truth: t's
+ Pred: t
+[ERROR] Could not resolve mismatch:
+ Truth: t's
+ Pred: t
+[FAIL] Advanced matching misses 5 tags.
+
+
+FRL-LEE-151_332.tei.xml -- 2cd57d36-e571-4791-ab1d-0f2d98de8b44
+[ERROR] Could not resolve mismatch:
+ Truth: observanten-cloester
+ Pred: observanten
+[ERROR] Could not resolve mismatch:
+ Truth: =Kollum
+ Pred: =
+[ERROR] Could not resolve mismatch:
+ Truth: L-nrs
+ Pred: L
+[FAIL] Advanced matching misses 3 tags.
+
+
+FRL-ZVW-150_354.tei.xml -- ddc22d82-2f0f-4cb1-bf48-06cf12135079
+[ERROR] Could not resolve mismatch:
+ Truth: eest
+ Pred: ee
+[ERROR] Could not resolve mismatch:
+ Truth: =Kolderwolde
+ Pred: =
+[ERROR] Could not resolve mismatch:
+ Truth: [al]s
+ Pred: [
+[FAIL] Advanced matching misses 3 tags.
+
+
+FRL-ZVW-151_355.tei.xml -- c8529065-e4a0-4314-a6a3-09203af5b10a
+[ERROR] Could not resolve mismatch:
+ Truth: pleec[h
+ Pred: pleec
+[FAIL] Advanced matching misses 1 tags.
+
+
+FRL-ZVW-152_356.tei.xml -- e30da0a2-0744-4a90-bc76-6d1b6ee53371
+[SUCCESS] Found all tags with advanced matching.
+
+
+FRL-ZVW-153_357.tei.xml -- cec70de9-1642-438d-b4d0-ccff382f234f
+[SUCCESS] Found all tags with advanced matching.
+
+
+OSL-BRV-154_139.tei.xml -- f8a43337-ba8c-45d5-99e3-6dfa1c1784e8
+[ERROR] Could not resolve mismatch:
+ Truth: dijens[t]
+ Pred: dijens
+[ERROR] Could not resolve mismatch:
+ Truth: hij[er
+ Pred: hij
+[ERROR] Could not resolve mismatch:
+ Truth: t]ho
+ Pred: [
+[FAIL] Advanced matching misses 3 tags.
+
+
+OSL-RLT-158_465.tei.xml -- 96b58a7d-1d96-4522-ad10-4fe730df0ac1
+[ERROR] Could not resolve mismatch:
+ Truth: ge‹nseriert
+ Pred: ge
+[FAIL] Advanced matching misses 1 tags.
+
+
+OVL-BKH-156_441.tei.xml -- c4318a7c-ed79-474c-b1ae-b3e167178deb
+[SUCCESS] Found all tags with advanced matching.
+
+
+OVL-DNM-156_444.tei.xml -- 47b0381d-09a4-4764-85bb-6d6b43ff12b3
+[ERROR] Could not resolve mismatch:
+ Truth: info-file
+ Pred: info
+[FAIL] Advanced matching misses 1 tags.
+
+
+OVL-GNT-150_451.tei.xml -- 324180e8-f1e6-48b7-9490-f306145f8298
+[ERROR] Could not resolve mismatch:
+ Truth: f°65v-67r
+ Pred: f°65v
+[ERROR] Could not resolve mismatch:
+ Truth: N.S
+ Pred: N
+[FAIL] Advanced matching misses 2 tags.
+
+
+OVL-GRB-156_454.tei.xml -- e87aee0a-f060-41a5-99ab-ab19bfb0b553
+[ERROR] Could not resolve mismatch:
+ Truth: luitenant-baljuw
+ Pred: luitenant
+[ERROR] Could not resolve mismatch:
+ Truth: Sint-Maria-Lierde
+ Pred: Sint
+[FAIL] Advanced matching misses 2 tags.
+
+
+OVL-GRB-157_455.tei.xml -- 7237b809-c945-4c93-b020-ce554fd95930
+[ERROR] Could not resolve mismatch:
+ Truth: Mercy-Argenteau
+ Pred: Mercy
+[FAIL] Advanced matching misses 1 tags.
+
+
+OVL-OUD-156_457.tei.xml -- 84c9fd24-8399-4f95-b03d-0794abdc578a
+[ERROR] Could not resolve mismatch:
+ Truth: info-file
+ Pred: info
+[FAIL] Advanced matching misses 1 tags.
+
+
+OVL-RNS-156_458.tei.xml -- 104b7cfc-539f-4adf-9d25-6b7e2ee4db27
+[SUCCESS] Found all tags with advanced matching.
+
+
+VBR-ERP-157_484.tei.xml -- 27efeb21-3915-4b85-baaa-81583aa1b7ea
+[ERROR] Could not resolve mismatch:
+ Truth: Ouder-hove
+ Pred: Ouder
+[ERROR] Could not resolve mismatch:
+ Truth: som-meren
+ Pred: som
+[ERROR] Could not resolve mismatch:
+ Truth: d'aenleggere
+ Pred: d
+[ERROR] Could not resolve mismatch:
+ Truth: hoff-weerdich
+ Pred: hoff
+[ERROR] Could not resolve mismatch:
+ Truth: d'ons
+ Pred: d
+[ERROR] Could not resolve mismatch:
+ Truth: stuy-vers
+ Pred: stuy
+[ERROR] Could not resolve mismatch:
+ Truth: Diede-gem
+ Pred: Diede
+[ERROR] Could not resolve mismatch:
+ Truth: haer-lieden
+ Pred: haer
+[ERROR] Could not resolve mismatch:
+ Truth: ont-erven
+ Pred: ont
+[ERROR] Could not resolve mismatch:
+ Truth: hooft-bancke
+ Pred: hooft
+[ERROR] Could not resolve mismatch:
+ Truth: over-hooren
+ Pred: over
+[ERROR] Could not resolve mismatch:
+ Truth: naer-beschreven
+ Pred: naer
+[ERROR] Could not resolve mismatch:
+ Truth: belee-ninghe
+ Pred: belee
+[ERROR] Could not resolve mismatch:
+ Truth: stadt-houdere
+ Pred: stadt
+[ERROR] Could not resolve mismatch:
+ Truth: t'ontgoeden
+ Pred: t
+[ERROR] Could not resolve mismatch:
+ Truth: t'onder-houden
+ Pred: t
+[ERROR] Could not resolve mismatch:
+ Truth: t'aboleren
+ Pred: t
+[ERROR] Could not resolve mismatch:
+ Truth: t'sedert
+ Pred: t
+[ERROR] Could not resolve mismatch:
+ Truth: stadt-houdere
+ Pred: stadt
+[ERROR] Could not resolve mismatch:
+ Truth: goe-dinghe
+ Pred: goe
+[ERROR] Could not resolve mismatch:
+ Truth: stadt-houdere
+ Pred: stadt
+[ERROR] Could not resolve mismatch:
+ Truth: heerend'erf-laeten
+ Pred: heerend
+[ERROR] Could not resolve mismatch:
+ Truth: thol-kamere
+ Pred: thol
+[ERROR] Could not resolve mismatch:
+ Truth: thol-kamere
+ Pred: thol
+[ERROR] Could not resolve mismatch:
+ Truth: endethol-kamere
+ Pred: endethol
+[ERROR] Could not resolve mismatch:
+ Truth: sche-penen
+ Pred: sche
+[ERROR] Could not resolve mismatch:
+ Truth: hooft-banck
+ Pred: hooft
+[ERROR] Could not resolve mismatch:
+ Truth: wet-houderen
+ Pred: wet
+[ERROR] Could not resolve mismatch:
+ Truth: per-sonele
+ Pred: per
+[ERROR] Could not resolve mismatch:
+ Truth: erf-goeden
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: uyt-ghedinght
+ Pred: uyt
+[ERROR] Could not resolve mismatch:
+ Truth: uyt-ghedaeght
+ Pred: uyt
+[ERROR] Could not resolve mismatch:
+ Truth: vangront-chynsen
+ Pred: vangront
+[ERROR] Could not resolve mismatch:
+ Truth: landt-charteren
+ Pred: landt
+[ERROR] Could not resolve mismatch:
+ Truth: blyde-incompsten
+ Pred: charteren
+[ERROR] Could not resolve mismatch:
+ Truth: erf-laten
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: d'erf-laeten
+ Pred: d
+[ERROR] Could not resolve mismatch:
+ Truth: voor-schreven
+ Pred: voor
+[ERROR] Could not resolve mismatch:
+ Truth: erf-laeten
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: laet-hove
+ Pred: laet
+[ERROR] Could not resolve mismatch:
+ Truth: vanhoef-goedinge
+ Pred: vanhoef
+[ERROR] Could not resolve mismatch:
+ Truth: over-heer
+ Pred: over
+[ERROR] Could not resolve mismatch:
+ Truth: erf-laeten
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: erf-goe-den
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: appel-latien
+ Pred: appel
+[ERROR] Could not resolve mismatch:
+ Truth: erf-goeden
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: hooft-banck
+ Pred: hooft
+[ERROR] Could not resolve mismatch:
+ Truth: d'welck
+ Pred: d
+[FAIL] Advanced matching misses 48 tags.
+
+
+VBR-OVY-157_495.tei.xml -- 9b86423b-4de5-4e5b-b1c2-015f003518a8
+[ERROR] Could not resolve mismatch:
+ Truth: D'ISQUE
+ Pred: D
+[ERROR] Could not resolve mismatch:
+ Truth: onder-teeckent
+ Pred: onder
+[ERROR] Could not resolve mismatch:
+ Truth: lest-leden
+ Pred: lest
+[ERROR] Could not resolve mismatch:
+ Truth: voor-gaenden
+ Pred: voor
+[ERROR] Could not resolve mismatch:
+ Truth: clock-slagh
+ Pred: -
+[ERROR] Could not resolve mismatch:
+ Truth: die-men
+ Pred: die
+[ERROR] Could not resolve mismatch:
+ Truth: al-hier
+ Pred: -
+[ERROR] Could not resolve mismatch:
+ Truth: imme-diaet
+ Pred: imme
+[ERROR] Could not resolve mismatch:
+ Truth: schepen-dom
+ Pred: schepen
+[ERROR] Could not resolve mismatch:
+ Truth: laet-hoven
+ Pred: laet
+[ERROR] Could not resolve mismatch:
+ Truth: voor-schreven
+ Pred: voor
+[ERROR] Could not resolve mismatch:
+ Truth: hun-lieden
+ Pred: hun
+[ERROR] Could not resolve mismatch:
+ Truth: banck-recht
+ Pred: banck
+[ERROR] Could not resolve mismatch:
+ Truth: daer-en-boven
+ Pred: daer
+[ERROR] Could not resolve mismatch:
+ Truth: d'administratie
+ Pred: d
+[ERROR] Could not resolve mismatch:
+ Truth: leen-hof
+ Pred: leen
+[ERROR] Could not resolve mismatch:
+ Truth: erf-goeden
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: twin-tichsten
+ Pred: twin
+[ERROR] Could not resolve mismatch:
+ Truth: pont-ghelt
+ Pred: pont
+[ERROR] Could not resolve mismatch:
+ Truth: dray-boomsgelegen
+ Pred: dray
+[ERROR] Could not resolve mismatch:
+ Truth: dray-booms
+ Pred: dray
+[ERROR] Could not resolve mismatch:
+ Truth: handt-teecken
+ Pred: handt
+[FAIL] Advanced matching misses 22 tags.
+
+
+VBR-SOZ-157_497.tei.xml -- 82c088f8-a7c7-49d2-9672-88c8ea902370
+[ERROR] Could not resolve mismatch:
+ Truth: cance-lier
+ Pred: cance
+[ERROR] Could not resolve mismatch:
+ Truth: in't
+ Pred: in
+[ERROR] Could not resolve mismatch:
+ Truth: d'inhouden
+ Pred: d
+[ERROR] Could not resolve mismatch:
+ Truth: hooft-banck
+ Pred: hooft
+[ERROR] Could not resolve mismatch:
+ Truth: erf-goeden
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: compe-terende
+ Pred: compe
+[ERROR] Could not resolve mismatch:
+ Truth: erf-goeden
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: erf-goeden
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: uyt-gewonnen
+ Pred: uyt
+[ERROR] Could not resolve mismatch:
+ Truth: gront-chynsen
+ Pred: gront
+[ERROR] Could not resolve mismatch:
+ Truth: evinceert-men
+ Pred: -
+[ERROR] Could not resolve mismatch:
+ Truth: mets'dinghs
+ Pred: evinceert
+[ERROR] Could not resolve mismatch:
+ Truth: voor-schreven
+ Pred: voor
+[ERROR] Could not resolve mismatch:
+ Truth: erf-goeden
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: belee-ninghe
+ Pred: belee
+[ERROR] Could not resolve mismatch:
+ Truth: thol-camere
+ Pred: thol
+[ERROR] Could not resolve mismatch:
+ Truth: erf-laeten
+ Pred: erf
+[ERROR] Could not resolve mismatch:
+ Truth: thol-camere
+ Pred: thol
+[ERROR] Could not resolve mismatch:
+ Truth: ghena-dichs
+ Pred: ghena
+[ERROR] Could not resolve mismatch:
+ Truth: over-midts
+ Pred: over
+[ERROR] Could not resolve mismatch:
+ Truth: lant-charteren
+ Pred: lant
+[ERROR] Could not resolve mismatch:
+ Truth: plac-caerte
+ Pred: plac
+[ERROR] Could not resolve mismatch:
+ Truth: gheswo-ren
+ Pred: gheswo
+[ERROR] Could not resolve mismatch:
+ Truth: ende't
+ Pred: ende
+[ERROR] Could not resolve mismatch:
+ Truth: ghe-daenhebbe
+ Pred: ghe
+[FAIL] Advanced matching misses 25 tags.
+
+
+WVL-BRG-153_178.tei.xml -- 55d589f7-b9b9-4c5a-a82e-f5c6ed248fe9
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-KRT-156_518.tei.xml -- 4de000d7-2f0b-4bb4-9c1a-4b8da0fc0a17
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-MEN-156_522.tei.xml -- 9ea0bee6-48dd-46ab-9f24-30949a89a76d
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-NWP-156_524.tei.xml -- 203455c8-e545-4f50-aebd-97178a954b09
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-OST-156_532.tei.xml -- 2a6dc163-9292-450a-92f6-5cedba4a6513
+[ERROR] Could not resolve mismatch:
+ Truth: info-file
+ Pred: info
+[FAIL] Advanced matching misses 1 tags.
+
+
+WVL-VDH-157_534.tei.xml -- 06525725-1796-4e46-976b-ce378df2c547
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-VRN-156_535.tei.xml -- 3a5afaf6-cb17-440a-9f49-9cb98eb54c64
+[ERROR] Could not resolve mismatch:
+ Truth: VEURNE-AMBACHT
+ Pred: VEURNE
+[ERROR] Could not resolve mismatch:
+ Truth: VEURNE-AMBACHT
+ Pred: VEURNE
+[ERROR] Could not resolve mismatch:
+ Truth: Veurne-Ambacht
+ Pred: Veurne
+[ERROR] Could not resolve mismatch:
+ Truth: info-file
+ Pred: info
+[ERROR] Could not resolve mismatch:
+ Truth: info-file
+ Pred: info
+[FAIL] Advanced matching misses 5 tags.
+
+
+WVL-WVK-156_536.tei.xml -- 890b851f-0ce7-4e8c-a5cb-3fcc4946e767
+[ERROR] Could not resolve mismatch:
+ Truth: Mercy-Argenteau
+ Pred: Mercy
+[ERROR] Could not resolve mismatch:
+ Truth: f°188r-188v
+ Pred: f°188r
+[FAIL] Advanced matching misses 2 tags.
+
+
+ZHL-LEI-156_386.tei.xml -- 58ae1cf8-ea29-497b-bab3-a8e97fc89ca5
+[SUCCESS] Found all tags with advanced matching.
+
+
+ZHL-SDM-158_561.tei.xml -- 03fef532-1276-4a1a-8c26-6c32b7fb8861
+[SUCCESS] Found all tags with advanced matching.
+
+
+Summary:
+ Simple matches: 19
+ Advanced matches: 44
+ Total files: 89
diff --git a/scripts/tag-matching/CLVN-pie-2023-11-20.txt b/scripts/tag-matching/CLVN-pie-2023-11-20.txt
new file mode 100644
index 0000000..b764182
--- /dev/null
+++ b/scripts/tag-matching/CLVN-pie-2023-11-20.txt
@@ -0,0 +1,492 @@
+01_Archief_Delfland_1531gDelfland.tei.xml -- 6da1b4cd-cc83-45a1-9c4a-f81441271689
+[ERROR] Could not resolve mismatch:
+ Truth: bij
+ Pred: bij…
+[ERROR] Could not resolve mismatch:
+ Truth: …
+ Pred: Gedinge
+[ERROR] Could not resolve mismatch:
+ Truth: Dijckhorens
+ Pred: Dijckhorens-
+[ERROR] Could not resolve mismatch:
+ Truth: -
+ Pred: polre
+[FAIL] Advanced matching misses 4 tags.
+
+
+01_Archief_Delfland_1532jDelfland.tei.xml -- e01f38ff-bc1c-4afe-97fb-c7fd9d71cc3c
+[SUCCESS] Found all tags with advanced matching.
+
+
+02_Archief_Elburg_1580aElburg.tei.xml -- 080ab7de-3f9a-4f79-9a0b-7fd0ab4a6274
+[ERROR] Could not resolve mismatch:
+ Truth: H..t
+ Pred: H
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_ALKMAAR_10-4-1573_TS.tei.xml -- 0abb6244-876f-459c-8b27-013a57b61df8
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Alkmaar_10-6-1575_TS.tei.xml -- 6e6de06f-df3e-4e97-a6b1-6d641ab8de6b
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t.
+ Pred: s
+[ERROR] Could not resolve mismatch:
+ Truth: Ordre
+ Pred: L
+[ERROR] Could not resolve mismatch:
+ Truth: van
+ Pred: .
+[ERROR] Could not resolve mismatch:
+ Truth: Senoy
+ Pred: Coren
+[ERROR] Could not resolve mismatch:
+ Truth: gouverneur
+ Pred: s
+[FAIL] Advanced matching misses 5 tags.
+
+
+03_ArchiefAlkmaar_Alkmaar_16-2-1574_TS.tei.xml -- 00176f3c-9ac2-443c-a0ef-c4d55e77a8ad
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t.
+ Pred: s
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Alkmaar_8-2-1574_TS.tei.xml -- 5a582f5c-ac0b-4531-8148-9207123a9b06
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t
+ Pred: s
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Delft_18-6-1573_TS.tei.xml -- d9a75dc7-1f29-4b8c-b1f5-c2b46e96d762
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Delft_24-3-1573_TS.tei.xml -- 966eb985-2519-4005-ae04-5ca873d07598
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t
+ Pred: s
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Den_Haag_15-9-1572_TS.tei.xml -- a5fb4752-9bf7-4a53-bf1f-15193b986ec0
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Den_Haag_2-10-1586_TS.tei.xml -- 3d82bbca-c0c8-487d-aed4-890366549878
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Diemerdijk_7-6-1573_TS.tei.xml -- f6fb158a-7a8e-40bb-a389-354cf6c073b1
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Dordrecht_23-8-1573_TS.tei.xml -- 1db709e8-40d7-436e-b2dc-d119797b01e1
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t
+ Pred: s
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Edam_15-9-1573_TS.tei.xml -- af78dcc3-6273-45c2-ad6b-3a0489f2717a
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Enkhuizen_5-4-1574_TS_b.tei.xml -- 61ee3744-69d2-4d5b-ab31-411742f049e9
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Enkhuizen__27-10-1572_TS.tei.xml -- ae4722c1-82fc-4dad-8301-40c57180196c
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Hoorn_17-8-1573_TS.tei.xml -- 63d6d61f-0322-40d3-8a17-c11cfb95f12c
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t
+ Pred: s
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Hoorn_8-3-1573_TS.tei.xml -- b23d1e66-d8e5-4039-9e20-3cde43430b93
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Leiden_13-7-1573_TS_b.tei.xml -- a40a7b23-1bf3-46f7-bb1c-f671e90c1657
+[ERROR] Could not resolve mismatch:
+ Truth: N.Brunynck
+ Pred: N
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Leiden_16-6-1573_TS.tei.xml -- b1bb7282-6b16-49d8-8d9c-e0793a0163a2
+[ERROR] Could not resolve mismatch:
+ Truth: N.Brunynck
+ Pred: N
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t
+ Pred: .
+[FAIL] Advanced matching misses 2 tags.
+
+
+03_ArchiefAlkmaar_Middelburg_15-3-1574_TS.tei.xml -- cf1b73d5-cb24-4473-90d6-b603e216123f
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Purmerend_2-5-1574_TS.tei.xml -- ffb5c177-8589-4632-af14-a9bb8f695eb8
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Purmerend_4-6-1574_TS.tei.xml -- c6611cf7-368e-4cf5-a643-94d1ecb4f22b
+[SUCCESS] Found all tags with advanced matching.
+
+
+04_ArchiefEnschede_1596eEnschede.tei.xml -- a436f3d0-1203-4140-bf96-b155c6850ed5
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_12maarta.tei.xml -- ba0db231-1e82-474b-8a9a-6b9132dda26e
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_12maartb.tei.xml -- 78eafa2f-c636-44f7-8b46-520dee8ce338
+[ERROR] Could not resolve mismatch:
+ Truth: J.M.W
+ Pred: J
+[FAIL] Advanced matching misses 1 tags.
+
+
+05_ArchiefZutphen_1552Zua.tei.xml -- 106757d0-fd5e-434b-b163-ac190fababf4
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_1571le.tei.xml -- 213e8a4e-527e-4d1c-ae61-64ac85db0b34
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_1571zur.tei.xml -- 6aaa3c61-403d-4c60-b5fd-ee5409c735c8
+[ERROR] Could not resolve mismatch:
+ Truth: V
+ Pred: V-
+[ERROR] Could not resolve mismatch:
+ Truth: -
+ Pred: Burghermeester
+[FAIL] Advanced matching misses 3 tags.
+
+
+05_ArchiefZutphen_1572eZutphen.tei.xml -- 93730ee4-0c6c-48d2-abc9-e168d9045090
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_1572iiZutphen.tei.xml -- 4b2956a0-c8ad-4894-af69-fd00ba08cd17
+[ERROR] Could not resolve mismatch:
+ Truth: banner
+ Pred: banner-
+[ERROR] Could not resolve mismatch:
+ Truth: -
+ Pred: ende
+[FAIL] Advanced matching misses 2 tags.
+
+
+05_ArchiefZutphen_1572uZutphen.tei.xml -- 34fef94c-57c1-48d6-9551-6df00a3c0162
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_1575fZutphen.tei.xml -- 43811525-9e0e-4111-9956-b6c2d562cee2
+[ERROR] Could not resolve mismatch:
+ Truth: gentz
+ Pred: gentz-
+[ERROR] Could not resolve mismatch:
+ Truth: -
+ Pred: lick
+[FAIL] Advanced matching misses 2 tags.
+
+
+05_ArchiefZutphen_20juli.tei.xml -- d45a0a0a-0181-4254-8145-9529691b8a29
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_23novembera.tei.xml -- d7b617cc-5947-40b5-9175-390c92d7b212
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_28juli.tei.xml -- 19f6e25f-537e-4a4d-9d47-d32662dc1d81
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_29decembera.tei.xml -- 493122fe-4b98-4241-9033-844e7dab295e
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_apr25a.tei.xml -- 69b16e9f-b987-4004-9eec-484834a2744d
+[ERROR] Could not resolve mismatch:
+ Truth: aver
+ Pred: aver-
+[ERROR] Could not resolve mismatch:
+ Truth: -,
+ Pred: ,
+[FAIL] Advanced matching misses 2 tags.
+
+
+06_DiT-Corpus_1594ara.tei.xml -- eeffd88f-6175-49af-8f5b-6dc93b8a8bb6
+[SUCCESS] Found all tags with advanced matching.
+
+
+06_DiT-Corpus_1594are.tei.xml -- 78f35131-937b-4c79-86e9-8c84ceaddaf7
+[ERROR] Could not resolve mismatch:
+ Truth: schellingen
+ Pred: schellingen-
+[ERROR] Could not resolve mismatch:
+ Truth: -
+ Pred: groote
+[FAIL] Advanced matching misses 2 tags.
+
+
+06_DiT-Corpus_1594arf.tei.xml -- 594e3f82-b6c2-473d-9a3f-464dd0b5224d
+[ERROR] Could not resolve mismatch:
+ Truth: B+B+S+R
+ Pred: B
+[FAIL] Advanced matching misses 1 tags.
+
+
+06_DiT-Corpus_1595ar.tei.xml -- 0ff13fe1-0ccc-4c5f-b1a5-2446e771629b
+[ERROR] Could not resolve mismatch:
+ Truth: zynddat
+ Pred: zynd
+[FAIL] Advanced matching misses 1 tags.
+
+
+06_DiT-Corpus_1596ara.tei.xml -- ac2bfaee-1008-4eee-bf36-81313482d4ba
+[SUCCESS] Found all tags with advanced matching.
+
+
+06_DiT-Corpus_1598ara.tei.xml -- e1b9b51e-1161-4c86-8e67-8bc9ffe51b66
+[ERROR] Could not resolve mismatch:
+ Truth: B.Cannoye
+ Pred: B
+[FAIL] Advanced matching misses 1 tags.
+
+
+06_DiT-Corpus_1598arb.tei.xml -- 6763b79b-60e3-4856-b22a-f31510e25551
+[SUCCESS] Found all tags with advanced matching.
+
+
+06_DiT-Corpus_1599ar.tei.xml -- 8c55df51-86c3-4cb0-8d1e-ad8dafbdb918
+[ERROR] Could not resolve mismatch:
+ Truth: B+B
+ Pred: B
+[FAIL] Advanced matching misses 1 tags.
+
+
+08_HeemkundekringGemert_1501.tei.xml -- 4afdbeea-e2fb-4b28-832e-ae529129efb7
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1509.tei.xml -- f0c0943a-4e45-4db1-85bf-1cd900b4b9dc
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1513.tei.xml -- 4fa404a2-9e2b-4537-a5bc-c70fbbae7ef6
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1541.tei.xml -- 0e2c4d9d-a168-4d9c-b8ed-fd60a5e449bf
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1543.tei.xml -- 1c7d1939-0d3b-403b-aea7-19b631012563
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1547sep.tei.xml -- d381fe9a-7103-4404-aee2-38fc3f1534b2
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1555.tei.xml -- 7a132d35-1df3-49e2-bb8f-d10efc9b2efc
+[SUCCESS] Found all tags with advanced matching.
+
+
+12_Seyger-metmetadata_1521Oldenzaalregio.tei.xml -- 12e08234-7728-47fc-9064-9a3e56016038
+[SUCCESS] Found all tags with advanced matching.
+
+
+12_Seyger-metmetadata_1532bOldenzaalregio.tei.xml -- 15dac08a-e58c-44ed-977e-6a416b4020a7
+[ERROR] Could not resolve mismatch:
+ Truth: gheue
+ Pred: gheue-
+[FAIL] Advanced matching misses 1 tags.
+
+
+12_Seyger-metmetadata_1545bOldenzaalregio.tei.xml -- 87ef0b91-014b-48a2-9742-f516cf2f7ac6
+[SUCCESS] Found all tags with advanced matching.
+
+
+12_Seyger-metmetadata_1548aOldenzaalregio.tei.xml -- 371894d5-7cbc-4ff4-ba05-d14303e98d42
+[SUCCESS] Found all tags with advanced matching.
+
+
+12_Seyger-metmetadata_1549Oldenzaalregio.tei.xml -- 59d21350-cd7c-401f-bf49-eedc116dcc88
+[ERROR] Could not resolve mismatch:
+ Truth: XLiX
+ Pred: XLi
+[FAIL] Advanced matching misses 1 tags.
+
+
+12_Seyger-metmetadata_1550bOldenzaalregio.tei.xml -- 2961107f-44a2-4e5a-9fed-27220e3f8bfd
+[SUCCESS] Found all tags with advanced matching.
+
+
+BRA-BRE-152_25.tei.xml -- c8c734b9-c9c9-4061-84c3-abecbaafd445
+[ERROR] Could not resolve mismatch:
+ Truth: XVC.XXVII
+ Pred: XVC
+[FAIL] Advanced matching misses 1 tags.
+
+
+BRA-BRE-158_30.tei.xml -- 36c08d5e-ae82-4f77-8d09-4282c7e83581
+[SUCCESS] Found all tags with advanced matching.
+
+
+BRA-OIR-153_257.tei.xml -- 84ac1032-1066-4f62-8d92-acc4a2ad70f7
+[SUCCESS] Found all tags with advanced matching.
+
+
+FRL-LEE-151_332.tei.xml -- 2cd57d36-e571-4791-ab1d-0f2d98de8b44
+[ERROR] Could not resolve mismatch:
+ Truth: =Kollum
+ Pred: =
+[FAIL] Advanced matching misses 1 tags.
+
+
+FRL-ZVW-150_354.tei.xml -- ddc22d82-2f0f-4cb1-bf48-06cf12135079
+[ERROR] Could not resolve mismatch:
+ Truth: eest
+ Pred: ee
+[ERROR] Could not resolve mismatch:
+ Truth: =Kolderwolde
+ Pred: =
+[FAIL] Advanced matching misses 2 tags.
+
+
+FRL-ZVW-151_355.tei.xml -- c8529065-e4a0-4314-a6a3-09203af5b10a
+[SUCCESS] Found all tags with advanced matching.
+
+
+FRL-ZVW-152_356.tei.xml -- e30da0a2-0744-4a90-bc76-6d1b6ee53371
+[SUCCESS] Found all tags with advanced matching.
+
+
+FRL-ZVW-153_357.tei.xml -- cec70de9-1642-438d-b4d0-ccff382f234f
+[SUCCESS] Found all tags with advanced matching.
+
+
+OSL-BRV-154_139.tei.xml -- f8a43337-ba8c-45d5-99e3-6dfa1c1784e8
+[ERROR] Could not resolve mismatch:
+ Truth: doe
+ Pred: doe-
+[ERROR] Could not resolve mismatch:
+ Truth: huus
+ Pred: huus-
+[ERROR] Could not resolve mismatch:
+ Truth: -
+ Pred: frou
+[FAIL] Advanced matching misses 3 tags.
+
+
+OSL-RLT-158_465.tei.xml -- 96b58a7d-1d96-4522-ad10-4fe730df0ac1
+[SUCCESS] Found all tags with advanced matching.
+
+
+OVL-BKH-156_441.tei.xml -- c4318a7c-ed79-474c-b1ae-b3e167178deb
+[SUCCESS] Found all tags with advanced matching.
+
+
+OVL-DNM-156_444.tei.xml -- 47b0381d-09a4-4764-85bb-6d6b43ff12b3
+[SUCCESS] Found all tags with advanced matching.
+
+
+OVL-GNT-150_451.tei.xml -- 324180e8-f1e6-48b7-9490-f306145f8298
+[ERROR] Could not resolve mismatch:
+ Truth: N.S
+ Pred: N
+[FAIL] Advanced matching misses 1 tags.
+
+
+OVL-GRB-156_454.tei.xml -- e87aee0a-f060-41a5-99ab-ab19bfb0b553
+[SUCCESS] Found all tags with advanced matching.
+
+
+OVL-GRB-157_455.tei.xml -- 7237b809-c945-4c93-b020-ce554fd95930
+[SUCCESS] Found all tags with advanced matching.
+
+
+OVL-OUD-156_457.tei.xml -- 84c9fd24-8399-4f95-b03d-0794abdc578a
+[SUCCESS] Found all tags with advanced matching.
+
+
+OVL-RNS-156_458.tei.xml -- 104b7cfc-539f-4adf-9d25-6b7e2ee4db27
+[SUCCESS] Found all tags with advanced matching.
+
+
+VBR-ERP-157_484.tei.xml -- 27efeb21-3915-4b85-baaa-81583aa1b7ea
+[SUCCESS] Found all tags with advanced matching.
+
+
+VBR-OVY-157_495.tei.xml -- 9b86423b-4de5-4e5b-b1c2-015f003518a8
+[ERROR] Could not resolve mismatch:
+ Truth: D'ISQUE
+ Pred: D'
+[FAIL] Advanced matching misses 1 tags.
+
+
+VBR-SOZ-157_497.tei.xml -- 82c088f8-a7c7-49d2-9672-88c8ea902370
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-BRG-153_178.tei.xml -- 55d589f7-b9b9-4c5a-a82e-f5c6ed248fe9
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-KRT-156_518.tei.xml -- 4de000d7-2f0b-4bb4-9c1a-4b8da0fc0a17
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-MEN-156_522.tei.xml -- 9ea0bee6-48dd-46ab-9f24-30949a89a76d
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-NWP-156_524.tei.xml -- 203455c8-e545-4f50-aebd-97178a954b09
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-OST-156_532.tei.xml -- 2a6dc163-9292-450a-92f6-5cedba4a6513
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-VDH-157_534.tei.xml -- 06525725-1796-4e46-976b-ce378df2c547
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-VRN-156_535.tei.xml -- 3a5afaf6-cb17-440a-9f49-9cb98eb54c64
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-WVK-156_536.tei.xml -- 890b851f-0ce7-4e8c-a5cb-3fcc4946e767
+[SUCCESS] Found all tags with advanced matching.
+
+
+ZHL-LEI-156_386.tei.xml -- 58ae1cf8-ea29-497b-bab3-a8e97fc89ca5
+[SUCCESS] Found all tags with advanced matching.
+
+
+ZHL-SDM-158_561.tei.xml -- 03fef532-1276-4a1a-8c26-6c32b7fb8861
+[SUCCESS] Found all tags with advanced matching.
+
+
+Summary:
+ Simple matches: 27
+ Advanced matches: 61
+ Total files: 89
diff --git a/scripts/tag-matching/CLVN-pie-2024-1-19.txt b/scripts/tag-matching/CLVN-pie-2024-1-19.txt
new file mode 100644
index 0000000..35e0c01
--- /dev/null
+++ b/scripts/tag-matching/CLVN-pie-2024-1-19.txt
@@ -0,0 +1,438 @@
+01_Archief_Delfland_1531gDelfland.tei.xml -- 6da1b4cd-cc83-45a1-9c4a-f81441271689
+[SUCCESS] Found all tags with advanced matching.
+
+
+01_Archief_Delfland_1532jDelfland.tei.xml -- e01f38ff-bc1c-4afe-97fb-c7fd9d71cc3c
+[SUCCESS] Found all tags with advanced matching.
+
+
+02_Archief_Elburg_1580aElburg.tei.xml -- 080ab7de-3f9a-4f79-9a0b-7fd0ab4a6274
+[ERROR] Could not resolve mismatch:
+ Truth: H..t
+ Pred: H
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_ALKMAAR_10-4-1573_TS.tei.xml -- 0abb6244-876f-459c-8b27-013a57b61df8
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Alkmaar_10-6-1575_TS.tei.xml -- 6e6de06f-df3e-4e97-a6b1-6d641ab8de6b
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t.
+ Pred: s
+[ERROR] Could not resolve mismatch:
+ Truth: Ordre
+ Pred: L
+[ERROR] Could not resolve mismatch:
+ Truth: van
+ Pred: .
+[ERROR] Could not resolve mismatch:
+ Truth: Senoy
+ Pred: Coren
+[ERROR] Could not resolve mismatch:
+ Truth: gouverneur
+ Pred: s
+[FAIL] Advanced matching misses 5 tags.
+
+
+03_ArchiefAlkmaar_Alkmaar_16-2-1574_TS.tei.xml -- 00176f3c-9ac2-443c-a0ef-c4d55e77a8ad
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t.
+ Pred: s
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Alkmaar_8-2-1574_TS.tei.xml -- 5a582f5c-ac0b-4531-8148-9207123a9b06
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t
+ Pred: s
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Delft_18-6-1573_TS.tei.xml -- d9a75dc7-1f29-4b8c-b1f5-c2b46e96d762
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Delft_24-3-1573_TS.tei.xml -- 966eb985-2519-4005-ae04-5ca873d07598
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t
+ Pred: s
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Den_Haag_15-9-1572_TS.tei.xml -- a5fb4752-9bf7-4a53-bf1f-15193b986ec0
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Den_Haag_2-10-1586_TS.tei.xml -- 3d82bbca-c0c8-487d-aed4-890366549878
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Diemerdijk_7-6-1573_TS.tei.xml -- f6fb158a-7a8e-40bb-a389-354cf6c073b1
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Dordrecht_23-8-1573_TS.tei.xml -- 1db709e8-40d7-436e-b2dc-d119797b01e1
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t
+ Pred: s
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Edam_15-9-1573_TS.tei.xml -- af78dcc3-6273-45c2-ad6b-3a0489f2717a
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Enkhuizen_5-4-1574_TS_b.tei.xml -- 61ee3744-69d2-4d5b-ab31-411742f049e9
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Enkhuizen__27-10-1572_TS.tei.xml -- ae4722c1-82fc-4dad-8301-40c57180196c
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Hoorn_17-8-1573_TS.tei.xml -- 63d6d61f-0322-40d3-8a17-c11cfb95f12c
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t
+ Pred: s
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Hoorn_8-3-1573_TS.tei.xml -- b23d1e66-d8e5-4039-9e20-3cde43430b93
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Leiden_13-7-1573_TS_b.tei.xml -- a40a7b23-1bf3-46f7-bb1c-f671e90c1657
+[ERROR] Could not resolve mismatch:
+ Truth: N.Brunynck
+ Pred: N
+[FAIL] Advanced matching misses 1 tags.
+
+
+03_ArchiefAlkmaar_Leiden_16-6-1573_TS.tei.xml -- b1bb7282-6b16-49d8-8d9c-e0793a0163a2
+[ERROR] Could not resolve mismatch:
+ Truth: N.Brunynck
+ Pred: N
+[ERROR] Could not resolve mismatch:
+ Truth: s.s.s.t
+ Pred: .
+[FAIL] Advanced matching misses 2 tags.
+
+
+03_ArchiefAlkmaar_Middelburg_15-3-1574_TS.tei.xml -- cf1b73d5-cb24-4473-90d6-b603e216123f
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Purmerend_2-5-1574_TS.tei.xml -- ffb5c177-8589-4632-af14-a9bb8f695eb8
+[SUCCESS] Found all tags with advanced matching.
+
+
+03_ArchiefAlkmaar_Purmerend_4-6-1574_TS.tei.xml -- c6611cf7-368e-4cf5-a643-94d1ecb4f22b
+[SUCCESS] Found all tags with advanced matching.
+
+
+04_ArchiefEnschede_1596eEnschede.tei.xml -- a436f3d0-1203-4140-bf96-b155c6850ed5
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_12maarta.tei.xml -- ba0db231-1e82-474b-8a9a-6b9132dda26e
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_12maartb.tei.xml -- 78eafa2f-c636-44f7-8b46-520dee8ce338
+[ERROR] Could not resolve mismatch:
+ Truth: J.M.W
+ Pred: J
+[FAIL] Advanced matching misses 1 tags.
+
+
+05_ArchiefZutphen_1552Zua.tei.xml -- 106757d0-fd5e-434b-b163-ac190fababf4
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_1571le.tei.xml -- 213e8a4e-527e-4d1c-ae61-64ac85db0b34
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_1571zur.tei.xml -- 6aaa3c61-403d-4c60-b5fd-ee5409c735c8
+[FAIL] Advanced matching misses 1 tags.
+
+
+05_ArchiefZutphen_1572eZutphen.tei.xml -- 93730ee4-0c6c-48d2-abc9-e168d9045090
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_1572iiZutphen.tei.xml -- 4b2956a0-c8ad-4894-af69-fd00ba08cd17
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_1572uZutphen.tei.xml -- 34fef94c-57c1-48d6-9551-6df00a3c0162
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_1575fZutphen.tei.xml -- 43811525-9e0e-4111-9956-b6c2d562cee2
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_20juli.tei.xml -- d45a0a0a-0181-4254-8145-9529691b8a29
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_23novembera.tei.xml -- d7b617cc-5947-40b5-9175-390c92d7b212
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_28juli.tei.xml -- 19f6e25f-537e-4a4d-9d47-d32662dc1d81
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_29decembera.tei.xml -- 493122fe-4b98-4241-9033-844e7dab295e
+[SUCCESS] Found all tags with advanced matching.
+
+
+05_ArchiefZutphen_apr25a.tei.xml -- 69b16e9f-b987-4004-9eec-484834a2744d
+[SUCCESS] Found all tags with advanced matching.
+
+
+06_DiT-Corpus_1594ara.tei.xml -- eeffd88f-6175-49af-8f5b-6dc93b8a8bb6
+[SUCCESS] Found all tags with advanced matching.
+
+
+06_DiT-Corpus_1594are.tei.xml -- 78f35131-937b-4c79-86e9-8c84ceaddaf7
+[SUCCESS] Found all tags with advanced matching.
+
+
+06_DiT-Corpus_1594arf.tei.xml -- 594e3f82-b6c2-473d-9a3f-464dd0b5224d
+[ERROR] Could not resolve mismatch:
+ Truth: B+B+S+R
+ Pred: B
+[FAIL] Advanced matching misses 1 tags.
+
+
+06_DiT-Corpus_1595ar.tei.xml -- 0ff13fe1-0ccc-4c5f-b1a5-2446e771629b
+[ERROR] Could not resolve mismatch:
+ Truth: zynddat
+ Pred: zynd
+[FAIL] Advanced matching misses 1 tags.
+
+
+06_DiT-Corpus_1596ara.tei.xml -- ac2bfaee-1008-4eee-bf36-81313482d4ba
+[SUCCESS] Found all tags with advanced matching.
+
+
+06_DiT-Corpus_1598ara.tei.xml -- e1b9b51e-1161-4c86-8e67-8bc9ffe51b66
+[ERROR] Could not resolve mismatch:
+ Truth: B.Cannoye
+ Pred: B
+[FAIL] Advanced matching misses 1 tags.
+
+
+06_DiT-Corpus_1598arb.tei.xml -- 6763b79b-60e3-4856-b22a-f31510e25551
+[SUCCESS] Found all tags with advanced matching.
+
+
+06_DiT-Corpus_1599ar.tei.xml -- 8c55df51-86c3-4cb0-8d1e-ad8dafbdb918
+[ERROR] Could not resolve mismatch:
+ Truth: B+B
+ Pred: B
+[FAIL] Advanced matching misses 1 tags.
+
+
+08_HeemkundekringGemert_1501.tei.xml -- 4afdbeea-e2fb-4b28-832e-ae529129efb7
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1509.tei.xml -- f0c0943a-4e45-4db1-85bf-1cd900b4b9dc
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1513.tei.xml -- 4fa404a2-9e2b-4537-a5bc-c70fbbae7ef6
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1541.tei.xml -- 0e2c4d9d-a168-4d9c-b8ed-fd60a5e449bf
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1543.tei.xml -- 1c7d1939-0d3b-403b-aea7-19b631012563
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1547sep.tei.xml -- d381fe9a-7103-4404-aee2-38fc3f1534b2
+[SUCCESS] Found all tags with advanced matching.
+
+
+08_HeemkundekringGemert_1555.tei.xml -- 7a132d35-1df3-49e2-bb8f-d10efc9b2efc
+[SUCCESS] Found all tags with advanced matching.
+
+
+12_Seyger-metmetadata_1521Oldenzaalregio.tei.xml -- 12e08234-7728-47fc-9064-9a3e56016038
+[SUCCESS] Found all tags with advanced matching.
+
+
+12_Seyger-metmetadata_1532bOldenzaalregio.tei.xml -- 15dac08a-e58c-44ed-977e-6a416b4020a7
+[SUCCESS] Found all tags with advanced matching.
+
+
+12_Seyger-metmetadata_1545bOldenzaalregio.tei.xml -- 87ef0b91-014b-48a2-9742-f516cf2f7ac6
+[SUCCESS] Found all tags with advanced matching.
+
+
+12_Seyger-metmetadata_1548aOldenzaalregio.tei.xml -- 371894d5-7cbc-4ff4-ba05-d14303e98d42
+[SUCCESS] Found all tags with advanced matching.
+
+
+12_Seyger-metmetadata_1549Oldenzaalregio.tei.xml -- 59d21350-cd7c-401f-bf49-eedc116dcc88
+[ERROR] Could not resolve mismatch:
+ Truth: XLiX
+ Pred: XLi
+[FAIL] Advanced matching misses 1 tags.
+
+
+12_Seyger-metmetadata_1550bOldenzaalregio.tei.xml -- 2961107f-44a2-4e5a-9fed-27220e3f8bfd
+[SUCCESS] Found all tags with advanced matching.
+
+
+BRA-BRE-152_25.tei.xml -- c8c734b9-c9c9-4061-84c3-abecbaafd445
+[ERROR] Could not resolve mismatch:
+ Truth: XVC.XXVII
+ Pred: XVC
+[FAIL] Advanced matching misses 1 tags.
+
+
+BRA-BRE-158_30.tei.xml -- 36c08d5e-ae82-4f77-8d09-4282c7e83581
+[SUCCESS] Found all tags with advanced matching.
+
+
+BRA-OIR-153_257.tei.xml -- 84ac1032-1066-4f62-8d92-acc4a2ad70f7
+[SUCCESS] Found all tags with advanced matching.
+
+
+FRL-LEE-151_332.tei.xml -- 2cd57d36-e571-4791-ab1d-0f2d98de8b44
+[ERROR] Could not resolve mismatch:
+ Truth: =Kollum
+ Pred: =
+[FAIL] Advanced matching misses 1 tags.
+
+
+FRL-ZVW-150_354.tei.xml -- ddc22d82-2f0f-4cb1-bf48-06cf12135079
+[ERROR] Could not resolve mismatch:
+ Truth: eest
+ Pred: ee
+[ERROR] Could not resolve mismatch:
+ Truth: =Kolderwolde
+ Pred: =
+[FAIL] Advanced matching misses 2 tags.
+
+
+FRL-ZVW-151_355.tei.xml -- c8529065-e4a0-4314-a6a3-09203af5b10a
+[SUCCESS] Found all tags with advanced matching.
+
+
+FRL-ZVW-152_356.tei.xml -- e30da0a2-0744-4a90-bc76-6d1b6ee53371
+[SUCCESS] Found all tags with advanced matching.
+
+
+FRL-ZVW-153_357.tei.xml -- cec70de9-1642-438d-b4d0-ccff382f234f
+[SUCCESS] Found all tags with advanced matching.
+
+
+OSL-BRV-154_139.tei.xml -- f8a43337-ba8c-45d5-99e3-6dfa1c1784e8
+[SUCCESS] Found all tags with advanced matching.
+
+
+OSL-RLT-158_465.tei.xml -- 96b58a7d-1d96-4522-ad10-4fe730df0ac1
+[SUCCESS] Found all tags with advanced matching.
+
+
+OVL-BKH-156_441.tei.xml -- c4318a7c-ed79-474c-b1ae-b3e167178deb
+[SUCCESS] Found all tags with advanced matching.
+
+
+OVL-DNM-156_444.tei.xml -- 47b0381d-09a4-4764-85bb-6d6b43ff12b3
+[SUCCESS] Found all tags with advanced matching.
+
+
+OVL-GNT-150_451.tei.xml -- 324180e8-f1e6-48b7-9490-f306145f8298
+[ERROR] Could not resolve mismatch:
+ Truth: N.S
+ Pred: N
+[FAIL] Advanced matching misses 1 tags.
+
+
+OVL-GRB-156_454.tei.xml -- e87aee0a-f060-41a5-99ab-ab19bfb0b553
+[SUCCESS] Found all tags with advanced matching.
+
+
+OVL-GRB-157_455.tei.xml -- 7237b809-c945-4c93-b020-ce554fd95930
+[SUCCESS] Found all tags with advanced matching.
+
+
+OVL-OUD-156_457.tei.xml -- 84c9fd24-8399-4f95-b03d-0794abdc578a
+[SUCCESS] Found all tags with advanced matching.
+
+
+OVL-RNS-156_458.tei.xml -- 104b7cfc-539f-4adf-9d25-6b7e2ee4db27
+[SUCCESS] Found all tags with advanced matching.
+
+
+VBR-ERP-157_484.tei.xml -- 27efeb21-3915-4b85-baaa-81583aa1b7ea
+[SUCCESS] Found all tags with advanced matching.
+
+
+VBR-OVY-157_495.tei.xml -- 9b86423b-4de5-4e5b-b1c2-015f003518a8
+[ERROR] Could not resolve mismatch:
+ Truth: D'ISQUE
+ Pred: D'
+[FAIL] Advanced matching misses 1 tags.
+
+
+VBR-SOZ-157_497.tei.xml -- 82c088f8-a7c7-49d2-9672-88c8ea902370
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-BRG-153_178.tei.xml -- 55d589f7-b9b9-4c5a-a82e-f5c6ed248fe9
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-KRT-156_518.tei.xml -- 4de000d7-2f0b-4bb4-9c1a-4b8da0fc0a17
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-MEN-156_522.tei.xml -- 9ea0bee6-48dd-46ab-9f24-30949a89a76d
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-NWP-156_524.tei.xml -- 203455c8-e545-4f50-aebd-97178a954b09
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-OST-156_532.tei.xml -- 2a6dc163-9292-450a-92f6-5cedba4a6513
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-VDH-157_534.tei.xml -- 06525725-1796-4e46-976b-ce378df2c547
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-VRN-156_535.tei.xml -- 3a5afaf6-cb17-440a-9f49-9cb98eb54c64
+[SUCCESS] Found all tags with advanced matching.
+
+
+WVL-WVK-156_536.tei.xml -- 890b851f-0ce7-4e8c-a5cb-3fcc4946e767
+[SUCCESS] Found all tags with advanced matching.
+
+
+ZHL-LEI-156_386.tei.xml -- 58ae1cf8-ea29-497b-bab3-a8e97fc89ca5
+[SUCCESS] Found all tags with advanced matching.
+
+
+ZHL-SDM-158_561.tei.xml -- 03fef532-1276-4a1a-8c26-6c32b7fb8861
+[SUCCESS] Found all tags with advanced matching.
+
+
+Summary:
+ Simple matches: 27
+ Advanced matches: 68
+ Total files: 89
diff --git a/scripts/tag-matching/check-non-ascii.sh b/scripts/tag-matching/check-non-ascii.sh
new file mode 100644
index 0000000..578fb4c
--- /dev/null
+++ b/scripts/tag-matching/check-non-ascii.sh
@@ -0,0 +1,9 @@
+for file in truth-CLVN/*.xml
+do
+ echo "$file"
+ cat $file | \
+ # awk works per line so we need to remove newlines.
+ tr --delete '\n' | \
+ awk -F '' '{print $2}' | \
+ grep --color=always -nio -e '[^ a-z äëöüï éó 0-9 <> \\ \( \) \. \? \+ °‘’,;:!-_=&%$#@*/~`^" \{ \} \[ | ]'
+done
diff --git a/scripts/tag-matching/match-tagged-docs.py b/scripts/tag-matching/match-tagged-docs.py
new file mode 100644
index 0000000..d2938c4
--- /dev/null
+++ b/scripts/tag-matching/match-tagged-docs.py
@@ -0,0 +1,237 @@
+from enum import Enum
+import re
+import string
+import xml.etree.ElementTree as ET
+import os
+import subprocess
+import logging
+import sys
+
+logging.basicConfig(
+ level=logging.WARNING, stream=sys.stdout, format="[%(levelname)s] %(message)s"
+)
+WINDOW_SIZE = 10
+PUNCTUATION = ".,;:!?‘’'[]()/\"-—–“”«»„…*"
+
+
+class Direction(Enum):
+ """
+ Useful for calculating array offset in both direction.
+ """
+
+ FORWARD = 1
+ BACKWARD = -1
+
+
+class Analyser:
+ """
+ Analyse the output of galahad tagged (predicted) documents against the ground truth.
+ Performs:
+ 1. Simple analysis:
+ counts the number of and tags in each file.
+ 2. Advanced analysis:
+ matches and tags 1-on-1 by literal. Traverses the xml tree,
+ offsetting the index when a mismatch is found.
+ """
+
+ truth_folder: str
+ galahad_folder: str
+ is_truth_plaintext: bool
+ # Number of matching documents.
+ n_simple_doc_matches: int
+ n_advanced_doc_matches: int
+ # Used for advanced analysis.
+ offset: int
+
+ def __init__(
+ self,
+ truth_folder: str = "truth",
+ galahad_folder: str = "galahad",
+ is_truth_plaintext: bool = False,
+ ):
+ self.is_truth_plaintext = is_truth_plaintext
+ self.truth_folder = truth_folder
+ self.galahad_folder = galahad_folder
+
+ def analyse(self):
+ self.n_simple_doc_matches = 0
+ self.n_advanced_doc_matches = 0
+ _, _, files = next(os.walk(self.galahad_folder))
+ for file in files:
+ print(f"{file} -- {self.get_document_id(self.galahad_folder, file)}")
+ self.simple_analysis(file)
+ self.advanced_analysis(file)
+ print("\n")
+
+ self.print_summary(files)
+
+ def get_document_id(self, folder: str, file: str) -> str:
+ try:
+ root = ET.parse(f"{folder}/{file}").getroot()
+ for tag in root.iter():
+ # tag.tag contains a namespace, hence "in".
+ if "idno" in tag.tag and tag.attrib["type"] == "pid":
+ document_id = f"{tag.text}"
+ return document_id
+ except:
+ pass
+ return "No ID"
+
+ def simple_analysis(self, file: str) -> None:
+ n_tags_in_pred = self.count_tags_simple(self.galahad_folder, file)
+ n_tags_in_truth = self.count_tags_simple(self.truth_folder, file)
+ if n_tags_in_pred == n_tags_in_truth:
+ self.n_simple_doc_matches += 1
+ logging.info("Simple analysis:")
+ logging.info(f"Tags in galahad: {n_tags_in_pred}")
+ logging.info(f"Tags in ground truth: {n_tags_in_truth}")
+
+ def count_tags_simple(self, folder: str, file: str) -> int:
+ return int(
+ subprocess.check_output(
+ f'cat "{folder}/{file}" | grep -oE " | " | wc -l',
+ shell=True,
+ text=True,
+ ).strip()
+ )
+
+ def advanced_analysis(self, f: str) -> None:
+ """
+ Match and tags 1-on-1 between the two files.
+ """
+ logging.info("Advanced analysis:")
+ pred_tags, truth_tags = self.get_tag_lists(f)
+ # print number of tags
+ logging.debug(f"Tags in pred: {len(pred_tags)}")
+ logging.debug(f"Tags in truth: {len(truth_tags)}")
+ matches = self.advanced_tag_matching(pred_tags, truth_tags)
+ self.print_advanced_summary(truth_tags, matches)
+
+ def get_tag_lists(self, file: str) -> tuple[list[str], list[str]]:
+ pred_tags = self.extract_tags_from_file(self.galahad_folder, file)
+ truth_tags = self.extract_tags_from_file(
+ self.truth_folder, file, self.is_truth_plaintext
+ )
+ return pred_tags, truth_tags
+
+ def extract_tags_from_file(
+ self, folder: str, file: str, is_plaintext: bool = False
+ ) -> list[str]:
+ """
+ Returns list of text content of all and tags from the file.
+ """
+ tags: list[str] = []
+ root = ET.parse(f"{folder}/{file}").getroot()
+ for element in root.iter():
+ tag = element.tag.split("}")[-1] # tag has a weird namespace.
+ if is_plaintext:
+ if tag == "body":
+ text_content = "".join(list(element.itertext()))
+ return self.tokenize(text_content)
+ else:
+ if tag == "w" or tag == "pc":
+ # Get text by iterating over all textable children.
+ text_content = "".join(list(element.itertext()))
+ tags.append(text_content)
+ return tags
+
+ def tokenize(self, text: str) -> list[str]:
+ """
+ Tokenizes text by splitting on whitespace-like.
+ """
+ tokens = re.split(rf"[{string.whitespace}]", text)
+ return [token for token in tokens if token != ""]
+
+ def advanced_tag_matching(self, pred_tags: list[str], truth_tags: list[str]) -> int:
+ """
+ Matches tags one-on-one between the two files.
+ """
+ matches: int = 0
+ self.offset: int = 0
+ for i in range(len(truth_tags)):
+ if i + self.offset >= len(pred_tags):
+ logging.debug(f"Reached end of pred tags. Stopping.")
+ break
+ truth_text = truth_tags[i]
+ pred_text = pred_tags[i + self.offset]
+ if self.text_matches(truth_text, pred_text):
+ matches += 1
+ else:
+ logging.debug(
+ f"Tags did not match.\n\tTruth: {truth_text}\n\tPred: {pred_text}"
+ )
+ matches = self.resolve_mismatch(i, pred_tags, truth_text, matches)
+ return matches
+
+ def text_matches(self, truth_text: str, pred_text: str) -> bool:
+ """
+ Checks if the two texts match, ignoring punctuation.
+ """
+ return truth_text.strip(PUNCTUATION) == pred_text.strip(PUNCTUATION)
+
+ def resolve_mismatch(
+ self, i: int, pred_tags: list[str], truth_text: str, matches: int
+ ) -> int:
+ # if truth_text consists completely of punctuation, we ignore it.
+ if truth_text.strip(PUNCTUATION) == "":
+ logging.debug(f"Ignoring punctuation.")
+ return matches + 1 # Just consider it a match.
+ # Try to find the correct shift to apply to offset.
+ forward_shift: int | None = self.find_shifted_match(
+ pred_tags, i, truth_text, Direction.FORWARD
+ )
+ backward_shift: int | None = self.find_shifted_match(
+ pred_tags, i, truth_text, Direction.BACKWARD
+ )
+
+ # If we found a match, we shift the offset.
+ if forward_shift is not None or backward_shift is not None:
+ # Get smallest non-null abs(shift).
+ shift = min(filter(None, [forward_shift, backward_shift]), key=abs)
+ logging.debug(f"Found match. Shifted offset forward by {shift}.")
+ self.offset += shift
+ return matches + 1
+ else:
+ pred_text = pred_tags[i + self.offset]
+ logging.error(
+ f"Could not resolve mismatch:\n\tTruth: {truth_text}\n\tPred: {pred_text}"
+ )
+ return matches
+
+ def find_shifted_match(
+ self, pred_tags: list[str], i: int, truth_text: str, dir: Direction
+ ) -> int | None:
+ """
+ Tries to find a match for truth_text WINDOW_SIZE, forward or backward.
+ Returns the required shift to apply to the offset.
+ """
+ for undirectional_j in range(1, WINDOW_SIZE):
+ # Make j go forward or backward.
+ j = undirectional_j * dir.value
+ # Out of bounds check.
+ if i + self.offset + j >= len(pred_tags) or i + self.offset + j < 0:
+ return None
+ # Match checking.
+ pred_text: str = pred_tags[i + self.offset + j]
+ if self.text_matches(truth_text, pred_text):
+ logging.debug(f"Found potential match. Shift: {j}")
+ return j
+ return None
+
+ def print_advanced_summary(self, truth_tags: list[str], matches: int) -> None:
+ logging.info(f"Advanced matches: {matches}")
+ if matches == len(truth_tags):
+ print(f"[SUCCESS] Found all tags with advanced matching.")
+ self.n_advanced_doc_matches += 1
+ else:
+ print(f"[FAIL] Advanced matching misses {len(truth_tags) - matches} tags.")
+
+ def print_summary(self, files: list[str]) -> None:
+ print(f"Summary:")
+ print(f"\tSimple matches: {self.n_simple_doc_matches}")
+ print(f"\tAdvanced matches: {self.n_advanced_doc_matches}")
+ print(f"\tTotal files: {len(files)}")
+
+
+if __name__ == "__main__":
+ Analyser("CLVN-truth", "CLVN-hug").analyse()
diff --git a/scripts/tag-matching/pie-clariah.txt b/scripts/tag-matching/pie-clariah.txt
new file mode 100644
index 0000000..103c18e
--- /dev/null
+++ b/scripts/tag-matching/pie-clariah.txt
@@ -0,0 +1,52 @@
+blom013aenm01_01.xml -- No ID
+[SUCCESS] Found all tags with advanced matching.
+
+
+cast005cons01_01.xml -- No ID
+[SUCCESS] Found all tags with advanced matching.
+
+
+craa001wand03_01.xml -- No ID
+[SUCCESS] Found all tags with advanced matching.
+
+
+enge022vand01_01.xml -- No ID
+[SUCCESS] Found all tags with advanced matching.
+
+
+ferm001nieu01_01.xml -- No ID
+[SUCCESS] Found all tags with advanced matching.
+
+
+mult001maxh14_01.xml -- None
+[SUCCESS] Found all tags with advanced matching.
+
+
+stev001wisc02_01.xml -- No ID
+[SUCCESS] Found all tags with advanced matching.
+
+
+_ams015amst01_01.xml -- No ID
+[SUCCESS] Found all tags with advanced matching.
+
+
+_boe007boec01_01.xml -- No ID
+[SUCCESS] Found all tags with advanced matching.
+
+
+_hoe003hoer01_01.xml -- No ID
+[SUCCESS] Found all tags with advanced matching.
+
+
+_vol002volm01_01.xml -- No ID
+[SUCCESS] Found all tags with advanced matching.
+
+
+_wal002walv01_01.xml -- No ID
+[SUCCESS] Found all tags with advanced matching.
+
+
+Summary:
+ Simple matches: 12
+ Advanced matches: 12
+ Total files: 12
diff --git a/scripts/tag-matching/pie-katrien-MNL.txt b/scripts/tag-matching/pie-katrien-MNL.txt
new file mode 100644
index 0000000..14a0191
--- /dev/null
+++ b/scripts/tag-matching/pie-katrien-MNL.txt
@@ -0,0 +1,26 @@
+roman_van_lancelot_proza.xml -- INT_0648bf05-8f52-44eb-b906-3f109d598bf2
+[ERROR] Could not resolve mismatch:
+ Truth: bego[...]se
+ Pred: bego[
+[ERROR] Could not resolve mismatch:
+ Truth: m[.]nne
+ Pred: m[
+[FAIL] Advanced matching misses 2 tags.
+
+
+tweede_martijn.xml -- INT_ab75a1bc-f63d-4a7f-9ab4-1358385004d1
+[SUCCESS] Found all tags with advanced matching.
+
+
+van_den_vos_reynaerde.xml -- INT_68d11107-06d3-488a-8c77-4bf95587fb46
+[SUCCESS] Found all tags with advanced matching.
+
+
+walewein_ende_keye.xml -- INT_f7eb888b-ef43-4139-a9f6-9213f6db6f2c
+[SUCCESS] Found all tags with advanced matching.
+
+
+Summary:
+ Simple matches: 0
+ Advanced matches: 3
+ Total files: 4
diff --git a/server/.run/GalahadApplication.run.xml b/server/.run/GalahadApplication.run.xml
new file mode 100644
index 0000000..550b273
--- /dev/null
+++ b/server/.run/GalahadApplication.run.xml
@@ -0,0 +1,14 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/server/Dockerfile b/server/Dockerfile
new file mode 100644
index 0000000..e697466
--- /dev/null
+++ b/server/Dockerfile
@@ -0,0 +1,27 @@
+FROM gradle:8.6-jdk17-alpine AS build
+# Note: the gradle version must match the version in build.gradle.kts
+COPY --chown=gradle:gradle . /home/gradle/src
+WORKDIR /home/gradle/src
+RUN echo "Contents of workdir:" && ls -l . \
+ && gradle build --no-daemon -x test \
+ && echo "Build complete, contents of build/libs:" && ls -l build/libs \
+ && rm build/libs/*-plain.jar \
+ && echo "Contents of build/libs after removing plain jars:" && ls -l build/libs
+
+FROM eclipse-temurin:17-jre-alpine
+
+# FIX CVE-2023-52425
+RUN apk --no-cache add --upgrade expat
+
+EXPOSE 8010:8080
+COPY --from=build /home/gradle/src/build/libs/*.jar galahad-server.jar
+COPY --from=build /home/gradle/src/application.properties application.properties
+
+COPY /data ./data
+
+# -Dlog4j2.formatMsgNoLookups=true is to mitigate log4j zero-day, which we are probably vulnerable to since we use
+# https://logging.apache.org/log4j/kotlin/ , however it is not clear how to updrage log4j itself whilst keep using the kotlin wrapper
+# Therefore we use a mitigation noted here: https://github.com/advisories/GHSA-jfh8-c2jp-5v3q
+# It can be removed once we have ensured the log4j verion is properly upgraded
+# We start with a big heap size to avoid OOM on large documents
+ENTRYPOINT ["java", "-Xms4g", "-Xmx8g", "-Dspring.profiles.active=prod", "-Dlog4j2.formatMsgNoLookups=true", "-Djava.security.egd=file:/dev/./urandom","-jar","galahad-server.jar"]
diff --git a/server/README.md b/server/README.md
new file mode 100644
index 0000000..1bdb2f2
--- /dev/null
+++ b/server/README.md
@@ -0,0 +1,57 @@
+# Dev info
+When debugging locally, add "profile=dev" to the environment variables (e.g. in the intelliJ debug configuration).
+This makes it so that:
+- taggers are called on localhost, instead of their docker container name via a docker network
+- we use a test user, instead of retrieving it from the request headers
+
+# Source code
+The src/ folder contains the following packages:
+
+## app
+The base for spring boot and some application wide interfaces.
+
+## data
+All Galahad data is stored on disk. Hence why we have classes here like FileBackedCache and FileBackedValue.
+
+## data.corpus
+
+## data.layer
+The annotations in a document (i.e. lemma and pos of each token) are collectively called a layer. A document can have multiple layers (as it can be tagged by multiple taggers). The original annotation layer is called the "sourceLayer".
+A layer consists of a list of terms. A term consists of a lemma, a part of speech, and a token. At some point, the system was designed for a term to be able to point to multiple token, hence why Term in reality has a list of tokens, called "word forms". But in reality there is only ever one token, and multi word terms were not fully developed.
+
+## evaluation
+For evaluating a single layer (the frequency distribution) or comparing two layers (part of speech confusion and accuracy metrics), where one represents the absolute truth (called the "reference") and one is being tested against it (called the "hypothesis"). The main use case is setting the sourceLayer as the absolute truth reference.
+
+The subpackages confusion, distribution, and metrics calculate their respective evaluations in a similar manner.
+E.g. for distribution: There is a CorpusDistribution, which calls a DocumentDistribution on each document in the corpus.
+Both types inherit from the same aggregation class. Within each document, term are aggregated into a single object. And within the corpus, documents are aggregated into a single object.
+
+![Diagram of downloading a CorpusConfusion](docs/CorpusConfusion.png)
+
+### evaluation.comparison
+To perform the evaluation, we compare layers (LayerComparison), which requires us to compare the terms (TermComparison), which requires us to compare the word forms (WordFormComparions). The part of speech confusion and accuracy metrics then use these comparisons to construct an evaluation. They also keep track of 10 random samples for each evaluation. For example, for the %-incorrect metric of lemmata, 10 random samples are chosen that show a term with an incorrect lemma. And, for example, for the part of speech confusion, 10 samples are chosen to demonstrate the evaluation "noun vs verb". Etc.
+
+Both evaluation.metrics and evaluation.comparison group on some annotation (e.g. pos). You can filter this even further
+
+### evaluation.metrics
+The metrics calculation does in part use the same aggregation as described above, but adds more complexity in order to keep track of false positives, false negatives, etc.; micro and macro metrics; grouped metrics and global metrics; and all of this grouped by various annotations (lemma, pos, etc.) and filtered by various criteria (e.g. multi pos only, like ADP+NOU).
+
+CorpusMetrics and DocumentMetrics exist and inherit from Metrics. Because of the group and filter option, we need to define these settings somewhere: MetricsSettings.
+
+Using MetricsSettings, you can create a MetricsType, which calculates metrics according to the settings. The Metrics base class, then, calculates a list of different MetricsTypes all at the same time.
+
+These MetricsTypes have global information: micro, macro, classification classes (true positive, etc.). And they have grouped information (e.g. grouped by pos: NOU, ADV, etc.): micro and classification classes.
+
+The global information is calculated based on the grouped information at the time of json serialization (at that point, all terms in the corpus have been seen).
+
+### evaluation.assays
+In order to show a leaderboard of taggers on datasets, we have so-called 'assays'. These are simplified accuracy metrics.
+
+## jobs
+The process of a tagger tagging a document, which creates a new annotation layer, is called a job.
+
+## port
+Contains all document readers, converters and mergers for the supported formats in Galahad.
+
+## tagset & tagger
+Both relatively simple packages. Read out yaml files in a folder and make them available in a singleton-like manner.
\ No newline at end of file
diff --git a/server/application-dev.properties b/server/application-dev.properties
new file mode 100644
index 0000000..621f642
--- /dev/null
+++ b/server/application-dev.properties
@@ -0,0 +1,3 @@
+springdoc.swagger-ui.config-url=/v3/api-docs/swagger-config
+springdoc.swagger-ui.path=/swagger-ui.html
+springdoc.swagger-ui.url=/v3/api-docs
diff --git a/server/application.properties b/server/application.properties
new file mode 100644
index 0000000..e29cc9c
--- /dev/null
+++ b/server/application.properties
@@ -0,0 +1,11 @@
+logging.level.org.springframework.web.filter.CommonsRequestLoggingFilter=DEBUG
+# https://stackoverflow.com/questions/72235752/cannot-invoke-org-springframework-web-servlet-mvc-condition-patternsrequestcond
+spring.mvc.pathmatch.matching-strategy=ant_path_matcher
+springdoc.swagger-ui.disable-swagger-default-url=true
+# forward headers strategy
+server.use-forward-headers=true
+server.forward-headers-strategy=framework
+
+#springdoc.swagger-ui.config-url=/galahad/api/v3/api-docs/swagger-config
+#springdoc.swagger-ui.path=/swagger-ui.html
+#springdoc.swagger-ui.url=/galahad/api/v3/api-docs
\ No newline at end of file
diff --git a/server/build.gradle.kts b/server/build.gradle.kts
new file mode 100644
index 0000000..7b63c76
--- /dev/null
+++ b/server/build.gradle.kts
@@ -0,0 +1,78 @@
+import org.jetbrains.dokka.DokkaConfiguration.Visibility
+import org.jetbrains.dokka.gradle.DokkaTask
+import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
+
+plugins {
+ id("org.springframework.boot") version "3.2.3"
+ id("io.spring.dependency-management") version "1.1.4"
+ id("org.jetbrains.dokka") version "1.9.10"
+ kotlin("jvm") version "1.9.22"
+ kotlin("plugin.spring") version "1.9.22"
+ kotlin("plugin.serialization") version "1.9.22"
+}
+
+group = "org.ivdnt"
+version = "0.0.2-ALPHA-SNAPSHOT"
+java.sourceCompatibility = JavaVersion.VERSION_17
+java.targetCompatibility = JavaVersion.VERSION_17
+
+repositories {
+ mavenCentral()
+ gradlePluginPortal()
+}
+
+dependencies {
+ // Spring
+ implementation("org.springframework.boot:spring-boot-starter-web:3.2.4")
+ // https://mvnrepository.com/artifact/org.springframework.boot/spring-boot-devtools
+ implementation("org.springframework.boot:spring-boot-devtools:3.2.3")
+
+ // kotlin
+ implementation("org.jetbrains.kotlin:kotlin-reflect:1.9.22")
+ // https://mvnrepository.com/artifact/org.jetbrains.kotlin/kotlin-stdlib
+ implementation("org.jetbrains.kotlin:kotlin-stdlib:1.9.22")
+ implementation("org.jetbrains.kotlinx:kotlinx-coroutines-core:1.8.0")
+ implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") // JVM dependency
+
+ // swagger
+ implementation("org.springdoc:springdoc-openapi-starter-webmvc-ui:2.5.0")
+
+
+ implementation("com.beust:klaxon:5.6")
+ implementation("org.apache.logging.log4j:log4j-api-kotlin:1.2.0")
+
+ // yaml
+ // https://mvnrepository.com/artifact/org.yaml/snakeyaml
+ implementation("org.yaml:snakeyaml:2.2")
+
+ // Tests
+ testImplementation ("org.springframework.boot:spring-boot-starter-test:3.2.3")
+}
+
+tasks.test {
+ environment(mapOf("profile" to "dev"))
+ useJUnitPlatform()
+}
+
+tasks.withType {
+ useJUnitPlatform()
+ // https://stackoverflow.com/questions/52733942/increase-heap-memory-for-gradle-test
+// minHeapSize = "4096m"
+// maxHeapSize = "4096m"
+// jvmArgs = listOf("-XX:MaxPermSize=1024m") // fails on some IDEs
+}
+
+tasks.withType {
+ kotlinOptions {
+ freeCompilerArgs = listOf("-Xjsr305=strict")
+ jvmTarget = "17"
+ }
+}
+
+tasks.withType().configureEach {
+ dokkaSourceSets {
+ configureEach {
+ documentedVisibilities.set(setOf(Visibility.PUBLIC, Visibility.PROTECTED, Visibility.PRIVATE, Visibility.INTERNAL))
+ }
+ }
+}
\ No newline at end of file
diff --git a/server/data/admins/admins.txt b/server/data/admins/admins.txt
new file mode 100644
index 0000000..a0cde59
--- /dev/null
+++ b/server/data/admins/admins.txt
@@ -0,0 +1 @@
+you
diff --git a/server/data/corpora/presets/daa39639-2c08-43a2-9c48-f4f3ea68d494/documents/dbnl-excerpts-15.test.tsv/format b/server/data/corpora/presets/daa39639-2c08-43a2-9c48-f4f3ea68d494/documents/dbnl-excerpts-15.test.tsv/format
new file mode 100644
index 0000000..ec69bf0
--- /dev/null
+++ b/server/data/corpora/presets/daa39639-2c08-43a2-9c48-f4f3ea68d494/documents/dbnl-excerpts-15.test.tsv/format
@@ -0,0 +1 @@
+tsv
\ No newline at end of file
diff --git a/server/data/corpora/presets/daa39639-2c08-43a2-9c48-f4f3ea68d494/documents/dbnl-excerpts-15.test.tsv/plaintext b/server/data/corpora/presets/daa39639-2c08-43a2-9c48-f4f3ea68d494/documents/dbnl-excerpts-15.test.tsv/plaintext
new file mode 100644
index 0000000..b6094de
--- /dev/null
+++ b/server/data/corpora/presets/daa39639-2c08-43a2-9c48-f4f3ea68d494/documents/dbnl-excerpts-15.test.tsv/plaintext
@@ -0,0 +1,141 @@
+Daer na sach ic sinte franciscus Die wel betoende dat hi der luider vrient was Want alsoe my dochte habitacien ende woninghen .
+Daer was alle blijschap sonder rouwe .
+Daer had elkerlijc meer van alle begeerten dan yemont soude konnen begeren .
+Maer een dinc wantroeste mi seer Dat was dat elckerlijc niet daer in en mocht comen tot sijnen begheren mits den inghanc die soe zeer nauwe ghewacht was .
+want cherubin was de portier Die een vyerich zwaert in sijnre hant hielt zeer snijdende aen beiden zijden Dies conste hi hem wel behelpen .
+want hi soe wel schermen const dat hem niemant voerbi lijden mocht sonder doot of ghewont te wesen .
+De prince van dier stat geboren om dat hi [ ] menschelichede angenomen had ende doer die stadt geleden was .
+ontfinc hi de doot gesteken mit eenre glauie in sijn zide ende liet sijn bloet daer te tolle Nochtan en was hi daer gheen tolle schuldich Al desghelijcs so deden alle sijn ridders ende soudeniers al droncken zij wt sijnre kelcken Ende ter passagen ontfinghen zij de doot Bouen ter cartelen van der poorten daer de portier niement en verdraghet So sach ic hanghen wimpelen gheuerwet met roeden bloede Als ic al dit merckende wart ende wel sach datmen mit fortse daer in most comen op datter ghen ander inganc en waer alst en was .
+Want elckerlijc was versaecht als hi cherubinne sach Daer om dochte my dat hi voertmeer sijn vuerich swaert wel besteden mocht Mar recht als ic mijn ogen op wart sloech ende opwaerts sach So sach ic daer een seer groet wonder dat mi alte seer verwonderde Want daer sach ic sinte augustijn sittende hoghe ten cartelen vander muere ende hy scheen wesende een vogelaer of een azere van vogelen Mit hem so waren veel ander luyden meesters ende leraers die alle holpen dat aes maken om de voghelen te asen ende te voeden Want mitten aze ende ghesuyckerden morselen die sij hielden ende die wytworpen die de voghelen aten veel luyden worden voghelen ende daer nae [ ] vlogen zij inde stat van bouen Voert sach ic daer veel iacopinen canoniken ende augustinen ende alle ander manieren van volcke die alle plumen vergaderden ende maecten daer of grote vlercken Daer na begonnen si te vlieghen om van bouen in dese stat te vlieghen ende alsoe daer in te comen sonder yet veel te gheuen om cherubins dangier
+